blob: f7ae30d2bf47ed005962311a55d88f95d2a3ecae [file] [log] [blame]
Dmitri Plotnikov238d9842010-05-04 14:14:57 -07001/*
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 */
16package com.android.contacts.list;
17
Xi Chen947c23d2016-06-30 20:23:52 -070018import android.accounts.Account;
Wenyi Wang587b8f22016-08-17 17:40:45 -070019import android.app.Activity;
20import android.content.ActivityNotFoundException;
Xi Chen947c23d2016-06-30 20:23:52 -070021import android.content.ContentResolver;
Wenyi Wang587b8f22016-08-17 17:40:45 -070022import android.content.ContentUris;
Jay Shrauner8c42b522013-08-15 09:32:47 -070023import android.content.Context;
Dave Santoro84cac442011-08-24 15:23:10 -070024import android.content.CursorLoader;
Wenyi Wang587b8f22016-08-17 17:40:45 -070025import android.content.Intent;
Wenyi Wang1114fde2016-07-11 21:44:02 -070026import android.content.Loader;
Wenyi Wang587b8f22016-08-17 17:40:45 -070027import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
yaolu491cca52016-09-30 14:04:40 -070029import android.content.res.Resources;
Wenyi Wang1114fde2016-07-11 21:44:02 -070030import android.database.Cursor;
Wenyi Wang587b8f22016-08-17 17:40:45 -070031import android.graphics.Rect;
Brian Attwellb831b142014-10-03 15:45:18 -070032import android.net.Uri;
Xi Chen947c23d2016-06-30 20:23:52 -070033import android.os.Bundle;
yaolu9d998592016-10-14 23:21:00 -070034import android.os.Handler;
Xi Chen947c23d2016-06-30 20:23:52 -070035import android.provider.ContactsContract;
Wenyi Wang5db18212016-08-10 17:11:26 -070036import android.provider.ContactsContract.Directory;
Walter Jang6367d2a2016-08-15 14:51:18 -070037import android.support.v4.widget.SwipeRefreshLayout;
Dmitri Plotnikov1a57fb22011-01-14 14:53:37 -080038import android.text.TextUtils;
Wenyi Wang587b8f22016-08-17 17:40:45 -070039import android.util.Log;
Wenyi Wangf128ea82016-07-25 11:56:40 -070040import android.view.Gravity;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070041import android.view.LayoutInflater;
Wenyi Wang587b8f22016-08-17 17:40:45 -070042import android.view.Menu;
43import android.view.MenuInflater;
44import android.view.MenuItem;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070045import android.view.View;
46import android.view.ViewGroup;
Daisuke Miyakawa8907f662011-09-06 11:36:43 -070047import android.view.accessibility.AccessibilityEvent;
Wenyi Wang4cc72d52016-10-06 12:42:54 -070048import android.view.accessibility.AccessibilityManager;
Wenyi Wangf128ea82016-07-25 11:56:40 -070049import android.widget.Button;
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -070050import android.widget.FrameLayout;
Wenyi Wangf128ea82016-07-25 11:56:40 -070051import android.widget.ImageView;
52import android.widget.LinearLayout.LayoutParams;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070053import android.widget.TextView;
Wenyi Wang587b8f22016-08-17 17:40:45 -070054import android.widget.Toast;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070055
Wenyi Wang587b8f22016-08-17 17:40:45 -070056import com.android.contacts.ContactSaveService;
57import com.android.contacts.ContactsDrawerActivity;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070058import com.android.contacts.R;
Wenyi Wang587b8f22016-08-17 17:40:45 -070059import com.android.contacts.activities.ActionBarAdapter;
yaolu395c3f82016-10-18 11:13:39 -070060import com.android.contacts.activities.PeopleActivity;
Xi Chen947c23d2016-06-30 20:23:52 -070061import com.android.contacts.common.Experiments;
Wenyi Wang4cc72d52016-10-06 12:42:54 -070062import com.android.contacts.common.compat.CompatUtils;
Wenyi Wang587b8f22016-08-17 17:40:45 -070063import com.android.contacts.common.list.ContactEntryListFragment;
Chiao Chenga0233a02012-11-01 16:41:08 -070064import com.android.contacts.common.list.ContactListAdapter;
Wenyi Wang1114fde2016-07-11 21:44:02 -070065import com.android.contacts.common.list.ContactListFilter;
Wenyi Wang587b8f22016-08-17 17:40:45 -070066import com.android.contacts.common.list.ContactListFilterController;
Paul Sliwowskifd4df882013-09-10 22:07:24 -070067import com.android.contacts.common.list.ContactListItemView;
Chiao Chenga0233a02012-11-01 16:41:08 -070068import com.android.contacts.common.list.DefaultContactListAdapter;
Wenyi Wanged9f9742016-09-08 13:50:39 -070069import com.android.contacts.common.list.DirectoryListLoader;
Wenyi Wangaad30f52016-05-12 14:45:22 -070070import com.android.contacts.common.list.FavoritesAndContactsLoader;
Wenyi Wang587b8f22016-08-17 17:40:45 -070071import com.android.contacts.common.logging.ListEvent;
72import com.android.contacts.common.logging.Logger;
73import com.android.contacts.common.logging.ScreenEvent;
Xi Chen947c23d2016-06-30 20:23:52 -070074import com.android.contacts.common.model.AccountTypeManager;
Wenyi Wang1114fde2016-07-11 21:44:02 -070075import com.android.contacts.common.model.account.AccountWithDataSet;
Wenyi Wang587b8f22016-08-17 17:40:45 -070076import com.android.contacts.common.util.AccountFilterUtil;
77import com.android.contacts.common.util.ImplicitIntentsUtil;
78import com.android.contacts.interactions.ContactDeletionInteraction;
79import com.android.contacts.interactions.ContactMultiDeletionInteraction;
80import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener;
81import com.android.contacts.quickcontact.QuickContactActivity;
82import com.android.contacts.util.SharedPreferenceUtil;
Xi Chen4a7a9ea2016-07-20 11:23:30 -070083import com.android.contacts.util.SyncUtil;
Walter Jang6367d2a2016-08-15 14:51:18 -070084import com.android.contactsbind.experiments.Flags;
Tingting Wang8857b942016-08-29 13:53:41 -070085import com.android.contactsbind.FeatureHighlightHelper;
Wenyi Wang587b8f22016-08-17 17:40:45 -070086
Xi Chen947c23d2016-06-30 20:23:52 -070087import java.util.List;
Wenyi Wang587b8f22016-08-17 17:40:45 -070088import java.util.Locale;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070089
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070090/**
91 * Fragment containing a contact list used for browsing (as compared to
92 * picking a contact with one of the PICK intents).
93 */
yaolu491cca52016-09-30 14:04:40 -070094public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
95 implements EnableGlobalSyncDialogFragment.Listener {
Wenyi Wang587b8f22016-08-17 17:40:45 -070096
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
101 private static final int ACTIVITY_REQUEST_CODE_SHARE = 0;
102
Dmitri Plotnikov17abbae2010-09-28 20:36:45 -0700103 private View mSearchHeaderView;
Josh Gargusf4aaa472012-04-26 17:27:22 -0700104 private View mSearchProgress;
Wenyi Wangf128ea82016-07-25 11:56:40 -0700105 private View mEmptyAccountView;
106 private View mEmptyHomeView;
107 private View mAccountFilterContainer;
Josh Gargusf4aaa472012-04-26 17:27:22 -0700108 private TextView mSearchProgressText;
yaolu9d998592016-10-14 23:21:00 -0700109
Xi Chen947c23d2016-06-30 20:23:52 -0700110 private SwipeRefreshLayout mSwipeRefreshLayout;
yaolu9d998592016-10-14 23:21:00 -0700111 private final Handler mHandler = new Handler();
112 private final Runnable mCancelRefresh = new Runnable() {
113 @Override
114 public void run() {
115 if (mSwipeRefreshLayout.isRefreshing()) {
116 mSwipeRefreshLayout.setRefreshing(false);
117 }
118 }
119 };
Tingting Wang1243cb42016-08-05 16:42:21 -0700120
yaolu491cca52016-09-30 14:04:40 -0700121 private View mAlertContainer;
122 private TextView mAlertText;
123 private ImageView mAlertDismissIcon;
124 private int mReasonSyncOff = SyncUtil.SYNC_SETTING_SYNC_ON;
125
Wenyi Wang587b8f22016-08-17 17:40:45 -0700126 private boolean mContactsAvailable;
127 private boolean mEnableDebugMenuOptions;
128 private boolean mIsRecreatedInstance;
129 private boolean mOptionsMenuContactsAvailable;
130
James Laskey1842bb22016-10-20 14:38:04 -0700131 private boolean mCanSetActionBar = false;
132
Wenyi Wang587b8f22016-08-17 17:40:45 -0700133 /**
Wenyi Wanged9f9742016-09-08 13:50:39 -0700134 * If {@link #configureFragment()} is already called. Used to avoid calling it twice
135 * in {@link #onResume()}.
136 * (This initialization only needs to be done once in onResume() when the Activity was just
137 * created from scratch -- i.e. onCreate() was just called)
138 */
139 private boolean mFragmentInitialized;
140
141 private boolean mFromOnNewIntent;
142
143 /**
Wenyi Wang587b8f22016-08-17 17:40:45 -0700144 * This is to tell whether we need to restart ContactMultiDeletionInteraction and set listener.
145 * if screen is rotated while deletion dialog is shown.
146 */
147 private boolean mIsDeletionInProgress;
148
149 /**
150 * This is to disable {@link #onOptionsItemSelected} when we trying to stop the
151 * activity/fragment.
152 */
153 private boolean mDisableOptionItemSelected;
154
155 private ActionBarAdapter mActionBarAdapter;
Wenyi Wang79675452016-08-17 10:43:28 -0700156 private ContactsDrawerActivity mActivity;
Wenyi Wang587b8f22016-08-17 17:40:45 -0700157 private ContactsRequest mContactsRequest;
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700158 private ContactListFilterController mContactListFilterController;
Wenyi Wang587b8f22016-08-17 17:40:45 -0700159
Wenyi Wang587b8f22016-08-17 17:40:45 -0700160 private final ActionBarAdapter.Listener mActionBarListener = new ActionBarAdapter.Listener() {
161 @Override
162 public void onAction(int action) {
163 switch (action) {
164 case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
165 displayCheckBoxes(true);
166 startSearchOrSelectionMode();
167 break;
168 case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
169 if (!mIsRecreatedInstance) {
Wenyi Wang79675452016-08-17 10:43:28 -0700170 Logger.logScreenView(mActivity, ScreenEvent.ScreenType.SEARCH);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700171 }
172 startSearchOrSelectionMode();
173 break;
174 case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
Wenyi Wang79675452016-08-17 10:43:28 -0700175 mActivity.showFabWithAnimation(/* showFab */ true);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700176 break;
177 case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
178 // If queryString is empty, fragment data will not be reloaded,
179 // so hamburger promo should be checked now.
180 // Otherwise, promo should be checked and displayed after reloading, b/30706521.
181 if (TextUtils.isEmpty(getQueryString())) {
182 maybeShowHamburgerFeatureHighlight();
183 }
184 setQueryTextToFragment("");
185 maybeHideCheckBoxes();
Wenyi Wang79675452016-08-17 10:43:28 -0700186 mActivity.invalidateOptionsMenu();
187 mActivity.showFabWithAnimation(/* showFab */ true);
yaolu491cca52016-09-30 14:04:40 -0700188
189 // Alert user if sync is off and not dismissed before
190 setSyncOffAlert();
191
Wenyi Wang587b8f22016-08-17 17:40:45 -0700192 // Determine whether the account has pullToRefresh feature
Walter Jang9adc9ef2016-11-02 18:50:38 -0700193 setSwipeRefreshLayoutEnabledOrNot(getFilter());
Wenyi Wang587b8f22016-08-17 17:40:45 -0700194 break;
195 case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
196 final String queryString = mActionBarAdapter.getQueryString();
197 setQueryTextToFragment(queryString);
198 updateDebugOptionsVisibility(
199 ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
200 break;
201 default:
202 throw new IllegalStateException("Unknown ActionBarAdapter action: " + action);
203 }
204 }
205
206 private void startSearchOrSelectionMode() {
207 configureContactListFragment();
208 maybeHideCheckBoxes();
Wenyi Wang79675452016-08-17 10:43:28 -0700209 mActivity.invalidateOptionsMenu();
210 mActivity.showFabWithAnimation(/* showFab */ false);
211
Wenyi Wang587b8f22016-08-17 17:40:45 -0700212 final Context context = getContext();
213 if (!SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(context)) {
214 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(context);
215 }
216 }
217
218 private void updateDebugOptionsVisibility(boolean visible) {
219 if (mEnableDebugMenuOptions != visible) {
220 mEnableDebugMenuOptions = visible;
Wenyi Wang79675452016-08-17 10:43:28 -0700221 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700222 }
223 }
224
225 private void setQueryTextToFragment(String query) {
226 setQueryString(query, true);
227 setVisibleScrollbarEnabled(!isSearchMode());
228 }
229
230 @Override
231 public void onUpButtonPressed() {
Wenyi Wang79675452016-08-17 10:43:28 -0700232 mActivity.onBackPressed();
233 }
234 };
235
236 private final View.OnClickListener mAddContactListener = new View.OnClickListener() {
237 @Override
238 public void onClick(View v) {
239 AccountFilterUtil.startEditorIntent(getContext(), mActivity.getIntent(), getFilter());
Wenyi Wang587b8f22016-08-17 17:40:45 -0700240 }
241 };
Dmitri Plotnikov4b4681d2010-05-06 21:06:28 -0700242
Dmitri Plotnikov9716d052010-05-10 18:03:54 -0700243 public DefaultContactBrowseListFragment() {
244 setPhotoLoaderEnabled(true);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700245 // Don't use a QuickContactBadge. Just use a regular ImageView. Using a QuickContactBadge
246 // inside the ListView prevents us from using MODE_FULLY_EXPANDED and messes up ripples.
247 setQuickContactEnabled(false);
Dmitri Plotnikov1ce1e7c2010-05-13 16:41:00 -0700248 setSectionHeaderDisplayEnabled(true);
Dmitri Plotnikov5245ea62010-12-10 15:03:13 -0800249 setVisibleScrollbarEnabled(true);
Wenyi Wang9746ad82016-06-28 12:48:28 -0700250 setDisplayDirectoryHeader(false);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700251 setHasOptionsMenu(true);
Tingting Wang1243cb42016-08-05 16:42:21 -0700252 }
253
Dmitri Plotnikov92a6fdf2010-07-09 13:19:33 -0700254 @Override
Jay Shrauner8c42b522013-08-15 09:32:47 -0700255 public CursorLoader createCursorLoader(Context context) {
Wenyi Wangaad30f52016-05-12 14:45:22 -0700256 return new FavoritesAndContactsLoader(context);
Dave Santoro84cac442011-08-24 15:23:10 -0700257 }
258
259 @Override
Wenyi Wang1114fde2016-07-11 21:44:02 -0700260 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Wenyi Wang5db18212016-08-10 17:11:26 -0700261 if (loader.getId() == Directory.DEFAULT) {
Walter Janga773fb92016-09-30 15:23:30 -0700262 bindListHeader(data == null ? 0 : data.getCount());
Wenyi Wang5db18212016-08-10 17:11:26 -0700263 }
Wenyi Wang1114fde2016-07-11 21:44:02 -0700264 super.onLoadFinished(loader, data);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700265 if (!isSearchMode()) {
266 maybeShowHamburgerFeatureHighlight();
267 }
268 }
269
Wenyi Wanged9f9742016-09-08 13:50:39 -0700270 private void maybeShowHamburgerFeatureHighlight() {
Tingting Wang8857b942016-08-29 13:53:41 -0700271 if (mActionBarAdapter!= null && !mActionBarAdapter.isSearchMode()
Wenyi Wang587b8f22016-08-17 17:40:45 -0700272 && !mActionBarAdapter.isSelectionMode()
Wenyi Wang4cc72d52016-10-06 12:42:54 -0700273 && !isTalkbackOnAndOnPreLollipopMr1()
Wenyi Wang587b8f22016-08-17 17:40:45 -0700274 && SharedPreferenceUtil.getShouldShowHamburgerPromo(getContext())) {
Wenyi Wang79675452016-08-17 10:43:28 -0700275 if (FeatureHighlightHelper.showHamburgerFeatureHighlight(mActivity)) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700276 SharedPreferenceUtil.setHamburgerPromoDisplayedBefore(getContext());
277 }
Tingting Wang1243cb42016-08-05 16:42:21 -0700278 }
Wenyi Wang1114fde2016-07-11 21:44:02 -0700279 }
280
Wenyi Wang4cc72d52016-10-06 12:42:54 -0700281 // There's a crash if we show feature highlight when Talkback is on, on API 21 and below.
282 // See b/31180524.
283 private boolean isTalkbackOnAndOnPreLollipopMr1(){
284 return ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE))
285 .isTouchExplorationEnabled()
286 && !CompatUtils.isLollipopMr1Compatible();
287 }
288
Wenyi Wang1114fde2016-07-11 21:44:02 -0700289 private void bindListHeader(int numberOfContacts) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700290 final ContactListFilter filter = getFilter();
Xi Chena455bd52016-07-25 17:00:22 -0700291 // If the phone has at least one Google account whose sync status is unsyncable or pending
292 // or active, we have to make mAccountFilterContainer visible.
293 if (!isSearchMode() && numberOfContacts <= 0 && shouldShowEmptyView(filter)) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700294 if (filter != null && filter.isContactsFilterType()) {
295 makeViewVisible(mEmptyHomeView);
296 } else {
297 makeViewVisible(mEmptyAccountView);
298 }
299 return;
300 }
301 makeViewVisible(mAccountFilterContainer);
Walter Jang92942632016-07-14 19:49:32 +0000302 if (isSearchMode()) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700303 hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
Walter Jang92942632016-07-14 19:49:32 +0000304 } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700305 bindListHeaderCustom(getListView(), mAccountFilterContainer);
Walter Jang92942632016-07-14 19:49:32 +0000306 } else if (filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
Wenyi Wang1114fde2016-07-11 21:44:02 -0700307 final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
308 filter.accountName, filter.accountType, filter.dataSet);
Wenyi Wangf128ea82016-07-25 11:56:40 -0700309 bindListHeader(getContext(), getListView(), mAccountFilterContainer,
Wenyi Wang1114fde2016-07-11 21:44:02 -0700310 accountWithDataSet, numberOfContacts);
311 } else {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700312 hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
Wenyi Wang1114fde2016-07-11 21:44:02 -0700313 }
314 }
315
Xi Chena455bd52016-07-25 17:00:22 -0700316 /**
317 * If at least one Google account is unsyncable or its sync status is pending or active, we
318 * should not show empty view even if the number of contacts is 0. We should show sync status
319 * with empty list instead.
320 */
321 private boolean shouldShowEmptyView(ContactListFilter filter) {
322 if (filter == null) {
323 return true;
324 }
325 // TODO(samchen) : Check ContactListFilter.FILTER_TYPE_CUSTOM
326 if (ContactListFilter.FILTER_TYPE_DEFAULT == filter.filterType
327 || ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS == filter.filterType) {
328 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(getContext())
329 .getAccounts(/* contactsWritableOnly */ true);
330 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
331
332 if (syncableAccounts != null && syncableAccounts.size() > 0) {
333 for (Account account : syncableAccounts) {
334 if (SyncUtil.isSyncStatusPendingOrActive(account)
335 || SyncUtil.isUnsyncableGoogleAccount(account)) {
336 return false;
337 }
338 }
339 }
340 } else if (ContactListFilter.FILTER_TYPE_ACCOUNT == filter.filterType) {
341 final Account account = new Account(filter.accountName, filter.accountType);
342 return !(SyncUtil.isSyncStatusPendingOrActive(account)
343 || SyncUtil.isUnsyncableGoogleAccount(account));
344 }
345 return true;
346 }
347
Wenyi Wangf128ea82016-07-25 11:56:40 -0700348 // Show the view that's specified by id and hide the other two.
349 private void makeViewVisible(View view) {
350 mEmptyAccountView.setVisibility(view == mEmptyAccountView ? View.VISIBLE : View.GONE);
351 mEmptyHomeView.setVisibility(view == mEmptyHomeView ? View.VISIBLE : View.GONE);
352 mAccountFilterContainer.setVisibility(
353 view == mAccountFilterContainer ? View.VISIBLE : View.GONE);
354 }
355
Wenyi Wang32912eb2016-09-29 17:17:07 -0700356 public void scrollToTop() {
357 if (getListView() != null) {
358 getListView().setSelection(0);
359 }
360 }
361
Wenyi Wang1114fde2016-07-11 21:44:02 -0700362 @Override
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700363 protected void onItemClick(int position, long id) {
Brian Attwellb831b142014-10-03 15:45:18 -0700364 final Uri uri = getAdapter().getContactUri(position);
365 if (uri == null) {
366 return;
367 }
Walter Jange9ea4f02016-05-10 09:39:46 -0700368 if (getAdapter().isDisplayingCheckBoxes()) {
369 super.onItemClick(position, id);
370 return;
371 }
Wenyi Wang2b943992016-05-20 17:21:35 -0700372 viewContact(position, uri, getAdapter().isEnterpriseContact(position));
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700373 }
374
375 @Override
376 protected ContactListAdapter createListAdapter() {
Dmitri Plotnikov0b976fc2010-06-28 16:56:17 -0700377 DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext());
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700378 adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
Brian Attwelldff843a2014-07-09 23:10:09 -0700379 adapter.setDisplayPhotos(true);
380 adapter.setPhotoPosition(
381 ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700382 return adapter;
383 }
384
385 @Override
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700386 public ContactListFilter getFilter() {
387 return mContactListFilterController.getFilter();
388 }
389
390 @Override
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700391 protected View inflateView(LayoutInflater inflater, ViewGroup container) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700392 final View view = inflater.inflate(R.layout.contact_list_content, null);
393
394 mAccountFilterContainer = view.findViewById(R.id.account_filter_header_container);
395
396 // Add empty main view and account view to list.
397 final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list);
398 mEmptyAccountView = getEmptyAccountView(inflater);
399 mEmptyHomeView = getEmptyHomeView(inflater);
400 contactListLayout.addView(mEmptyAccountView);
401 contactListLayout.addView(mEmptyHomeView);
402
403 return view;
404 }
405
406 private View getEmptyHomeView(LayoutInflater inflater) {
407 final View emptyHomeView = inflater.inflate(R.layout.empty_home_view, null);
408 // Set image margins.
409 final ImageView image = (ImageView) emptyHomeView.findViewById(R.id.empty_home_image);
410 final LayoutParams params = (LayoutParams) image.getLayoutParams();
411 final int screenHeight = getResources().getDisplayMetrics().heightPixels;
412 final int marginTop = screenHeight / 2 -
413 getResources().getDimensionPixelSize(R.dimen.empty_home_view_image_offset) ;
414 params.setMargins(0, marginTop, 0, 0);
415 params.gravity = Gravity.CENTER_HORIZONTAL;
416 image.setLayoutParams(params);
417
418 // Set up add contact button.
419 final Button addContactButton =
420 (Button) emptyHomeView.findViewById(R.id.add_contact_button);
Wenyi Wang79675452016-08-17 10:43:28 -0700421 addContactButton.setOnClickListener(mAddContactListener);
Wenyi Wangf128ea82016-07-25 11:56:40 -0700422 return emptyHomeView;
423 }
424
425 private View getEmptyAccountView(LayoutInflater inflater) {
426 final View emptyAccountView = inflater.inflate(R.layout.empty_account_view, null);
427 // Set image margins.
428 final ImageView image = (ImageView) emptyAccountView.findViewById(R.id.empty_account_image);
429 final LayoutParams params = (LayoutParams) image.getLayoutParams();
430 final int height = getResources().getDisplayMetrics().heightPixels;
431 final int divisor =
432 getResources().getInteger(R.integer.empty_account_view_image_margin_divisor);
433 final int offset =
434 getResources().getDimensionPixelSize(R.dimen.empty_account_view_image_offset);
435 params.setMargins(0, height / divisor + offset, 0, 0);
436 params.gravity = Gravity.CENTER_HORIZONTAL;
437 image.setLayoutParams(params);
438
439 // Set up add contact button.
440 final Button addContactButton =
441 (Button) emptyAccountView.findViewById(R.id.add_contact_button);
Wenyi Wang79675452016-08-17 10:43:28 -0700442 addContactButton.setOnClickListener(mAddContactListener);
Wenyi Wangf128ea82016-07-25 11:56:40 -0700443 return emptyAccountView;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700444 }
445
446 @Override
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700447 public void onCreate(Bundle savedState) {
448 super.onCreate(savedState);
449 mIsRecreatedInstance = (savedState != null);
450 mContactListFilterController = ContactListFilterController.getInstance(getContext());
451 mContactListFilterController.checkFilterValidity(false);
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700452 // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one.
Wenyi Wanged9f9742016-09-08 13:50:39 -0700453 // This is useful when user upgrades app while an account filter was
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700454 // stored in sharedPreference in a previous version of Contacts app.
455 final ContactListFilter filter = mIsRecreatedInstance
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700456 ? getFilter()
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700457 : AccountFilterUtil.createContactsFilter(getContext());
458 setContactListFilter(filter);
459 }
460
461 @Override
Dmitri Plotnikovbba62ac2010-06-03 10:22:59 -0700462 protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
463 super.onCreateView(inflater, container);
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700464
Walter Jang9adc9ef2016-11-02 18:50:38 -0700465 initSwipeRefreshLayout();
466
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700467 // Putting the header view inside a container will allow us to make
468 // it invisible later. See checkHeaderViewVisibility()
Wenyi Wang587b8f22016-08-17 17:40:45 -0700469 final FrameLayout headerContainer = new FrameLayout(inflater.getContext());
Dmitri Plotnikov17abbae2010-09-28 20:36:45 -0700470 mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false);
471 headerContainer.addView(mSearchHeaderView);
Dmitri Plotnikov8db19132011-01-13 14:24:52 -0800472 getListView().addHeaderView(headerContainer, null, false);
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700473 checkHeaderViewVisibility();
Josh Gargusf4aaa472012-04-26 17:27:22 -0700474
475 mSearchProgress = getView().findViewById(R.id.search_progress);
476 mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText);
yaolu491cca52016-09-30 14:04:40 -0700477
478 mAlertContainer = getView().findViewById(R.id.alert_container);
479 mAlertText = (TextView) mAlertContainer.findViewById(R.id.alert_text);
480 mAlertDismissIcon = (ImageView) mAlertContainer.findViewById(R.id.alert_dismiss_icon);
481 mAlertText.setOnClickListener(new View.OnClickListener() {
482 @Override
483 public void onClick(View v) {
484 turnSyncOn();
485 }
486 });
487 mAlertDismissIcon.setOnClickListener(new View.OnClickListener() {
488 @Override
489 public void onClick(View v) {
490 dismiss();
491 }
492 });
493
494 mAlertContainer.setVisibility(View.GONE);
495 }
496
497 private void turnSyncOn() {
498 final ContactListFilter filter = getFilter();
499 if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
500 && mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
501 ContentResolver.setSyncAutomatically(
502 new Account(filter.accountName, filter.accountType),
503 ContactsContract.AUTHORITY, true);
504 mAlertContainer.setVisibility(View.GONE);
505 } else {
506 final EnableGlobalSyncDialogFragment dialog = new
507 EnableGlobalSyncDialogFragment();
508 dialog.show(this, filter);
509 }
510 }
511
512 @Override
513 public void onEnableAutoSync(ContactListFilter filter) {
514 // Turn on auto-sync
515 ContentResolver.setMasterSyncAutomatically(true);
516 // Also enable Contacts sync
517 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(
518 getContext()).getAccounts(/* contactsWritableOnly */ true);
519 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
520 if (syncableAccounts != null && syncableAccounts.size() > 0) {
521 for (Account account : syncableAccounts) {
522 ContentResolver.setSyncAutomatically(new Account(account.name, account.type),
523 ContactsContract.AUTHORITY, true);
524 }
525 }
526 mAlertContainer.setVisibility(View.GONE);
527 }
528
529 private void dismiss() {
530 if (mReasonSyncOff == SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF) {
531 SharedPreferenceUtil.incNumOfDismissesForAutoSyncOff(getContext());
532 } else if (mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
533 SharedPreferenceUtil.incNumOfDismissesForAccountSyncOff(
534 getContext(), getFilter().accountName);
535 }
536 mAlertContainer.setVisibility(View.GONE);
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700537 }
538
Xi Chen947c23d2016-06-30 20:23:52 -0700539 private void initSwipeRefreshLayout() {
540 mSwipeRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh);
541 if (mSwipeRefreshLayout == null) {
542 return;
543 }
544
545 mSwipeRefreshLayout.setEnabled(true);
546 // Request sync contacts
547 mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
548 @Override
549 public void onRefresh() {
yaolu9d998592016-10-14 23:21:00 -0700550 mHandler.removeCallbacks(mCancelRefresh);
yaolu395c3f82016-10-18 11:13:39 -0700551
552 final boolean isNetworkConnected = SyncUtil.isNetworkConnected(getContext());
553 if (!isNetworkConnected) {
554 mSwipeRefreshLayout.setRefreshing(false);
555 ((PeopleActivity)getActivity()).showConnectionErrorMsg();
556 return;
557 }
558
yaolu9d998592016-10-14 23:21:00 -0700559 syncContacts(getFilter());
Walter Jangdf86ede2016-10-19 09:48:29 -0700560 mHandler.postDelayed(mCancelRefresh, Flags.getInstance()
yaolu9d998592016-10-14 23:21:00 -0700561 .getInteger(Experiments.PULL_TO_REFRESH_CANCEL_REFRESH_MILLIS));
Xi Chen947c23d2016-06-30 20:23:52 -0700562 }
563 });
564 mSwipeRefreshLayout.setColorSchemeResources(
565 R.color.swipe_refresh_color1,
566 R.color.swipe_refresh_color2,
567 R.color.swipe_refresh_color3,
568 R.color.swipe_refresh_color4);
569 mSwipeRefreshLayout.setDistanceToTriggerSync(
570 (int) getResources().getDimension(R.dimen.pull_to_refresh_distance));
571 }
572
Xi Chena455bd52016-07-25 17:00:22 -0700573 /**
574 * Request sync for the Google accounts (not include Google+ accounts) specified by the given
575 * filter.
576 */
Xi Chen947c23d2016-06-30 20:23:52 -0700577 private void syncContacts(ContactListFilter filter) {
578 if (filter == null) {
579 return;
580 }
yaolu395c3f82016-10-18 11:13:39 -0700581
Xi Chen947c23d2016-06-30 20:23:52 -0700582 final Bundle bundle = new Bundle();
583 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
584 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
585
Xi Chen4a7a9ea2016-07-20 11:23:30 -0700586 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(
Wenyi Wang79675452016-08-17 10:43:28 -0700587 getContext()).getAccounts(/* contactsWritableOnly */ true);
Xi Chen4a7a9ea2016-07-20 11:23:30 -0700588 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
589 if (syncableAccounts != null && syncableAccounts.size() > 0) {
590 for (Account account : syncableAccounts) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700591 // We can prioritize Contacts sync if sync is not initialized yet.
Xi Chena455bd52016-07-25 17:00:22 -0700592 if (!SyncUtil.isSyncStatusPendingOrActive(account)
593 || SyncUtil.isUnsyncableGoogleAccount(account)) {
Xi Chen4a7a9ea2016-07-20 11:23:30 -0700594 ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle);
Xi Chen947c23d2016-06-30 20:23:52 -0700595 }
596 }
597 }
598 }
599
yaolu491cca52016-09-30 14:04:40 -0700600 private void setSyncOffAlert() {
601 final ContactListFilter filter = getFilter();
602 final Account account = filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
603 && filter.isGoogleAccountType()
604 ? new Account(filter.accountName, filter.accountType) : null;
605
606 if (account == null && !filter.isContactsFilterType()) {
607 mAlertContainer.setVisibility(View.GONE);
608 } else {
609 mReasonSyncOff = SyncUtil.calculateReasonSyncOff(getContext(), account);
610 final boolean isAlertVisible =
611 SyncUtil.isAlertVisible(getContext(), account, mReasonSyncOff);
612 setSyncOffMsg(mReasonSyncOff);
613 mAlertContainer.setVisibility(isAlertVisible ? View.VISIBLE : View.GONE);
614 }
615 }
616
617 private void setSyncOffMsg(int reason) {
618 final Resources resources = getResources();
619 switch (reason) {
620 case SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF:
621 mAlertText.setText(resources.getString(R.string.auto_sync_off));
622 break;
623 case SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF:
624 mAlertText.setText(resources.getString(R.string.account_sync_off));
625 break;
626 default:
627 }
628 }
629
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700630 @Override
Wenyi Wang587b8f22016-08-17 17:40:45 -0700631 public void onActivityCreated(Bundle savedInstanceState) {
632 super.onActivityCreated(savedInstanceState);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700633
Wenyi Wang79675452016-08-17 10:43:28 -0700634 mActivity = (ContactsDrawerActivity) getActivity();
635 mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener,
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700636 mActivity.getSupportActionBar(), mActivity.getToolbar(),
637 R.string.enter_contact_name);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700638 mActionBarAdapter.setShowHomeIcon(true);
639 initializeActionBarAdapter(savedInstanceState);
640 if (isSearchMode()) {
641 mActionBarAdapter.setFocusOnSearchView();
642 }
643
644 setCheckBoxListListener(new CheckBoxListListener());
645 setOnContactListActionListener(new ContactBrowserActionListener());
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700646 if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700647 deleteSelectedContacts();
648 }
Wenyi Wang72b0f452016-09-30 15:29:04 -0700649
650 setDirectorySearchMode();
James Laskey1842bb22016-10-20 14:38:04 -0700651 mCanSetActionBar = true;
Wenyi Wang587b8f22016-08-17 17:40:45 -0700652 }
653
654 public void initializeActionBarAdapter(Bundle savedInstanceState) {
655 if (mActionBarAdapter != null) {
656 mActionBarAdapter.initialize(savedInstanceState, mContactsRequest);
657 }
658 }
659
Wenyi Wanged9f9742016-09-08 13:50:39 -0700660 private void configureFragment() {
661 if (mFragmentInitialized && !mFromOnNewIntent) {
662 return;
663 }
664
665 mFragmentInitialized = true;
666
667 if (mFromOnNewIntent || !mIsRecreatedInstance) {
Wenyi Wanga730d9d2016-09-16 18:25:51 -0700668 mFromOnNewIntent = false;
Wenyi Wanged9f9742016-09-08 13:50:39 -0700669 configureFragmentForRequest();
670 }
671
672 configureContactListFragment();
673 }
674
675 private void configureFragmentForRequest() {
676 ContactListFilter filter = null;
677 final int actionCode = mContactsRequest.getActionCode();
678 boolean searchMode = mContactsRequest.isSearchMode();
679 switch (actionCode) {
680 case ContactsRequest.ACTION_ALL_CONTACTS:
681 filter = AccountFilterUtil.createContactsFilter(getContext());
682 break;
683 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
684 filter = ContactListFilter.createFilterWithType(
685 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
686 break;
687
688 case ContactsRequest.ACTION_FREQUENT:
689 case ContactsRequest.ACTION_STREQUENT:
690 case ContactsRequest.ACTION_STARRED:
691 case ContactsRequest.ACTION_VIEW_CONTACT:
692 default:
693 break;
694 }
695
696 if (filter != null) {
697 setContactListFilter(filter);
698 searchMode = false;
699 }
700
701 if (mContactsRequest.getContactUri() != null) {
702 searchMode = false;
703 }
704
705 mActionBarAdapter.setSearchMode(searchMode);
706 configureContactListFragmentForRequest();
707 }
708
709 private void configureContactListFragmentForRequest() {
710 final Uri contactUri = mContactsRequest.getContactUri();
711 if (contactUri != null) {
712 setSelectedContactUri(contactUri);
713 }
714
715 setQueryString(mActionBarAdapter.getQueryString(), true);
716 setVisibleScrollbarEnabled(!isSearchMode());
Wenyi Wang72b0f452016-09-30 15:29:04 -0700717 }
Wenyi Wanged9f9742016-09-08 13:50:39 -0700718
Wenyi Wang72b0f452016-09-30 15:29:04 -0700719 private void setDirectorySearchMode() {
Wenyi Wangc3857fa2016-11-07 17:10:07 -0800720 if (mContactsRequest != null && mContactsRequest.isDirectorySearchEnabled()) {
Wenyi Wanged9f9742016-09-08 13:50:39 -0700721 setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
722 } else {
723 setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
724 }
725 }
726
Wenyi Wang587b8f22016-08-17 17:40:45 -0700727 @Override
728 public void onResume() {
729 super.onResume();
Wenyi Wanged9f9742016-09-08 13:50:39 -0700730 configureFragment();
731 maybeShowHamburgerFeatureHighlight();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700732 // Re-register the listener, which may have been cleared when onSaveInstanceState was
733 // called. See also: onSaveInstanceState
734 mActionBarAdapter.setListener(mActionBarListener);
735 mDisableOptionItemSelected = false;
736 maybeHideCheckBoxes();
737 }
738
739 private void maybeHideCheckBoxes() {
740 if (!mActionBarAdapter.isSelectionMode()) {
741 displayCheckBoxes(false);
742 }
743 }
744
745 public ActionBarAdapter getActionBarAdapter(){
746 return mActionBarAdapter;
747 }
748
749 @Override
Makoto Onuki1db00f62011-10-19 12:40:12 -0700750 protected void setSearchMode(boolean flag) {
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700751 super.setSearchMode(flag);
752 checkHeaderViewVisibility();
Josh Gargusf4aaa472012-04-26 17:27:22 -0700753 if (!flag) showSearchProgress(false);
754 }
755
756 /** Show or hide the directory-search progress spinner. */
757 private void showSearchProgress(boolean show) {
Andrew Leee599bf02014-05-15 14:13:17 -0700758 if (mSearchProgress != null) {
759 mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE);
760 }
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700761 }
762
763 private void checkHeaderViewVisibility() {
Andrew Leee15828e2014-05-01 14:00:25 -0700764 // Hide the search header by default.
Dmitri Plotnikov17abbae2010-09-28 20:36:45 -0700765 if (mSearchHeaderView != null) {
766 mSearchHeaderView.setVisibility(View.GONE);
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700767 }
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700768 }
769
770 @Override
Wenyi Wangaad30f52016-05-12 14:45:22 -0700771 protected void setListHeader() {
772 if (!isSearchMode()) {
773 return;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700774 }
Wenyi Wangaad30f52016-05-12 14:45:22 -0700775 ContactListAdapter adapter = getAdapter();
776 if (adapter == null) {
777 return;
778 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700779
Wenyi Wangaad30f52016-05-12 14:45:22 -0700780 // In search mode we only display the header if there is nothing found
781 if (TextUtils.isEmpty(getQueryString()) || !adapter.areAllPartitionsEmpty()) {
782 mSearchHeaderView.setVisibility(View.GONE);
783 showSearchProgress(false);
784 } else {
785 mSearchHeaderView.setVisibility(View.VISIBLE);
786 if (adapter.isLoading()) {
787 mSearchProgressText.setText(R.string.search_results_searching);
788 showSearchProgress(true);
789 } else {
790 mSearchProgressText.setText(R.string.listFoundAllContactsZero);
791 mSearchProgressText.sendAccessibilityEvent(
792 AccessibilityEvent.TYPE_VIEW_SELECTED);
793 showSearchProgress(false);
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700794 }
Wenyi Wangaad30f52016-05-12 14:45:22 -0700795 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700796 }
Xi Chen947c23d2016-06-30 20:23:52 -0700797
798 public SwipeRefreshLayout getSwipeRefreshLayout() {
799 return mSwipeRefreshLayout;
800 }
Wenyi Wang587b8f22016-08-17 17:40:45 -0700801
802 private final class CheckBoxListListener implements OnCheckBoxListActionListener {
803 @Override
804 public void onStartDisplayingCheckBoxes() {
805 mActionBarAdapter.setSelectionMode(true);
Wenyi Wang79675452016-08-17 10:43:28 -0700806 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700807 }
808
809 @Override
810 public void onSelectedContactIdsChanged() {
811 mActionBarAdapter.setSelectionCount(getSelectedContactIds().size());
Wenyi Wang79675452016-08-17 10:43:28 -0700812 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700813 }
814
815 @Override
816 public void onStopDisplayingCheckBoxes() {
817 mActionBarAdapter.setSelectionMode(false);
818 }
819 }
820
Wenyi Wang32912eb2016-09-29 17:17:07 -0700821 public void setFilterAndUpdateTitle(ContactListFilter filter) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700822 setFilterAndUpdateTitle(filter, true);
823 }
824
825 private void setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri) {
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700826 setContactListFilter(filter);
827 updateListFilter(filter, restoreSelectedUri);
Wenyi Wangcfe94652016-10-03 21:05:04 -0700828 mActivity.setTitle(AccountFilterUtil.getActionBarTitleForFilter(mActivity, filter));
Wenyi Wang587b8f22016-08-17 17:40:45 -0700829
yaolu491cca52016-09-30 14:04:40 -0700830 // Alert user if sync is off and not dismissed before
831 setSyncOffAlert();
832
Wenyi Wang587b8f22016-08-17 17:40:45 -0700833 // Determine whether the account has pullToRefresh feature
Walter Jang9adc9ef2016-11-02 18:50:38 -0700834 setSwipeRefreshLayoutEnabledOrNot(filter);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700835 }
836
Wenyi Wanged9f9742016-09-08 13:50:39 -0700837 private void setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700838 final SwipeRefreshLayout swipeRefreshLayout = getSwipeRefreshLayout();
839 if (swipeRefreshLayout == null) {
840 if (Log.isLoggable(TAG, Log.DEBUG)) {
841 Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
842 }
843 return;
844 }
845
846 swipeRefreshLayout.setRefreshing(false);
847 swipeRefreshLayout.setEnabled(false);
848
849 if (filter != null && !mActionBarAdapter.isSearchMode()
850 && !mActionBarAdapter.isSelectionMode()) {
851 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(getContext())
852 .getAccounts(/* contactsWritableOnly */ true);
853 if (filter.isSyncable(accounts)) {
854 swipeRefreshLayout.setEnabled(true);
855 }
856 }
857 }
858
Wenyi Wanged9f9742016-09-08 13:50:39 -0700859 private void configureContactListFragment() {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700860 // Filter may be changed when activity is in background.
861 setFilterAndUpdateTitle(getFilter());
862 setVerticalScrollbarPosition(getScrollBarPosition());
863 setSelectionVisible(false);
Wenyi Wang79675452016-08-17 10:43:28 -0700864 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700865 }
866
867 private int getScrollBarPosition() {
868 final Locale locale = Locale.getDefault();
869 final boolean isRTL =
870 TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
871 return isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
872 }
873
874 private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
875 ContactBrowserActionListener() {}
876
877 @Override
878 public void onSelectionChange() {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700879 }
880
881 @Override
882 public void onViewContactAction(int position, Uri contactLookupUri,
883 boolean isEnterpriseContact) {
884 if (isEnterpriseContact) {
885 // No implicit intent as user may have a different contacts app in work profile.
886 ContactsContract.QuickContact.showQuickContact(getContext(), new Rect(),
887 contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
888 } else {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700889 final int previousScreen;
890 if (isSearchMode()) {
891 previousScreen = ScreenEvent.ScreenType.SEARCH;
892 } else {
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700893 if (isAllContactsFilter(getFilter())) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700894 if (position < getAdapter().getNumberOfFavorites()) {
895 previousScreen = ScreenEvent.ScreenType.FAVORITES;
896 } else {
897 previousScreen = ScreenEvent.ScreenType.ALL_CONTACTS;
898 }
899 } else {
900 previousScreen = ScreenEvent.ScreenType.LIST_ACCOUNT;
901 }
902 }
Walter Jang0396cf72016-09-21 13:23:29 -0700903
Wenyi Wang587b8f22016-08-17 17:40:45 -0700904 Logger.logListEvent(ListEvent.ActionType.CLICK,
905 /* listType */ getListTypeIncludingSearch(),
906 /* count */ getAdapter().getCount(),
907 /* clickedIndex */ position, /* numSelected */ 0);
Walter Jang0396cf72016-09-21 13:23:29 -0700908
909 ImplicitIntentsUtil.startQuickContact(
910 getActivity(), contactLookupUri, previousScreen);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700911 }
912 }
913
914 @Override
915 public void onDeleteContactAction(Uri contactUri) {
Wenyi Wang79675452016-08-17 10:43:28 -0700916 ContactDeletionInteraction.start(mActivity, contactUri, false);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700917 }
918
919 @Override
920 public void onFinishAction() {
Wenyi Wang79675452016-08-17 10:43:28 -0700921 mActivity.onBackPressed();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700922 }
923
924 @Override
925 public void onInvalidSelection() {
926 ContactListFilter filter;
927 ContactListFilter currentFilter = getFilter();
928 if (currentFilter != null
929 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
930 filter = AccountFilterUtil.createContactsFilter(getContext());
931 setFilterAndUpdateTitle(filter);
932 } else {
933 filter = ContactListFilter.createFilterWithType(
934 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
935 setFilterAndUpdateTitle(filter, /* restoreSelectedUri */ false);
936 }
937 setContactListFilter(filter);
938 }
939 }
940
941 private boolean isAllContactsFilter(ContactListFilter filter) {
942 return filter != null && filter.isContactsFilterType();
943 }
944
945 public void setContactsAvailable(boolean contactsAvailable) {
946 mContactsAvailable = contactsAvailable;
947 }
948
949 /**
950 * Set filter via ContactListFilterController
951 */
Wenyi Wanged9f9742016-09-08 13:50:39 -0700952 private void setContactListFilter(ContactListFilter filter) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700953 mContactListFilterController.setContactListFilter(filter,
954 /* persistent */ isAllContactsFilter(filter));
955 }
956
957 @Override
958 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
Wenyi Wang79675452016-08-17 10:43:28 -0700959 if (!mContactsAvailable || mActivity.isInSecondLevel()) {
960 // If contacts aren't available or this fragment is not visible, hide all menu items.
Wenyi Wang587b8f22016-08-17 17:40:45 -0700961 return;
962 }
963 super.onCreateOptionsMenu(menu, inflater);
964 inflater.inflate(R.menu.people_options, menu);
965 }
966
967 @Override
968 public void onPrepareOptionsMenu(Menu menu) {
969 mOptionsMenuContactsAvailable = mContactsAvailable;
970 if (!mOptionsMenuContactsAvailable) {
971 return;
972 }
973
974 final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode()
975 || mActionBarAdapter.isSelectionMode();
976 makeMenuItemVisible(menu, R.id.menu_search, !isSearchOrSelectionMode);
977
978 final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode()
979 && getSelectedContactIds().size() != 0;
980 makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions);
981 makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions);
982 final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode()
983 && getSelectedContactIds().size() > 1;
984 makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions);
985
986 // Debug options need to be visible even in search mode.
987 makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions &&
988 hasExportIntentHandler());
989 }
990
991 private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
992 final MenuItem item = menu.findItem(itemId);
993 if (item != null) {
994 item.setVisible(visible);
995 }
996 }
997
998 private boolean hasExportIntentHandler() {
999 final Intent intent = new Intent();
1000 intent.setAction("com.android.providers.contacts.DUMP_DATABASE");
1001 final List<ResolveInfo> receivers =
1002 getContext().getPackageManager().queryIntentActivities(intent,
1003 PackageManager.MATCH_DEFAULT_ONLY);
1004 return receivers != null && receivers.size() > 0;
1005 }
1006
1007 @Override
1008 public boolean onOptionsItemSelected(MenuItem item) {
1009 if (mDisableOptionItemSelected) {
1010 return false;
1011 }
1012
1013 switch (item.getItemId()) {
1014 case android.R.id.home: {
1015 // The home icon on the action bar is pressed
1016 if (mActionBarAdapter.isUpShowing()) {
1017 // "UP" icon press -- should be treated as "back".
Wenyi Wang79675452016-08-17 10:43:28 -07001018 mActivity.onBackPressed();
Wenyi Wang587b8f22016-08-17 17:40:45 -07001019 }
1020 return true;
1021 }
1022 case R.id.menu_search: {
1023 if (!mActionBarAdapter.isSelectionMode()) {
1024 mActionBarAdapter.setSearchMode(true);
1025 }
1026 return true;
1027 }
1028 case R.id.menu_share: {
1029 shareSelectedContacts();
1030 return true;
1031 }
1032 case R.id.menu_join: {
1033 Logger.logListEvent(ListEvent.ActionType.LINK,
1034 /* listType */ getListTypeIncludingSearch(),
1035 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
1036 /* numSelected */ getAdapter().getSelectedContactIds().size());
1037 joinSelectedContacts();
1038 return true;
1039 }
1040 case R.id.menu_delete: {
1041 deleteSelectedContacts();
1042 return true;
1043 }
1044 case R.id.export_database: {
1045 final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
1046 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1047 ImplicitIntentsUtil.startActivityOutsideApp(getContext(), intent);
1048 return true;
1049 }
1050 }
1051 return super.onOptionsItemSelected(item);
1052 }
1053
1054 /**
Wenyi Wanged9f9742016-09-08 13:50:39 -07001055 * Share all contacts that are currently selected. This method is pretty inefficient for
1056 * handling large numbers of contacts. I don't expect this to be a problem.
Wenyi Wang587b8f22016-08-17 17:40:45 -07001057 */
1058 private void shareSelectedContacts() {
1059 final StringBuilder uriListBuilder = new StringBuilder();
1060 for (Long contactId : getSelectedContactIds()) {
1061 final Uri contactUri = ContentUris.withAppendedId(
1062 ContactsContract.Contacts.CONTENT_URI, contactId);
1063 final Uri lookupUri = ContactsContract.Contacts.getLookupUri(
1064 getContext().getContentResolver(), contactUri);
1065 if (lookupUri == null) {
1066 continue;
1067 }
1068 final List<String> pathSegments = lookupUri.getPathSegments();
1069 if (pathSegments.size() < 2) {
1070 continue;
1071 }
1072 final String lookupKey = pathSegments.get(pathSegments.size() - 2);
1073 if (uriListBuilder.length() > 0) {
1074 uriListBuilder.append(':');
1075 }
1076 uriListBuilder.append(Uri.encode(lookupKey));
1077 }
1078 if (uriListBuilder.length() == 0) {
1079 return;
1080 }
1081 final Uri uri = Uri.withAppendedPath(
1082 ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI,
1083 Uri.encode(uriListBuilder.toString()));
1084 final Intent intent = new Intent(Intent.ACTION_SEND);
1085 intent.setType(ContactsContract.Contacts.CONTENT_VCARD_TYPE);
1086 intent.putExtra(Intent.EXTRA_STREAM, uri);
1087 try {
1088 startActivityForResult(Intent.createChooser(intent, getResources().getQuantityString(
1089 R.plurals.title_share_via,/* quantity */ getSelectedContactIds().size()))
1090 , ACTIVITY_REQUEST_CODE_SHARE);
1091 } catch (final ActivityNotFoundException ex) {
1092 Toast.makeText(getContext(), R.string.share_error, Toast.LENGTH_SHORT).show();
1093 }
1094 }
1095
1096 private void joinSelectedContacts() {
1097 final Context context = getContext();
1098 final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(
1099 context, getSelectedContactIdsArray());
1100 context.startService(intent);
1101
1102 mActionBarAdapter.setSelectionMode(false);
1103 }
1104
1105 private void deleteSelectedContacts() {
Wenyi Wangcc8f2722016-09-07 14:52:32 -07001106 final ContactMultiDeletionInteraction multiDeletionInteraction =
Wenyi Wang587b8f22016-08-17 17:40:45 -07001107 ContactMultiDeletionInteraction.start(this, getSelectedContactIds());
Wenyi Wangcc8f2722016-09-07 14:52:32 -07001108 multiDeletionInteraction.setListener(new MultiDeleteListener());
Wenyi Wang587b8f22016-08-17 17:40:45 -07001109 mIsDeletionInProgress = true;
1110 }
1111
1112 private final class MultiDeleteListener implements MultiContactDeleteListener {
1113 @Override
1114 public void onDeletionFinished() {
1115 // The parameters count and numSelected are both the number of contacts before deletion.
1116 Logger.logListEvent(ListEvent.ActionType.DELETE,
1117 /* listType */ getListTypeIncludingSearch(),
1118 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
1119 /* numSelected */ getSelectedContactIds().size());
1120 mActionBarAdapter.setSelectionMode(false);
1121 mIsDeletionInProgress = false;
1122 }
1123 }
1124
1125 private int getListTypeIncludingSearch() {
1126 return isSearchMode() ? ListEvent.ListType.SEARCH_RESULT : getListType();
1127 }
1128
Wenyi Wanged9f9742016-09-08 13:50:39 -07001129 public void setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent) {
Wenyi Wang587b8f22016-08-17 17:40:45 -07001130 mContactsRequest = contactsRequest;
Wenyi Wanged9f9742016-09-08 13:50:39 -07001131 mFromOnNewIntent = fromOnNewIntent;
Wenyi Wang587b8f22016-08-17 17:40:45 -07001132 }
1133
1134 @Override
1135 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1136 switch (requestCode) {
1137 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1138 // anymore
1139 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1140 if (resultCode == Activity.RESULT_OK) {
1141 onPickerResult(data);
1142 }
1143 case ACTIVITY_REQUEST_CODE_SHARE:
1144 Logger.logListEvent(ListEvent.ActionType.SHARE,
1145 /* listType */ getListTypeIncludingSearch(),
1146 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
1147 /* numSelected */ getAdapter().getSelectedContactIds().size());
1148
1149// TODO fix or remove multipicker code: ag/54762
1150// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1151// // Finish the activity if the sub activity was canceled as back key is used
1152// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1153// finish();
1154// }
1155// break;
1156 }
1157 }
1158
1159 public boolean getOptionsMenuContactsAvailable() {
1160 return mOptionsMenuContactsAvailable;
1161 }
1162
1163 @Override
1164 public void onSaveInstanceState(Bundle outState) {
1165 super.onSaveInstanceState(outState);
1166 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1167 // in order to avoid doing fragment transactions after it.
1168 // TODO Figure out a better way to deal with the issue (ag/120686).
1169 if (mActionBarAdapter != null) {
1170 mActionBarAdapter.setListener(null);
1171 mActionBarAdapter.onSaveInstanceState(outState);
1172 }
1173 mDisableOptionItemSelected = true;
1174 outState.putBoolean(KEY_DELETION_IN_PROGRESS, mIsDeletionInProgress);
1175 }
1176
1177 @Override
1178 public void onPause() {
1179 mOptionsMenuContactsAvailable = false;
1180 super.onPause();
1181 }
1182
1183 @Override
1184 public void onDestroy() {
1185 if (mActionBarAdapter != null) {
1186 mActionBarAdapter.setListener(null);
1187 }
Wenyi Wang587b8f22016-08-17 17:40:45 -07001188 super.onDestroy();
1189 }
Wenyi Wanged9f9742016-09-08 13:50:39 -07001190
1191 public boolean onKeyDown(int unicodeChar) {
1192 if (mActionBarAdapter.isSelectionMode()) {
1193 // Ignore keyboard input when in selection mode.
1194 return true;
1195 }
1196
1197 if (!mActionBarAdapter.isSearchMode()) {
1198 final String query = new String(new int[]{unicodeChar}, 0, 1);
1199 mActionBarAdapter.setSearchMode(true);
1200 mActionBarAdapter.setQueryString(query);
1201 return true;
1202 }
1203
1204 return false;
1205 }
James Laskey1842bb22016-10-20 14:38:04 -07001206
1207 public boolean canSetActionBar() {
1208 return mCanSetActionBar;
1209 }
Tingting Wang5d612da2016-08-08 10:00:45 -07001210}