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 package com.android.contacts.list; 17 18 import android.accounts.Account; 19 import android.app.Activity; 20 import android.content.ActivityNotFoundException; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.Context; 24 import android.content.CursorLoader; 25 import android.content.Intent; 26 import android.content.Loader; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.res.Resources; 30 import android.database.Cursor; 31 import android.graphics.PorterDuff; 32 import android.graphics.Rect; 33 import android.graphics.drawable.Drawable; 34 import android.icu.text.MessageFormat; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.provider.ContactsContract; 39 import android.provider.ContactsContract.Directory; 40 import androidx.core.content.ContextCompat; 41 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.view.Gravity; 45 import android.view.LayoutInflater; 46 import android.view.Menu; 47 import android.view.MenuInflater; 48 import android.view.MenuItem; 49 import android.view.View; 50 import android.view.ViewGroup; 51 import android.view.accessibility.AccessibilityEvent; 52 import android.view.accessibility.AccessibilityManager; 53 import android.widget.Button; 54 import android.widget.FrameLayout; 55 import android.widget.ImageView; 56 import android.widget.LinearLayout.LayoutParams; 57 import android.widget.TextView; 58 import android.widget.Toast; 59 60 import com.android.contacts.ContactSaveService; 61 import com.android.contacts.Experiments; 62 import com.android.contacts.R; 63 import com.android.contacts.activities.ActionBarAdapter; 64 import com.android.contacts.activities.PeopleActivity; 65 import com.android.contacts.compat.CompatUtils; 66 import com.android.contacts.interactions.ContactDeletionInteraction; 67 import com.android.contacts.interactions.ContactMultiDeletionInteraction; 68 import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener; 69 import com.android.contacts.logging.ListEvent; 70 import com.android.contacts.logging.Logger; 71 import com.android.contacts.logging.ScreenEvent; 72 import com.android.contacts.model.AccountTypeManager; 73 import com.android.contacts.model.account.AccountInfo; 74 import com.android.contacts.model.account.AccountWithDataSet; 75 import com.android.contacts.quickcontact.QuickContactActivity; 76 import com.android.contacts.util.AccountFilterUtil; 77 import com.android.contacts.util.ImplicitIntentsUtil; 78 import com.android.contacts.util.SharedPreferenceUtil; 79 import com.android.contacts.util.SyncUtil; 80 import com.android.contactsbind.FeatureHighlightHelper; 81 import com.android.contactsbind.experiments.Flags; 82 import com.google.common.util.concurrent.Futures; 83 84 import java.util.HashMap; 85 import java.util.List; 86 import java.util.Locale; 87 import java.util.Map; 88 import java.util.concurrent.Future; 89 90 /** 91 * Fragment containing a contact list used for browsing (as compared to 92 * picking a contact with one of the PICK intents). 93 */ 94 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment 95 implements EnableGlobalSyncDialogFragment.Listener { 96 97 private static final String TAG = "DefaultListFragment"; 98 private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!"; 99 private static final String KEY_DELETION_IN_PROGRESS = "deletionInProgress"; 100 private static final String KEY_SEARCH_RESULT_CLICKED = "search_result_clicked"; 101 102 private static final int ACTIVITY_REQUEST_CODE_SHARE = 0; 103 104 private View mSearchHeaderView; 105 private View mSearchProgress; 106 private View mEmptyAccountView; 107 private View mEmptyHomeView; 108 private View mAccountFilterContainer; 109 private TextView mSearchProgressText; 110 111 private SwipeRefreshLayout mSwipeRefreshLayout; 112 private final Handler mHandler = new Handler(); 113 private final Runnable mCancelRefresh = new Runnable() { 114 @Override 115 public void run() { 116 if (mSwipeRefreshLayout.isRefreshing()) { 117 mSwipeRefreshLayout.setRefreshing(false); 118 } 119 } 120 }; 121 122 private View mAlertContainer; 123 private TextView mAlertText; 124 private ImageView mAlertDismissIcon; 125 private int mReasonSyncOff = SyncUtil.SYNC_SETTING_SYNC_ON; 126 127 private boolean mContactsAvailable; 128 private boolean mEnableDebugMenuOptions; 129 private boolean mIsRecreatedInstance; 130 private boolean mOptionsMenuContactsAvailable; 131 132 private boolean mCanSetActionBar = false; 133 134 /** 135 * If {@link #configureFragment()} is already called. Used to avoid calling it twice 136 * in {@link #onResume()}. 137 * (This initialization only needs to be done once in onResume() when the Activity was just 138 * created from scratch -- i.e. onCreate() was just called) 139 */ 140 private boolean mFragmentInitialized; 141 142 private boolean mFromOnNewIntent; 143 144 /** 145 * This is to tell whether we need to restart ContactMultiDeletionInteraction and set listener. 146 * if screen is rotated while deletion dialog is shown. 147 */ 148 private boolean mIsDeletionInProgress; 149 150 /** 151 * This is to disable {@link #onOptionsItemSelected} when we trying to stop the 152 * activity/fragment. 153 */ 154 private boolean mDisableOptionItemSelected; 155 156 private boolean mSearchResultClicked; 157 158 private ActionBarAdapter mActionBarAdapter; 159 private PeopleActivity mActivity; 160 private ContactsRequest mContactsRequest; 161 private ContactListFilterController mContactListFilterController; 162 163 private Future<List<AccountInfo>> mWritableAccountsFuture; 164 165 private final ActionBarAdapter.Listener mActionBarListener = new ActionBarAdapter.Listener() { 166 @Override 167 public void onAction(int action) { 168 switch (action) { 169 case ActionBarAdapter.Listener.Action.START_SELECTION_MODE: 170 displayCheckBoxes(true); 171 startSearchOrSelectionMode(); 172 break; 173 case ActionBarAdapter.Listener.Action.START_SEARCH_MODE: 174 if (!mIsRecreatedInstance) { 175 Logger.logScreenView(mActivity, ScreenEvent.ScreenType.SEARCH); 176 } 177 startSearchOrSelectionMode(); 178 break; 179 case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE: 180 mActivity.showFabWithAnimation(/* showFab */ true); 181 break; 182 case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE: 183 // If queryString is empty, fragment data will not be reloaded, 184 // so hamburger promo should be checked now. 185 // Otherwise, promo should be checked and displayed after reloading, b/30706521. 186 if (TextUtils.isEmpty(getQueryString())) { 187 maybeShowHamburgerFeatureHighlight(); 188 } 189 setQueryTextToFragment(""); 190 maybeHideCheckBoxes(); 191 mActivity.invalidateOptionsMenu(); 192 mActivity.showFabWithAnimation(/* showFab */ true); 193 194 // Alert user if sync is off and not dismissed before 195 setSyncOffAlert(); 196 197 // Determine whether the account has pullToRefresh feature 198 setSwipeRefreshLayoutEnabledOrNot(getFilter()); 199 break; 200 case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY: 201 final String queryString = mActionBarAdapter.getQueryString(); 202 setQueryTextToFragment(queryString); 203 updateDebugOptionsVisibility( 204 ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString)); 205 break; 206 default: 207 throw new IllegalStateException("Unknown ActionBarAdapter action: " + action); 208 } 209 } 210 211 private void startSearchOrSelectionMode() { 212 configureContactListFragment(); 213 maybeHideCheckBoxes(); 214 mActivity.invalidateOptionsMenu(); 215 mActivity.showFabWithAnimation(/* showFab */ false); 216 217 final Context context = getContext(); 218 if (!SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(context)) { 219 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(context); 220 } 221 } 222 223 private void updateDebugOptionsVisibility(boolean visible) { 224 if (mEnableDebugMenuOptions != visible) { 225 mEnableDebugMenuOptions = visible; 226 mActivity.invalidateOptionsMenu(); 227 } 228 } 229 230 private void setQueryTextToFragment(String query) { 231 setQueryString(query, true); 232 setVisibleScrollbarEnabled(!isSearchMode()); 233 } 234 235 @Override 236 public void onUpButtonPressed() { 237 mActivity.onBackPressed(); 238 } 239 }; 240 241 private final View.OnClickListener mAddContactListener = new View.OnClickListener() { 242 @Override 243 public void onClick(View v) { 244 AccountFilterUtil.startEditorIntent(getContext(), mActivity.getIntent(), getFilter()); 245 } 246 }; 247 DefaultContactBrowseListFragment()248 public DefaultContactBrowseListFragment() { 249 setPhotoLoaderEnabled(true); 250 // Don't use a QuickContactBadge. Just use a regular ImageView. Using a QuickContactBadge 251 // inside the ListView prevents us from using MODE_FULLY_EXPANDED and messes up ripples. 252 setQuickContactEnabled(false); 253 setSectionHeaderDisplayEnabled(true); 254 setVisibleScrollbarEnabled(true); 255 setDisplayDirectoryHeader(false); 256 setHasOptionsMenu(true); 257 } 258 259 /** 260 * Whether a search result was clicked by the user. Tracked so that we can distinguish 261 * between exiting the search mode after a result was clicked from exiting w/o clicking 262 * any search result. 263 */ wasSearchResultClicked()264 public boolean wasSearchResultClicked() { 265 return mSearchResultClicked; 266 } 267 268 /** 269 * Resets whether a search result was clicked by the user to false. 270 */ resetSearchResultClicked()271 public void resetSearchResultClicked() { 272 mSearchResultClicked = false; 273 } 274 275 @Override createCursorLoader(Context context)276 public CursorLoader createCursorLoader(Context context) { 277 return new FavoritesAndContactsLoader(context); 278 } 279 280 @Override onLoadFinished(Loader<Cursor> loader, Cursor data)281 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 282 if (loader.getId() == Directory.DEFAULT) { 283 bindListHeader(data == null ? 0 : data.getCount()); 284 } 285 super.onLoadFinished(loader, data); 286 if (!isSearchMode()) { 287 maybeShowHamburgerFeatureHighlight(); 288 } 289 if (mActionBarAdapter != null) { 290 mActionBarAdapter.updateOverflowButtonColor(); 291 } 292 } 293 maybeShowHamburgerFeatureHighlight()294 private void maybeShowHamburgerFeatureHighlight() { 295 if (mActionBarAdapter!= null && !mActionBarAdapter.isSearchMode() 296 && !mActionBarAdapter.isSelectionMode() 297 && !isTalkbackOnAndOnPreLollipopMr1() 298 && SharedPreferenceUtil.getShouldShowHamburgerPromo(getContext())) { 299 if (FeatureHighlightHelper.showHamburgerFeatureHighlight(mActivity)) { 300 SharedPreferenceUtil.setHamburgerPromoDisplayedBefore(getContext()); 301 } 302 } 303 } 304 305 // There's a crash if we show feature highlight when Talkback is on, on API 21 and below. 306 // See b/31180524. isTalkbackOnAndOnPreLollipopMr1()307 private boolean isTalkbackOnAndOnPreLollipopMr1(){ 308 return ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE)) 309 .isTouchExplorationEnabled() 310 && !CompatUtils.isLollipopMr1Compatible(); 311 } 312 bindListHeader(int numberOfContacts)313 private void bindListHeader(int numberOfContacts) { 314 final ContactListFilter filter = getFilter(); 315 // If the phone has at least one Google account whose sync status is unsyncable or pending 316 // or active, we have to make mAccountFilterContainer visible. 317 if (!isSearchMode() && numberOfContacts <= 0 && shouldShowEmptyView(filter)) { 318 if (filter != null && filter.isContactsFilterType()) { 319 makeViewVisible(mEmptyHomeView); 320 } else { 321 makeViewVisible(mEmptyAccountView); 322 } 323 return; 324 } 325 makeViewVisible(mAccountFilterContainer); 326 if (isSearchMode()) { 327 hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer); 328 } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) { 329 bindListHeaderCustom(getListView(), mAccountFilterContainer); 330 } else if (filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) { 331 final AccountWithDataSet accountWithDataSet = new AccountWithDataSet( 332 filter.accountName, filter.accountType, filter.dataSet); 333 bindListHeader(getContext(), getListView(), mAccountFilterContainer, 334 accountWithDataSet, numberOfContacts); 335 } else { 336 hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer); 337 } 338 } 339 340 /** 341 * If at least one Google account is unsyncable or its sync status is pending or active, we 342 * should not show empty view even if the number of contacts is 0. We should show sync status 343 * with empty list instead. 344 */ shouldShowEmptyView(ContactListFilter filter)345 private boolean shouldShowEmptyView(ContactListFilter filter) { 346 if (filter == null) { 347 return true; 348 } 349 // TODO(samchen) : Check ContactListFilter.FILTER_TYPE_CUSTOM 350 if (ContactListFilter.FILTER_TYPE_DEFAULT == filter.filterType 351 || ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS == filter.filterType) { 352 final List<AccountInfo> syncableAccounts = 353 AccountTypeManager.getInstance(getContext()).getWritableGoogleAccounts(); 354 355 if (syncableAccounts != null && syncableAccounts.size() > 0) { 356 for (AccountInfo info : syncableAccounts) { 357 // Won't be null because Google accounts have a non-null name and type. 358 final Account account = info.getAccount().getAccountOrNull(); 359 if (SyncUtil.isSyncStatusPendingOrActive(account) 360 || SyncUtil.isUnsyncableGoogleAccount(account)) { 361 return false; 362 } 363 } 364 } 365 } else if (ContactListFilter.FILTER_TYPE_ACCOUNT == filter.filterType) { 366 final Account account = new Account(filter.accountName, filter.accountType); 367 return !(SyncUtil.isSyncStatusPendingOrActive(account) 368 || SyncUtil.isUnsyncableGoogleAccount(account)); 369 } 370 return true; 371 } 372 373 // Show the view that's specified by id and hide the other two. makeViewVisible(View view)374 private void makeViewVisible(View view) { 375 mEmptyAccountView.setVisibility(view == mEmptyAccountView ? View.VISIBLE : View.GONE); 376 mEmptyHomeView.setVisibility(view == mEmptyHomeView ? View.VISIBLE : View.GONE); 377 mAccountFilterContainer.setVisibility( 378 view == mAccountFilterContainer ? View.VISIBLE : View.GONE); 379 } 380 scrollToTop()381 public void scrollToTop() { 382 if (getListView() != null) { 383 getListView().setSelection(0); 384 } 385 } 386 387 @Override onItemClick(int position, long id)388 protected void onItemClick(int position, long id) { 389 final Uri uri = getAdapter().getContactUri(position); 390 if (uri == null) { 391 return; 392 } 393 if (getAdapter().isDisplayingCheckBoxes()) { 394 super.onItemClick(position, id); 395 return; 396 } else { 397 if (isSearchMode()) { 398 mSearchResultClicked = true; 399 Logger.logSearchEvent(createSearchStateForSearchResultClick(position)); 400 } 401 } 402 viewContact(position, uri, getAdapter().isEnterpriseContact(position)); 403 } 404 405 @Override createListAdapter()406 protected ContactListAdapter createListAdapter() { 407 DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext()); 408 adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled()); 409 adapter.setDisplayPhotos(true); 410 adapter.setPhotoPosition( 411 ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false)); 412 return adapter; 413 } 414 415 @Override getFilter()416 public ContactListFilter getFilter() { 417 return mContactListFilterController.getFilter(); 418 } 419 420 @Override inflateView(LayoutInflater inflater, ViewGroup container)421 protected View inflateView(LayoutInflater inflater, ViewGroup container) { 422 final View view = inflater.inflate(R.layout.contact_list_content, null); 423 424 mAccountFilterContainer = view.findViewById(R.id.account_filter_header_container); 425 426 // Add empty main view and account view to list. 427 final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list); 428 mEmptyAccountView = getEmptyAccountView(inflater); 429 mEmptyHomeView = getEmptyHomeView(inflater); 430 contactListLayout.addView(mEmptyAccountView); 431 contactListLayout.addView(mEmptyHomeView); 432 433 return view; 434 } 435 getEmptyHomeView(LayoutInflater inflater)436 private View getEmptyHomeView(LayoutInflater inflater) { 437 final View emptyHomeView = inflater.inflate(R.layout.empty_home_view, null); 438 // Set image margins. 439 final ImageView image = (ImageView) emptyHomeView.findViewById(R.id.empty_home_image); 440 final LayoutParams params = (LayoutParams) image.getLayoutParams(); 441 final int screenHeight = getResources().getDisplayMetrics().heightPixels; 442 final int marginTop = screenHeight / 2 - 443 getResources().getDimensionPixelSize(R.dimen.empty_home_view_image_offset) ; 444 params.setMargins(0, marginTop, 0, 0); 445 params.gravity = Gravity.CENTER_HORIZONTAL; 446 image.setLayoutParams(params); 447 448 // Set up add contact button. 449 final Button addContactButton = 450 (Button) emptyHomeView.findViewById(R.id.add_contact_button); 451 addContactButton.setOnClickListener(mAddContactListener); 452 return emptyHomeView; 453 } 454 getEmptyAccountView(LayoutInflater inflater)455 private View getEmptyAccountView(LayoutInflater inflater) { 456 final View emptyAccountView = inflater.inflate(R.layout.empty_account_view, null); 457 // Set image margins. 458 final ImageView image = (ImageView) emptyAccountView.findViewById(R.id.empty_account_image); 459 final LayoutParams params = (LayoutParams) image.getLayoutParams(); 460 final int height = getResources().getDisplayMetrics().heightPixels; 461 final int divisor = 462 getResources().getInteger(R.integer.empty_account_view_image_margin_divisor); 463 final int offset = 464 getResources().getDimensionPixelSize(R.dimen.empty_account_view_image_offset); 465 params.setMargins(0, height / divisor + offset, 0, 0); 466 params.gravity = Gravity.CENTER_HORIZONTAL; 467 image.setLayoutParams(params); 468 469 // Set up add contact button. 470 final Button addContactButton = 471 (Button) emptyAccountView.findViewById(R.id.add_contact_button); 472 addContactButton.setOnClickListener(mAddContactListener); 473 return emptyAccountView; 474 } 475 476 @Override onCreate(Bundle savedState)477 public void onCreate(Bundle savedState) { 478 super.onCreate(savedState); 479 mIsRecreatedInstance = (savedState != null); 480 mContactListFilterController = ContactListFilterController.getInstance(getContext()); 481 mContactListFilterController.checkFilterValidity(false); 482 // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one. 483 // This is useful when user upgrades app while an account filter was 484 // stored in sharedPreference in a previous version of Contacts app. 485 final ContactListFilter filter = mIsRecreatedInstance 486 ? getFilter() 487 : AccountFilterUtil.createContactsFilter(getContext()); 488 setContactListFilter(filter); 489 } 490 491 @Override onCreateView(LayoutInflater inflater, ViewGroup container)492 protected void onCreateView(LayoutInflater inflater, ViewGroup container) { 493 super.onCreateView(inflater, container); 494 495 initSwipeRefreshLayout(); 496 497 // Putting the header view inside a container will allow us to make 498 // it invisible later. See checkHeaderViewVisibility() 499 final FrameLayout headerContainer = new FrameLayout(inflater.getContext()); 500 mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false); 501 headerContainer.addView(mSearchHeaderView); 502 getListView().addHeaderView(headerContainer, null, false); 503 checkHeaderViewVisibility(); 504 505 mSearchProgress = getView().findViewById(R.id.search_progress); 506 mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText); 507 508 mAlertContainer = getView().findViewById(R.id.alert_container); 509 mAlertText = (TextView) mAlertContainer.findViewById(R.id.alert_text); 510 mAlertDismissIcon = (ImageView) mAlertContainer.findViewById(R.id.alert_dismiss_icon); 511 mAlertText.setOnClickListener(new View.OnClickListener() { 512 @Override 513 public void onClick(View v) { 514 turnSyncOn(); 515 } 516 }); 517 mAlertDismissIcon.setOnClickListener(new View.OnClickListener() { 518 @Override 519 public void onClick(View v) { 520 dismiss(); 521 } 522 }); 523 524 mAlertContainer.setVisibility(View.GONE); 525 } 526 turnSyncOn()527 private void turnSyncOn() { 528 final ContactListFilter filter = getFilter(); 529 if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT 530 && mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) { 531 ContentResolver.setSyncAutomatically( 532 new Account(filter.accountName, filter.accountType), 533 ContactsContract.AUTHORITY, true); 534 mAlertContainer.setVisibility(View.GONE); 535 } else { 536 final EnableGlobalSyncDialogFragment dialog = new 537 EnableGlobalSyncDialogFragment(); 538 dialog.show(this, filter); 539 } 540 } 541 542 @Override onEnableAutoSync(ContactListFilter filter)543 public void onEnableAutoSync(ContactListFilter filter) { 544 // Turn on auto-sync 545 ContentResolver.setMasterSyncAutomatically(true); 546 547 // This should be OK (won't block) because this only happens after a user action 548 final List<AccountInfo> accountInfos = Futures.getUnchecked(mWritableAccountsFuture); 549 // Also enable Contacts sync 550 final List<AccountWithDataSet> accounts = AccountInfo.extractAccounts(accountInfos); 551 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts); 552 if (syncableAccounts != null && syncableAccounts.size() > 0) { 553 for (Account account : syncableAccounts) { 554 ContentResolver.setSyncAutomatically(new Account(account.name, account.type), 555 ContactsContract.AUTHORITY, true); 556 } 557 } 558 mAlertContainer.setVisibility(View.GONE); 559 } 560 dismiss()561 private void dismiss() { 562 if (mReasonSyncOff == SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF) { 563 SharedPreferenceUtil.incNumOfDismissesForAutoSyncOff(getContext()); 564 } else if (mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) { 565 SharedPreferenceUtil.incNumOfDismissesForAccountSyncOff( 566 getContext(), getFilter().accountName); 567 } 568 mAlertContainer.setVisibility(View.GONE); 569 } 570 initSwipeRefreshLayout()571 private void initSwipeRefreshLayout() { 572 mSwipeRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh); 573 if (mSwipeRefreshLayout == null) { 574 return; 575 } 576 577 mSwipeRefreshLayout.setEnabled(true); 578 // Request sync contacts 579 mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 580 @Override 581 public void onRefresh() { 582 mHandler.removeCallbacks(mCancelRefresh); 583 584 final boolean isNetworkConnected = SyncUtil.isNetworkConnected(getContext()); 585 if (!isNetworkConnected) { 586 mSwipeRefreshLayout.setRefreshing(false); 587 ((PeopleActivity)getActivity()).showConnectionErrorMsg(); 588 return; 589 } 590 591 syncContacts(getFilter()); 592 mHandler.postDelayed(mCancelRefresh, Flags.getInstance() 593 .getInteger(Experiments.PULL_TO_REFRESH_CANCEL_REFRESH_MILLIS)); 594 } 595 }); 596 mSwipeRefreshLayout.setColorSchemeResources( 597 R.color.swipe_refresh_color1, 598 R.color.swipe_refresh_color2, 599 R.color.swipe_refresh_color3, 600 R.color.swipe_refresh_color4); 601 mSwipeRefreshLayout.setDistanceToTriggerSync( 602 (int) getResources().getDimension(R.dimen.pull_to_refresh_distance)); 603 } 604 605 /** 606 * Request sync for the Google accounts (not include Google+ accounts) specified by the given 607 * filter. 608 */ syncContacts(ContactListFilter filter)609 private void syncContacts(ContactListFilter filter) { 610 if (filter == null) { 611 return; 612 } 613 614 final Bundle bundle = new Bundle(); 615 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 616 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 617 618 final List<AccountWithDataSet> accounts = AccountInfo.extractAccounts( 619 Futures.getUnchecked(mWritableAccountsFuture)); 620 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts); 621 if (syncableAccounts != null && syncableAccounts.size() > 0) { 622 for (Account account : syncableAccounts) { 623 // We can prioritize Contacts sync if sync is not initialized yet. 624 if (!SyncUtil.isSyncStatusPendingOrActive(account) 625 || SyncUtil.isUnsyncableGoogleAccount(account)) { 626 ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle); 627 } 628 } 629 } 630 } 631 setSyncOffAlert()632 private void setSyncOffAlert() { 633 final ContactListFilter filter = getFilter(); 634 final Account account = filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT 635 && filter.isGoogleAccountType() 636 ? new Account(filter.accountName, filter.accountType) : null; 637 638 if (account == null && !filter.isContactsFilterType()) { 639 mAlertContainer.setVisibility(View.GONE); 640 } else { 641 mReasonSyncOff = SyncUtil.calculateReasonSyncOff(getContext(), account); 642 final boolean isAlertVisible = 643 SyncUtil.isAlertVisible(getContext(), account, mReasonSyncOff); 644 setSyncOffMsg(mReasonSyncOff); 645 mAlertContainer.setVisibility(isAlertVisible ? View.VISIBLE : View.GONE); 646 } 647 } 648 setSyncOffMsg(int reason)649 private void setSyncOffMsg(int reason) { 650 final Resources resources = getResources(); 651 switch (reason) { 652 case SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF: 653 mAlertText.setText(resources.getString(R.string.auto_sync_off)); 654 break; 655 case SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF: 656 mAlertText.setText(resources.getString(R.string.account_sync_off)); 657 break; 658 default: 659 } 660 } 661 662 @Override onActivityCreated(Bundle savedInstanceState)663 public void onActivityCreated(Bundle savedInstanceState) { 664 super.onActivityCreated(savedInstanceState); 665 666 mActivity = (PeopleActivity) getActivity(); 667 mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener, 668 mActivity.getSupportActionBar(), mActivity.getToolbar(), 669 R.string.enter_contact_name); 670 mActionBarAdapter.setShowHomeIcon(true); 671 initializeActionBarAdapter(savedInstanceState); 672 if (isSearchMode()) { 673 mActionBarAdapter.setFocusOnSearchView(); 674 } 675 676 setCheckBoxListListener(new CheckBoxListListener()); 677 setOnContactListActionListener(new ContactBrowserActionListener()); 678 if (savedInstanceState != null) { 679 if (savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) { 680 deleteSelectedContacts(); 681 } 682 mSearchResultClicked = savedInstanceState.getBoolean(KEY_SEARCH_RESULT_CLICKED); 683 } 684 685 setDirectorySearchMode(); 686 mCanSetActionBar = true; 687 } 688 initializeActionBarAdapter(Bundle savedInstanceState)689 public void initializeActionBarAdapter(Bundle savedInstanceState) { 690 if (mActionBarAdapter != null) { 691 mActionBarAdapter.initialize(savedInstanceState, mContactsRequest); 692 } 693 } 694 configureFragment()695 private void configureFragment() { 696 if (mFragmentInitialized && !mFromOnNewIntent) { 697 return; 698 } 699 700 mFragmentInitialized = true; 701 702 if (mFromOnNewIntent || !mIsRecreatedInstance) { 703 mFromOnNewIntent = false; 704 configureFragmentForRequest(); 705 } 706 707 configureContactListFragment(); 708 } 709 configureFragmentForRequest()710 private void configureFragmentForRequest() { 711 ContactListFilter filter = null; 712 final int actionCode = mContactsRequest.getActionCode(); 713 boolean searchMode = mContactsRequest.isSearchMode(); 714 switch (actionCode) { 715 case ContactsRequest.ACTION_ALL_CONTACTS: 716 filter = AccountFilterUtil.createContactsFilter(getContext()); 717 break; 718 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES: 719 filter = ContactListFilter.createFilterWithType( 720 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY); 721 break; 722 723 case ContactsRequest.ACTION_FREQUENT: 724 case ContactsRequest.ACTION_STREQUENT: 725 case ContactsRequest.ACTION_STARRED: 726 case ContactsRequest.ACTION_VIEW_CONTACT: 727 default: 728 break; 729 } 730 731 if (filter != null) { 732 setContactListFilter(filter); 733 searchMode = false; 734 } 735 736 if (mContactsRequest.getContactUri() != null) { 737 searchMode = false; 738 } 739 740 mActionBarAdapter.setSearchMode(searchMode); 741 configureContactListFragmentForRequest(); 742 } 743 configureContactListFragmentForRequest()744 private void configureContactListFragmentForRequest() { 745 final Uri contactUri = mContactsRequest.getContactUri(); 746 if (contactUri != null) { 747 setSelectedContactUri(contactUri); 748 } 749 750 setQueryString(mActionBarAdapter.getQueryString(), true); 751 setVisibleScrollbarEnabled(!isSearchMode()); 752 } 753 setDirectorySearchMode()754 private void setDirectorySearchMode() { 755 if (mContactsRequest != null && mContactsRequest.isDirectorySearchEnabled()) { 756 setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT); 757 } else { 758 setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); 759 } 760 } 761 762 @Override onResume()763 public void onResume() { 764 super.onResume(); 765 configureFragment(); 766 maybeShowHamburgerFeatureHighlight(); 767 // Re-register the listener, which may have been cleared when onSaveInstanceState was 768 // called. See also: onSaveInstanceState 769 mActionBarAdapter.setListener(mActionBarListener); 770 mDisableOptionItemSelected = false; 771 maybeHideCheckBoxes(); 772 773 mWritableAccountsFuture = AccountTypeManager.getInstance(getContext()).filterAccountsAsync( 774 AccountTypeManager.writableFilter()); 775 } 776 maybeHideCheckBoxes()777 private void maybeHideCheckBoxes() { 778 if (!mActionBarAdapter.isSelectionMode()) { 779 displayCheckBoxes(false); 780 } 781 } 782 getActionBarAdapter()783 public ActionBarAdapter getActionBarAdapter(){ 784 return mActionBarAdapter; 785 } 786 787 @Override setSearchMode(boolean flag)788 protected void setSearchMode(boolean flag) { 789 super.setSearchMode(flag); 790 checkHeaderViewVisibility(); 791 if (!flag) showSearchProgress(false); 792 } 793 794 /** Show or hide the directory-search progress spinner. */ showSearchProgress(boolean show)795 private void showSearchProgress(boolean show) { 796 if (mSearchProgress != null) { 797 mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE); 798 } 799 } 800 checkHeaderViewVisibility()801 private void checkHeaderViewVisibility() { 802 // Hide the search header by default. 803 if (mSearchHeaderView != null) { 804 mSearchHeaderView.setVisibility(View.GONE); 805 } 806 } 807 808 @Override setListHeader()809 protected void setListHeader() { 810 if (!isSearchMode()) { 811 return; 812 } 813 ContactListAdapter adapter = getAdapter(); 814 if (adapter == null) { 815 return; 816 } 817 818 // In search mode we only display the header if there is nothing found 819 if (TextUtils.isEmpty(getQueryString()) || !adapter.areAllPartitionsEmpty()) { 820 mSearchHeaderView.setVisibility(View.GONE); 821 showSearchProgress(false); 822 } else { 823 mSearchHeaderView.setVisibility(View.VISIBLE); 824 if (adapter.isLoading()) { 825 mSearchProgressText.setText(R.string.search_results_searching); 826 showSearchProgress(true); 827 } else { 828 mSearchProgressText.setText(R.string.listFoundAllContactsZero); 829 mSearchProgressText.sendAccessibilityEvent( 830 AccessibilityEvent.TYPE_VIEW_SELECTED); 831 showSearchProgress(false); 832 } 833 } 834 } 835 getSwipeRefreshLayout()836 public SwipeRefreshLayout getSwipeRefreshLayout() { 837 return mSwipeRefreshLayout; 838 } 839 840 private final class CheckBoxListListener implements OnCheckBoxListActionListener { 841 @Override onStartDisplayingCheckBoxes()842 public void onStartDisplayingCheckBoxes() { 843 mActionBarAdapter.setSelectionMode(true); 844 mActivity.invalidateOptionsMenu(); 845 } 846 847 @Override onSelectedContactIdsChanged()848 public void onSelectedContactIdsChanged() { 849 mActionBarAdapter.setSelectionCount(getSelectedContactIds().size()); 850 mActivity.invalidateOptionsMenu(); 851 mActionBarAdapter.updateOverflowButtonColor(); 852 } 853 854 @Override onStopDisplayingCheckBoxes()855 public void onStopDisplayingCheckBoxes() { 856 mActionBarAdapter.setSelectionMode(false); 857 } 858 } 859 setFilterAndUpdateTitle(ContactListFilter filter)860 public void setFilterAndUpdateTitle(ContactListFilter filter) { 861 setFilterAndUpdateTitle(filter, true); 862 } 863 setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri)864 private void setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri) { 865 setContactListFilter(filter); 866 updateListFilter(filter, restoreSelectedUri); 867 mActivity.setTitle(AccountFilterUtil.getActionBarTitleForFilter(mActivity, filter)); 868 869 // Alert user if sync is off and not dismissed before 870 setSyncOffAlert(); 871 872 // Determine whether the account has pullToRefresh feature 873 setSwipeRefreshLayoutEnabledOrNot(filter); 874 } 875 setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter)876 private void setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter) { 877 final SwipeRefreshLayout swipeRefreshLayout = getSwipeRefreshLayout(); 878 if (swipeRefreshLayout == null) { 879 if (Log.isLoggable(TAG, Log.DEBUG)) { 880 Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null"); 881 } 882 return; 883 } 884 885 swipeRefreshLayout.setRefreshing(false); 886 swipeRefreshLayout.setEnabled(false); 887 888 if (filter != null && !mActionBarAdapter.isSearchMode() 889 && !mActionBarAdapter.isSelectionMode()) { 890 if (filter.isSyncable() 891 || (filter.shouldShowSyncState() 892 && SyncUtil.hasSyncableAccount(AccountTypeManager.getInstance(getContext())))) { 893 swipeRefreshLayout.setEnabled(true); 894 } 895 } 896 } 897 configureContactListFragment()898 private void configureContactListFragment() { 899 // Filter may be changed when activity is in background. 900 setFilterAndUpdateTitle(getFilter()); 901 setVerticalScrollbarPosition(getScrollBarPosition()); 902 setSelectionVisible(false); 903 mActivity.invalidateOptionsMenu(); 904 } 905 getScrollBarPosition()906 private int getScrollBarPosition() { 907 final Locale locale = Locale.getDefault(); 908 final boolean isRTL = 909 TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL; 910 return isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT; 911 } 912 913 private final class ContactBrowserActionListener implements OnContactBrowserActionListener { ContactBrowserActionListener()914 ContactBrowserActionListener() {} 915 916 @Override onSelectionChange()917 public void onSelectionChange() { 918 } 919 920 @Override onViewContactAction(int position, Uri contactLookupUri, boolean isEnterpriseContact)921 public void onViewContactAction(int position, Uri contactLookupUri, 922 boolean isEnterpriseContact) { 923 if (isEnterpriseContact) { 924 // No implicit intent as user may have a different contacts app in work profile. 925 ContactsContract.QuickContact.showQuickContact(getContext(), new Rect(), 926 contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null); 927 } else { 928 final int previousScreen; 929 if (isSearchMode()) { 930 previousScreen = ScreenEvent.ScreenType.SEARCH; 931 } else { 932 if (isAllContactsFilter(getFilter())) { 933 if (position < getAdapter().getNumberOfFavorites()) { 934 previousScreen = ScreenEvent.ScreenType.FAVORITES; 935 } else { 936 previousScreen = ScreenEvent.ScreenType.ALL_CONTACTS; 937 } 938 } else { 939 previousScreen = ScreenEvent.ScreenType.LIST_ACCOUNT; 940 } 941 } 942 943 Logger.logListEvent(ListEvent.ActionType.CLICK, 944 /* listType */ getListTypeIncludingSearch(), 945 /* count */ getAdapter().getCount(), 946 /* clickedIndex */ position, /* numSelected */ 0); 947 948 ImplicitIntentsUtil.startQuickContact( 949 getActivity(), contactLookupUri, previousScreen); 950 } 951 } 952 953 @Override onDeleteContactAction(Uri contactUri)954 public void onDeleteContactAction(Uri contactUri) { 955 ContactDeletionInteraction.start(mActivity, contactUri, false); 956 } 957 958 @Override onFinishAction()959 public void onFinishAction() { 960 mActivity.onBackPressed(); 961 } 962 963 @Override onInvalidSelection()964 public void onInvalidSelection() { 965 ContactListFilter filter; 966 ContactListFilter currentFilter = getFilter(); 967 if (currentFilter != null 968 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 969 filter = AccountFilterUtil.createContactsFilter(getContext()); 970 setFilterAndUpdateTitle(filter); 971 } else { 972 filter = ContactListFilter.createFilterWithType( 973 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT); 974 setFilterAndUpdateTitle(filter, /* restoreSelectedUri */ false); 975 } 976 setContactListFilter(filter); 977 } 978 } 979 isAllContactsFilter(ContactListFilter filter)980 private boolean isAllContactsFilter(ContactListFilter filter) { 981 return filter != null && filter.isContactsFilterType(); 982 } 983 setContactsAvailable(boolean contactsAvailable)984 public void setContactsAvailable(boolean contactsAvailable) { 985 mContactsAvailable = contactsAvailable; 986 } 987 988 /** 989 * Set filter via ContactListFilterController 990 */ setContactListFilter(ContactListFilter filter)991 private void setContactListFilter(ContactListFilter filter) { 992 mContactListFilterController.setContactListFilter(filter, 993 /* persistent */ isAllContactsFilter(filter)); 994 } 995 996 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)997 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 998 if (!mContactsAvailable || mActivity.isInSecondLevel()) { 999 // If contacts aren't available or this fragment is not visible, hide all menu items. 1000 return; 1001 } 1002 super.onCreateOptionsMenu(menu, inflater); 1003 inflater.inflate(R.menu.people_options, menu); 1004 } 1005 1006 @Override onPrepareOptionsMenu(Menu menu)1007 public void onPrepareOptionsMenu(Menu menu) { 1008 mOptionsMenuContactsAvailable = mContactsAvailable; 1009 if (!mOptionsMenuContactsAvailable) { 1010 return; 1011 } 1012 1013 final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode() 1014 || mActionBarAdapter.isSelectionMode(); 1015 makeMenuItemVisible(menu, R.id.menu_search, !isSearchOrSelectionMode); 1016 1017 final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode() 1018 && getSelectedContactIds().size() != 0; 1019 makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions); 1020 makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions); 1021 final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode() 1022 && getSelectedContactIds().size() > 1; 1023 makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions); 1024 1025 // Debug options need to be visible even in search mode. 1026 makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions && 1027 hasExportIntentHandler()); 1028 1029 // Light tint the icons for normal mode, dark tint for search or selection mode. 1030 for (int i = 0; i < menu.size(); ++i) { 1031 final Drawable icon = menu.getItem(i).getIcon(); 1032 if (icon != null && !isSearchOrSelectionMode) { 1033 icon.mutate().setColorFilter(ContextCompat.getColor(getContext(), 1034 R.color.actionbar_icon_color), PorterDuff.Mode.SRC_ATOP); 1035 } 1036 } 1037 } 1038 makeMenuItemVisible(Menu menu, int itemId, boolean visible)1039 private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) { 1040 final MenuItem item = menu.findItem(itemId); 1041 if (item != null) { 1042 item.setVisible(visible); 1043 } 1044 } 1045 hasExportIntentHandler()1046 private boolean hasExportIntentHandler() { 1047 final Intent intent = new Intent(); 1048 intent.setAction("com.android.providers.contacts.DUMP_DATABASE"); 1049 final List<ResolveInfo> receivers = 1050 getContext().getPackageManager().queryIntentActivities(intent, 1051 PackageManager.MATCH_DEFAULT_ONLY); 1052 return receivers != null && receivers.size() > 0; 1053 } 1054 1055 @Override onOptionsItemSelected(MenuItem item)1056 public boolean onOptionsItemSelected(MenuItem item) { 1057 if (mDisableOptionItemSelected) { 1058 return false; 1059 } 1060 1061 final int id = item.getItemId(); 1062 if (id == android.R.id.home) { 1063 if (mActionBarAdapter.isUpShowing()) { 1064 // "UP" icon press -- should be treated as "back". 1065 mActivity.onBackPressed(); 1066 } 1067 return true; 1068 } else if (id == R.id.menu_search) { 1069 if (!mActionBarAdapter.isSelectionMode()) { 1070 mActionBarAdapter.setSearchMode(true); 1071 } 1072 return true; 1073 } else if (id == R.id.menu_share) { 1074 shareSelectedContacts(); 1075 return true; 1076 } else if (id == R.id.menu_join) { 1077 Logger.logListEvent(ListEvent.ActionType.LINK, 1078 /* listType */ getListTypeIncludingSearch(), 1079 /* count */ getAdapter().getCount(), /* clickedIndex */ -1, 1080 /* numSelected */ getAdapter().getSelectedContactIds().size()); 1081 joinSelectedContacts(); 1082 return true; 1083 } else if (id == R.id.menu_delete) { 1084 deleteSelectedContacts(); 1085 return true; 1086 } else if (id == R.id.export_database) { 1087 final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE"); 1088 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 1089 ImplicitIntentsUtil.startActivityOutsideApp(getContext(), intent); 1090 return true; 1091 } 1092 return super.onOptionsItemSelected(item); 1093 } 1094 1095 /** 1096 * Share all contacts that are currently selected. This method is pretty inefficient for 1097 * handling large numbers of contacts. I don't expect this to be a problem. 1098 */ shareSelectedContacts()1099 private void shareSelectedContacts() { 1100 final StringBuilder uriListBuilder = new StringBuilder(); 1101 for (Long contactId : getSelectedContactIds()) { 1102 final Uri contactUri = ContentUris.withAppendedId( 1103 ContactsContract.Contacts.CONTENT_URI, contactId); 1104 final Uri lookupUri = ContactsContract.Contacts.getLookupUri( 1105 getContext().getContentResolver(), contactUri); 1106 if (lookupUri == null) { 1107 continue; 1108 } 1109 final List<String> pathSegments = lookupUri.getPathSegments(); 1110 if (pathSegments.size() < 2) { 1111 continue; 1112 } 1113 final String lookupKey = pathSegments.get(pathSegments.size() - 2); 1114 if (uriListBuilder.length() > 0) { 1115 uriListBuilder.append(':'); 1116 } 1117 uriListBuilder.append(Uri.encode(lookupKey)); 1118 } 1119 if (uriListBuilder.length() == 0) { 1120 return; 1121 } 1122 final Uri uri = Uri.withAppendedPath( 1123 ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI, 1124 Uri.encode(uriListBuilder.toString())); 1125 final Intent intent = new Intent(Intent.ACTION_SEND); 1126 intent.setType(ContactsContract.Contacts.CONTENT_VCARD_TYPE); 1127 intent.putExtra(Intent.EXTRA_STREAM, uri); 1128 try { 1129 MessageFormat msgFormat = new MessageFormat( 1130 getResources().getString(R.string.title_share_via), 1131 Locale.getDefault()); 1132 Map<String, Object> arguments = new HashMap<>(); 1133 arguments.put("count", getSelectedContactIds().size()); 1134 startActivityForResult(Intent.createChooser(intent, msgFormat.format(arguments)) 1135 , ACTIVITY_REQUEST_CODE_SHARE); 1136 } catch (final ActivityNotFoundException ex) { 1137 Toast.makeText(getContext(), R.string.share_error, Toast.LENGTH_SHORT).show(); 1138 } 1139 } 1140 joinSelectedContacts()1141 private void joinSelectedContacts() { 1142 final Context context = getContext(); 1143 final Intent intent = ContactSaveService.createJoinSeveralContactsIntent( 1144 context, getSelectedContactIdsArray()); 1145 context.startService(intent); 1146 1147 mActionBarAdapter.setSelectionMode(false); 1148 } 1149 deleteSelectedContacts()1150 private void deleteSelectedContacts() { 1151 final ContactMultiDeletionInteraction multiDeletionInteraction = 1152 ContactMultiDeletionInteraction.start(this, getSelectedContactIds()); 1153 multiDeletionInteraction.setListener(new MultiDeleteListener()); 1154 mIsDeletionInProgress = true; 1155 } 1156 1157 private final class MultiDeleteListener implements MultiContactDeleteListener { 1158 @Override onDeletionFinished()1159 public void onDeletionFinished() { 1160 // The parameters count and numSelected are both the number of contacts before deletion. 1161 Logger.logListEvent(ListEvent.ActionType.DELETE, 1162 /* listType */ getListTypeIncludingSearch(), 1163 /* count */ getAdapter().getCount(), /* clickedIndex */ -1, 1164 /* numSelected */ getSelectedContactIds().size()); 1165 mActionBarAdapter.setSelectionMode(false); 1166 mIsDeletionInProgress = false; 1167 } 1168 } 1169 getListTypeIncludingSearch()1170 private int getListTypeIncludingSearch() { 1171 return isSearchMode() ? ListEvent.ListType.SEARCH_RESULT : getListType(); 1172 } 1173 setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent)1174 public void setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent) { 1175 mContactsRequest = contactsRequest; 1176 mFromOnNewIntent = fromOnNewIntent; 1177 } 1178 1179 @Override onActivityResult(int requestCode, int resultCode, Intent data)1180 public void onActivityResult(int requestCode, int resultCode, Intent data) { 1181 switch (requestCode) { 1182 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed 1183 // anymore 1184 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER: 1185 if (resultCode == Activity.RESULT_OK) { 1186 onPickerResult(data); 1187 } 1188 case ACTIVITY_REQUEST_CODE_SHARE: 1189 Logger.logListEvent(ListEvent.ActionType.SHARE, 1190 /* listType */ getListTypeIncludingSearch(), 1191 /* count */ getAdapter().getCount(), /* clickedIndex */ -1, 1192 /* numSelected */ getAdapter().getSelectedContactIds().size()); 1193 1194 // TODO fix or remove multipicker code: ag/54762 1195 // else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { 1196 // // Finish the activity if the sub activity was canceled as back key is used 1197 // // to confirm user selection in MODE_PICK_MULTIPLE_PHONES. 1198 // finish(); 1199 // } 1200 // break; 1201 } 1202 } 1203 getOptionsMenuContactsAvailable()1204 public boolean getOptionsMenuContactsAvailable() { 1205 return mOptionsMenuContactsAvailable; 1206 } 1207 1208 @Override onSaveInstanceState(Bundle outState)1209 public void onSaveInstanceState(Bundle outState) { 1210 super.onSaveInstanceState(outState); 1211 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState, 1212 // in order to avoid doing fragment transactions after it. 1213 // TODO Figure out a better way to deal with the issue (ag/120686). 1214 if (mActionBarAdapter != null) { 1215 mActionBarAdapter.setListener(null); 1216 mActionBarAdapter.onSaveInstanceState(outState); 1217 } 1218 mDisableOptionItemSelected = true; 1219 outState.putBoolean(KEY_DELETION_IN_PROGRESS, mIsDeletionInProgress); 1220 outState.putBoolean(KEY_SEARCH_RESULT_CLICKED, mSearchResultClicked); 1221 } 1222 1223 @Override onPause()1224 public void onPause() { 1225 mOptionsMenuContactsAvailable = false; 1226 super.onPause(); 1227 } 1228 1229 @Override onDestroy()1230 public void onDestroy() { 1231 if (mActionBarAdapter != null) { 1232 mActionBarAdapter.setListener(null); 1233 } 1234 super.onDestroy(); 1235 } 1236 onKeyDown(int unicodeChar)1237 public boolean onKeyDown(int unicodeChar) { 1238 if (mActionBarAdapter != null && mActionBarAdapter.isSelectionMode()) { 1239 // Ignore keyboard input when in selection mode. 1240 return true; 1241 } 1242 1243 if (mActionBarAdapter != null && !mActionBarAdapter.isSearchMode()) { 1244 final String query = new String(new int[]{unicodeChar}, 0, 1); 1245 mActionBarAdapter.setSearchMode(true); 1246 mActionBarAdapter.setQueryString(query); 1247 return true; 1248 } 1249 1250 return false; 1251 } 1252 canSetActionBar()1253 public boolean canSetActionBar() { 1254 return mCanSetActionBar; 1255 } 1256 } 1257