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