blob: 079c5a5ebadd3a7563d065613b301e5f7777a879 [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;
John Shaobd9ef3c2016-12-15 12:42:03 -080031import android.graphics.PorterDuff;
Wenyi Wang587b8f22016-08-17 17:40:45 -070032import android.graphics.Rect;
John Shaobd9ef3c2016-12-15 12:42:03 -080033import android.graphics.drawable.Drawable;
Brian Attwellb831b142014-10-03 15:45:18 -070034import android.net.Uri;
Xi Chen947c23d2016-06-30 20:23:52 -070035import android.os.Bundle;
yaolu9d998592016-10-14 23:21:00 -070036import android.os.Handler;
Xi Chen947c23d2016-06-30 20:23:52 -070037import android.provider.ContactsContract;
Wenyi Wang5db18212016-08-10 17:11:26 -070038import android.provider.ContactsContract.Directory;
John Shaobd9ef3c2016-12-15 12:42:03 -080039import android.support.v4.content.ContextCompat;
Walter Jang6367d2a2016-08-15 14:51:18 -070040import android.support.v4.widget.SwipeRefreshLayout;
Dmitri Plotnikov1a57fb22011-01-14 14:53:37 -080041import android.text.TextUtils;
Wenyi Wang587b8f22016-08-17 17:40:45 -070042import android.util.Log;
Wenyi Wangf128ea82016-07-25 11:56:40 -070043import android.view.Gravity;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070044import android.view.LayoutInflater;
Wenyi Wang587b8f22016-08-17 17:40:45 -070045import android.view.Menu;
46import android.view.MenuInflater;
47import android.view.MenuItem;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070048import android.view.View;
49import android.view.ViewGroup;
Daisuke Miyakawa8907f662011-09-06 11:36:43 -070050import android.view.accessibility.AccessibilityEvent;
Wenyi Wang4cc72d52016-10-06 12:42:54 -070051import android.view.accessibility.AccessibilityManager;
Wenyi Wangf128ea82016-07-25 11:56:40 -070052import android.widget.Button;
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -070053import android.widget.FrameLayout;
Wenyi Wangf128ea82016-07-25 11:56:40 -070054import android.widget.ImageView;
55import android.widget.LinearLayout.LayoutParams;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070056import android.widget.TextView;
Wenyi Wang587b8f22016-08-17 17:40:45 -070057import android.widget.Toast;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070058
Wenyi Wang587b8f22016-08-17 17:40:45 -070059import com.android.contacts.ContactSaveService;
60import com.android.contacts.ContactsDrawerActivity;
Gary Mai0a49afa2016-12-05 15:53:58 -080061import com.android.contacts.Experiments;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070062import com.android.contacts.R;
Wenyi Wang587b8f22016-08-17 17:40:45 -070063import com.android.contacts.activities.ActionBarAdapter;
yaolu395c3f82016-10-18 11:13:39 -070064import com.android.contacts.activities.PeopleActivity;
Gary Mai69c182a2016-12-05 13:07:03 -080065import com.android.contacts.compat.CompatUtils;
Gary Mai0a49afa2016-12-05 15:53:58 -080066import com.android.contacts.interactions.ContactDeletionInteraction;
67import com.android.contacts.interactions.ContactMultiDeletionInteraction;
68import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener;
Gary Mai69c182a2016-12-05 13:07:03 -080069import com.android.contacts.logging.ListEvent;
70import com.android.contacts.logging.Logger;
71import com.android.contacts.logging.ScreenEvent;
72import com.android.contacts.model.AccountTypeManager;
73import com.android.contacts.model.account.AccountWithDataSet;
Gary Mai0a49afa2016-12-05 15:53:58 -080074import com.android.contacts.quickcontact.QuickContactActivity;
Gary Mai69c182a2016-12-05 13:07:03 -080075import com.android.contacts.util.AccountFilterUtil;
76import com.android.contacts.util.ImplicitIntentsUtil;
Wenyi Wang587b8f22016-08-17 17:40:45 -070077import com.android.contacts.util.SharedPreferenceUtil;
Xi Chen4a7a9ea2016-07-20 11:23:30 -070078import com.android.contacts.util.SyncUtil;
Tingting Wang8857b942016-08-29 13:53:41 -070079import com.android.contactsbind.FeatureHighlightHelper;
Gary Mai0a49afa2016-12-05 15:53:58 -080080import com.android.contactsbind.experiments.Flags;
Wenyi Wang587b8f22016-08-17 17:40:45 -070081
Xi Chen947c23d2016-06-30 20:23:52 -070082import java.util.List;
Wenyi Wang587b8f22016-08-17 17:40:45 -070083import java.util.Locale;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070084
Dmitri Plotnikov238d9842010-05-04 14:14:57 -070085/**
86 * Fragment containing a contact list used for browsing (as compared to
87 * picking a contact with one of the PICK intents).
88 */
yaolu491cca52016-09-30 14:04:40 -070089public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
90 implements EnableGlobalSyncDialogFragment.Listener {
Wenyi Wang587b8f22016-08-17 17:40:45 -070091
92 private static final String TAG = "DefaultListFragment";
93 private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
94 private static final String KEY_DELETION_IN_PROGRESS = "deletionInProgress";
95
96 private static final int ACTIVITY_REQUEST_CODE_SHARE = 0;
97
Dmitri Plotnikov17abbae2010-09-28 20:36:45 -070098 private View mSearchHeaderView;
Josh Gargusf4aaa472012-04-26 17:27:22 -070099 private View mSearchProgress;
Wenyi Wangf128ea82016-07-25 11:56:40 -0700100 private View mEmptyAccountView;
101 private View mEmptyHomeView;
102 private View mAccountFilterContainer;
Josh Gargusf4aaa472012-04-26 17:27:22 -0700103 private TextView mSearchProgressText;
yaolu9d998592016-10-14 23:21:00 -0700104
Xi Chen947c23d2016-06-30 20:23:52 -0700105 private SwipeRefreshLayout mSwipeRefreshLayout;
yaolu9d998592016-10-14 23:21:00 -0700106 private final Handler mHandler = new Handler();
107 private final Runnable mCancelRefresh = new Runnable() {
108 @Override
109 public void run() {
110 if (mSwipeRefreshLayout.isRefreshing()) {
111 mSwipeRefreshLayout.setRefreshing(false);
112 }
113 }
114 };
Tingting Wang1243cb42016-08-05 16:42:21 -0700115
yaolu491cca52016-09-30 14:04:40 -0700116 private View mAlertContainer;
117 private TextView mAlertText;
118 private ImageView mAlertDismissIcon;
119 private int mReasonSyncOff = SyncUtil.SYNC_SETTING_SYNC_ON;
120
Wenyi Wang587b8f22016-08-17 17:40:45 -0700121 private boolean mContactsAvailable;
122 private boolean mEnableDebugMenuOptions;
123 private boolean mIsRecreatedInstance;
124 private boolean mOptionsMenuContactsAvailable;
125
James Laskey1842bb22016-10-20 14:38:04 -0700126 private boolean mCanSetActionBar = false;
127
Wenyi Wang587b8f22016-08-17 17:40:45 -0700128 /**
Wenyi Wanged9f9742016-09-08 13:50:39 -0700129 * If {@link #configureFragment()} is already called. Used to avoid calling it twice
130 * in {@link #onResume()}.
131 * (This initialization only needs to be done once in onResume() when the Activity was just
132 * created from scratch -- i.e. onCreate() was just called)
133 */
134 private boolean mFragmentInitialized;
135
136 private boolean mFromOnNewIntent;
137
138 /**
Wenyi Wang587b8f22016-08-17 17:40:45 -0700139 * This is to tell whether we need to restart ContactMultiDeletionInteraction and set listener.
140 * if screen is rotated while deletion dialog is shown.
141 */
142 private boolean mIsDeletionInProgress;
143
144 /**
145 * This is to disable {@link #onOptionsItemSelected} when we trying to stop the
146 * activity/fragment.
147 */
148 private boolean mDisableOptionItemSelected;
149
150 private ActionBarAdapter mActionBarAdapter;
Wenyi Wang79675452016-08-17 10:43:28 -0700151 private ContactsDrawerActivity mActivity;
Wenyi Wang587b8f22016-08-17 17:40:45 -0700152 private ContactsRequest mContactsRequest;
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700153 private ContactListFilterController mContactListFilterController;
Wenyi Wang587b8f22016-08-17 17:40:45 -0700154
Wenyi Wang587b8f22016-08-17 17:40:45 -0700155 private final ActionBarAdapter.Listener mActionBarListener = new ActionBarAdapter.Listener() {
156 @Override
157 public void onAction(int action) {
158 switch (action) {
159 case ActionBarAdapter.Listener.Action.START_SELECTION_MODE:
160 displayCheckBoxes(true);
161 startSearchOrSelectionMode();
162 break;
163 case ActionBarAdapter.Listener.Action.START_SEARCH_MODE:
164 if (!mIsRecreatedInstance) {
Wenyi Wang79675452016-08-17 10:43:28 -0700165 Logger.logScreenView(mActivity, ScreenEvent.ScreenType.SEARCH);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700166 }
167 startSearchOrSelectionMode();
168 break;
169 case ActionBarAdapter.Listener.Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE:
Wenyi Wang79675452016-08-17 10:43:28 -0700170 mActivity.showFabWithAnimation(/* showFab */ true);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700171 break;
172 case ActionBarAdapter.Listener.Action.STOP_SEARCH_AND_SELECTION_MODE:
173 // If queryString is empty, fragment data will not be reloaded,
174 // so hamburger promo should be checked now.
175 // Otherwise, promo should be checked and displayed after reloading, b/30706521.
176 if (TextUtils.isEmpty(getQueryString())) {
177 maybeShowHamburgerFeatureHighlight();
178 }
179 setQueryTextToFragment("");
180 maybeHideCheckBoxes();
Wenyi Wang79675452016-08-17 10:43:28 -0700181 mActivity.invalidateOptionsMenu();
182 mActivity.showFabWithAnimation(/* showFab */ true);
yaolu491cca52016-09-30 14:04:40 -0700183
184 // Alert user if sync is off and not dismissed before
185 setSyncOffAlert();
186
Wenyi Wang587b8f22016-08-17 17:40:45 -0700187 // Determine whether the account has pullToRefresh feature
Walter Jang9adc9ef2016-11-02 18:50:38 -0700188 setSwipeRefreshLayoutEnabledOrNot(getFilter());
Wenyi Wang587b8f22016-08-17 17:40:45 -0700189 break;
190 case ActionBarAdapter.Listener.Action.CHANGE_SEARCH_QUERY:
191 final String queryString = mActionBarAdapter.getQueryString();
192 setQueryTextToFragment(queryString);
193 updateDebugOptionsVisibility(
194 ENABLE_DEBUG_OPTIONS_HIDDEN_CODE.equals(queryString));
195 break;
196 default:
197 throw new IllegalStateException("Unknown ActionBarAdapter action: " + action);
198 }
199 }
200
201 private void startSearchOrSelectionMode() {
202 configureContactListFragment();
203 maybeHideCheckBoxes();
Wenyi Wang79675452016-08-17 10:43:28 -0700204 mActivity.invalidateOptionsMenu();
205 mActivity.showFabWithAnimation(/* showFab */ false);
206
Wenyi Wang587b8f22016-08-17 17:40:45 -0700207 final Context context = getContext();
208 if (!SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(context)) {
209 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(context);
210 }
211 }
212
213 private void updateDebugOptionsVisibility(boolean visible) {
214 if (mEnableDebugMenuOptions != visible) {
215 mEnableDebugMenuOptions = visible;
Wenyi Wang79675452016-08-17 10:43:28 -0700216 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700217 }
218 }
219
220 private void setQueryTextToFragment(String query) {
221 setQueryString(query, true);
222 setVisibleScrollbarEnabled(!isSearchMode());
223 }
224
225 @Override
226 public void onUpButtonPressed() {
Wenyi Wang79675452016-08-17 10:43:28 -0700227 mActivity.onBackPressed();
228 }
229 };
230
231 private final View.OnClickListener mAddContactListener = new View.OnClickListener() {
232 @Override
233 public void onClick(View v) {
234 AccountFilterUtil.startEditorIntent(getContext(), mActivity.getIntent(), getFilter());
Wenyi Wang587b8f22016-08-17 17:40:45 -0700235 }
236 };
Dmitri Plotnikov4b4681d2010-05-06 21:06:28 -0700237
Dmitri Plotnikov9716d052010-05-10 18:03:54 -0700238 public DefaultContactBrowseListFragment() {
239 setPhotoLoaderEnabled(true);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700240 // Don't use a QuickContactBadge. Just use a regular ImageView. Using a QuickContactBadge
241 // inside the ListView prevents us from using MODE_FULLY_EXPANDED and messes up ripples.
242 setQuickContactEnabled(false);
Dmitri Plotnikov1ce1e7c2010-05-13 16:41:00 -0700243 setSectionHeaderDisplayEnabled(true);
Dmitri Plotnikov5245ea62010-12-10 15:03:13 -0800244 setVisibleScrollbarEnabled(true);
Wenyi Wang9746ad82016-06-28 12:48:28 -0700245 setDisplayDirectoryHeader(false);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700246 setHasOptionsMenu(true);
Tingting Wang1243cb42016-08-05 16:42:21 -0700247 }
248
Dmitri Plotnikov92a6fdf2010-07-09 13:19:33 -0700249 @Override
Jay Shrauner8c42b522013-08-15 09:32:47 -0700250 public CursorLoader createCursorLoader(Context context) {
Wenyi Wangaad30f52016-05-12 14:45:22 -0700251 return new FavoritesAndContactsLoader(context);
Dave Santoro84cac442011-08-24 15:23:10 -0700252 }
253
254 @Override
Wenyi Wang1114fde2016-07-11 21:44:02 -0700255 public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Wenyi Wang5db18212016-08-10 17:11:26 -0700256 if (loader.getId() == Directory.DEFAULT) {
Walter Janga773fb92016-09-30 15:23:30 -0700257 bindListHeader(data == null ? 0 : data.getCount());
Wenyi Wang5db18212016-08-10 17:11:26 -0700258 }
Wenyi Wang1114fde2016-07-11 21:44:02 -0700259 super.onLoadFinished(loader, data);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700260 if (!isSearchMode()) {
261 maybeShowHamburgerFeatureHighlight();
262 }
263 }
264
Wenyi Wanged9f9742016-09-08 13:50:39 -0700265 private void maybeShowHamburgerFeatureHighlight() {
Tingting Wang8857b942016-08-29 13:53:41 -0700266 if (mActionBarAdapter!= null && !mActionBarAdapter.isSearchMode()
Wenyi Wang587b8f22016-08-17 17:40:45 -0700267 && !mActionBarAdapter.isSelectionMode()
Wenyi Wang4cc72d52016-10-06 12:42:54 -0700268 && !isTalkbackOnAndOnPreLollipopMr1()
Wenyi Wang587b8f22016-08-17 17:40:45 -0700269 && SharedPreferenceUtil.getShouldShowHamburgerPromo(getContext())) {
Wenyi Wang79675452016-08-17 10:43:28 -0700270 if (FeatureHighlightHelper.showHamburgerFeatureHighlight(mActivity)) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700271 SharedPreferenceUtil.setHamburgerPromoDisplayedBefore(getContext());
272 }
Tingting Wang1243cb42016-08-05 16:42:21 -0700273 }
Wenyi Wang1114fde2016-07-11 21:44:02 -0700274 }
275
Wenyi Wang4cc72d52016-10-06 12:42:54 -0700276 // There's a crash if we show feature highlight when Talkback is on, on API 21 and below.
277 // See b/31180524.
278 private boolean isTalkbackOnAndOnPreLollipopMr1(){
279 return ((AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE))
280 .isTouchExplorationEnabled()
281 && !CompatUtils.isLollipopMr1Compatible();
282 }
283
Wenyi Wang1114fde2016-07-11 21:44:02 -0700284 private void bindListHeader(int numberOfContacts) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700285 final ContactListFilter filter = getFilter();
Xi Chena455bd52016-07-25 17:00:22 -0700286 // If the phone has at least one Google account whose sync status is unsyncable or pending
287 // or active, we have to make mAccountFilterContainer visible.
288 if (!isSearchMode() && numberOfContacts <= 0 && shouldShowEmptyView(filter)) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700289 if (filter != null && filter.isContactsFilterType()) {
290 makeViewVisible(mEmptyHomeView);
291 } else {
292 makeViewVisible(mEmptyAccountView);
293 }
294 return;
295 }
296 makeViewVisible(mAccountFilterContainer);
Walter Jang92942632016-07-14 19:49:32 +0000297 if (isSearchMode()) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700298 hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
Walter Jang92942632016-07-14 19:49:32 +0000299 } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700300 bindListHeaderCustom(getListView(), mAccountFilterContainer);
Walter Jang92942632016-07-14 19:49:32 +0000301 } else if (filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
Wenyi Wang1114fde2016-07-11 21:44:02 -0700302 final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
303 filter.accountName, filter.accountType, filter.dataSet);
Wenyi Wangf128ea82016-07-25 11:56:40 -0700304 bindListHeader(getContext(), getListView(), mAccountFilterContainer,
Wenyi Wang1114fde2016-07-11 21:44:02 -0700305 accountWithDataSet, numberOfContacts);
306 } else {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700307 hideHeaderAndAddPadding(getContext(), getListView(), mAccountFilterContainer);
Wenyi Wang1114fde2016-07-11 21:44:02 -0700308 }
309 }
310
Xi Chena455bd52016-07-25 17:00:22 -0700311 /**
312 * If at least one Google account is unsyncable or its sync status is pending or active, we
313 * should not show empty view even if the number of contacts is 0. We should show sync status
314 * with empty list instead.
315 */
316 private boolean shouldShowEmptyView(ContactListFilter filter) {
317 if (filter == null) {
318 return true;
319 }
320 // TODO(samchen) : Check ContactListFilter.FILTER_TYPE_CUSTOM
321 if (ContactListFilter.FILTER_TYPE_DEFAULT == filter.filterType
322 || ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS == filter.filterType) {
323 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(getContext())
324 .getAccounts(/* contactsWritableOnly */ true);
325 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
326
327 if (syncableAccounts != null && syncableAccounts.size() > 0) {
328 for (Account account : syncableAccounts) {
329 if (SyncUtil.isSyncStatusPendingOrActive(account)
330 || SyncUtil.isUnsyncableGoogleAccount(account)) {
331 return false;
332 }
333 }
334 }
335 } else if (ContactListFilter.FILTER_TYPE_ACCOUNT == filter.filterType) {
336 final Account account = new Account(filter.accountName, filter.accountType);
337 return !(SyncUtil.isSyncStatusPendingOrActive(account)
338 || SyncUtil.isUnsyncableGoogleAccount(account));
339 }
340 return true;
341 }
342
Wenyi Wangf128ea82016-07-25 11:56:40 -0700343 // Show the view that's specified by id and hide the other two.
344 private void makeViewVisible(View view) {
345 mEmptyAccountView.setVisibility(view == mEmptyAccountView ? View.VISIBLE : View.GONE);
346 mEmptyHomeView.setVisibility(view == mEmptyHomeView ? View.VISIBLE : View.GONE);
347 mAccountFilterContainer.setVisibility(
348 view == mAccountFilterContainer ? View.VISIBLE : View.GONE);
349 }
350
Wenyi Wang32912eb2016-09-29 17:17:07 -0700351 public void scrollToTop() {
352 if (getListView() != null) {
353 getListView().setSelection(0);
354 }
355 }
356
Wenyi Wang1114fde2016-07-11 21:44:02 -0700357 @Override
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700358 protected void onItemClick(int position, long id) {
Brian Attwellb831b142014-10-03 15:45:18 -0700359 final Uri uri = getAdapter().getContactUri(position);
360 if (uri == null) {
361 return;
362 }
Walter Jange9ea4f02016-05-10 09:39:46 -0700363 if (getAdapter().isDisplayingCheckBoxes()) {
364 super.onItemClick(position, id);
365 return;
366 }
Wenyi Wang2b943992016-05-20 17:21:35 -0700367 viewContact(position, uri, getAdapter().isEnterpriseContact(position));
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700368 }
369
370 @Override
371 protected ContactListAdapter createListAdapter() {
Dmitri Plotnikov0b976fc2010-06-28 16:56:17 -0700372 DefaultContactListAdapter adapter = new DefaultContactListAdapter(getContext());
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700373 adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
Brian Attwelldff843a2014-07-09 23:10:09 -0700374 adapter.setDisplayPhotos(true);
375 adapter.setPhotoPosition(
376 ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700377 return adapter;
378 }
379
380 @Override
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700381 public ContactListFilter getFilter() {
382 return mContactListFilterController.getFilter();
383 }
384
385 @Override
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700386 protected View inflateView(LayoutInflater inflater, ViewGroup container) {
Wenyi Wangf128ea82016-07-25 11:56:40 -0700387 final View view = inflater.inflate(R.layout.contact_list_content, null);
388
389 mAccountFilterContainer = view.findViewById(R.id.account_filter_header_container);
390
391 // Add empty main view and account view to list.
392 final FrameLayout contactListLayout = (FrameLayout) view.findViewById(R.id.contact_list);
393 mEmptyAccountView = getEmptyAccountView(inflater);
394 mEmptyHomeView = getEmptyHomeView(inflater);
395 contactListLayout.addView(mEmptyAccountView);
396 contactListLayout.addView(mEmptyHomeView);
397
398 return view;
399 }
400
401 private View getEmptyHomeView(LayoutInflater inflater) {
402 final View emptyHomeView = inflater.inflate(R.layout.empty_home_view, null);
403 // Set image margins.
404 final ImageView image = (ImageView) emptyHomeView.findViewById(R.id.empty_home_image);
405 final LayoutParams params = (LayoutParams) image.getLayoutParams();
406 final int screenHeight = getResources().getDisplayMetrics().heightPixels;
407 final int marginTop = screenHeight / 2 -
408 getResources().getDimensionPixelSize(R.dimen.empty_home_view_image_offset) ;
409 params.setMargins(0, marginTop, 0, 0);
410 params.gravity = Gravity.CENTER_HORIZONTAL;
411 image.setLayoutParams(params);
412
413 // Set up add contact button.
414 final Button addContactButton =
415 (Button) emptyHomeView.findViewById(R.id.add_contact_button);
Wenyi Wang79675452016-08-17 10:43:28 -0700416 addContactButton.setOnClickListener(mAddContactListener);
Wenyi Wangf128ea82016-07-25 11:56:40 -0700417 return emptyHomeView;
418 }
419
420 private View getEmptyAccountView(LayoutInflater inflater) {
421 final View emptyAccountView = inflater.inflate(R.layout.empty_account_view, null);
422 // Set image margins.
423 final ImageView image = (ImageView) emptyAccountView.findViewById(R.id.empty_account_image);
424 final LayoutParams params = (LayoutParams) image.getLayoutParams();
425 final int height = getResources().getDisplayMetrics().heightPixels;
426 final int divisor =
427 getResources().getInteger(R.integer.empty_account_view_image_margin_divisor);
428 final int offset =
429 getResources().getDimensionPixelSize(R.dimen.empty_account_view_image_offset);
430 params.setMargins(0, height / divisor + offset, 0, 0);
431 params.gravity = Gravity.CENTER_HORIZONTAL;
432 image.setLayoutParams(params);
433
434 // Set up add contact button.
435 final Button addContactButton =
436 (Button) emptyAccountView.findViewById(R.id.add_contact_button);
Wenyi Wang79675452016-08-17 10:43:28 -0700437 addContactButton.setOnClickListener(mAddContactListener);
Wenyi Wangf128ea82016-07-25 11:56:40 -0700438 return emptyAccountView;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700439 }
440
441 @Override
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700442 public void onCreate(Bundle savedState) {
443 super.onCreate(savedState);
444 mIsRecreatedInstance = (savedState != null);
445 mContactListFilterController = ContactListFilterController.getInstance(getContext());
446 mContactListFilterController.checkFilterValidity(false);
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700447 // Use FILTER_TYPE_ALL_ACCOUNTS filter if the instance is not a re-created one.
Wenyi Wanged9f9742016-09-08 13:50:39 -0700448 // This is useful when user upgrades app while an account filter was
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700449 // stored in sharedPreference in a previous version of Contacts app.
450 final ContactListFilter filter = mIsRecreatedInstance
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700451 ? getFilter()
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700452 : AccountFilterUtil.createContactsFilter(getContext());
453 setContactListFilter(filter);
454 }
455
456 @Override
Dmitri Plotnikovbba62ac2010-06-03 10:22:59 -0700457 protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
458 super.onCreateView(inflater, container);
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700459
Walter Jang9adc9ef2016-11-02 18:50:38 -0700460 initSwipeRefreshLayout();
461
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700462 // Putting the header view inside a container will allow us to make
463 // it invisible later. See checkHeaderViewVisibility()
Wenyi Wang587b8f22016-08-17 17:40:45 -0700464 final FrameLayout headerContainer = new FrameLayout(inflater.getContext());
Dmitri Plotnikov17abbae2010-09-28 20:36:45 -0700465 mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false);
466 headerContainer.addView(mSearchHeaderView);
Dmitri Plotnikov8db19132011-01-13 14:24:52 -0800467 getListView().addHeaderView(headerContainer, null, false);
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700468 checkHeaderViewVisibility();
Josh Gargusf4aaa472012-04-26 17:27:22 -0700469
470 mSearchProgress = getView().findViewById(R.id.search_progress);
471 mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText);
yaolu491cca52016-09-30 14:04:40 -0700472
473 mAlertContainer = getView().findViewById(R.id.alert_container);
474 mAlertText = (TextView) mAlertContainer.findViewById(R.id.alert_text);
475 mAlertDismissIcon = (ImageView) mAlertContainer.findViewById(R.id.alert_dismiss_icon);
476 mAlertText.setOnClickListener(new View.OnClickListener() {
477 @Override
478 public void onClick(View v) {
479 turnSyncOn();
480 }
481 });
482 mAlertDismissIcon.setOnClickListener(new View.OnClickListener() {
483 @Override
484 public void onClick(View v) {
485 dismiss();
486 }
487 });
488
489 mAlertContainer.setVisibility(View.GONE);
490 }
491
492 private void turnSyncOn() {
493 final ContactListFilter filter = getFilter();
494 if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
495 && mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
496 ContentResolver.setSyncAutomatically(
497 new Account(filter.accountName, filter.accountType),
498 ContactsContract.AUTHORITY, true);
499 mAlertContainer.setVisibility(View.GONE);
500 } else {
501 final EnableGlobalSyncDialogFragment dialog = new
502 EnableGlobalSyncDialogFragment();
503 dialog.show(this, filter);
504 }
505 }
506
507 @Override
508 public void onEnableAutoSync(ContactListFilter filter) {
509 // Turn on auto-sync
510 ContentResolver.setMasterSyncAutomatically(true);
511 // Also enable Contacts sync
512 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(
513 getContext()).getAccounts(/* contactsWritableOnly */ true);
514 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
515 if (syncableAccounts != null && syncableAccounts.size() > 0) {
516 for (Account account : syncableAccounts) {
517 ContentResolver.setSyncAutomatically(new Account(account.name, account.type),
518 ContactsContract.AUTHORITY, true);
519 }
520 }
521 mAlertContainer.setVisibility(View.GONE);
522 }
523
524 private void dismiss() {
525 if (mReasonSyncOff == SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF) {
526 SharedPreferenceUtil.incNumOfDismissesForAutoSyncOff(getContext());
527 } else if (mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
528 SharedPreferenceUtil.incNumOfDismissesForAccountSyncOff(
529 getContext(), getFilter().accountName);
530 }
531 mAlertContainer.setVisibility(View.GONE);
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700532 }
533
Xi Chen947c23d2016-06-30 20:23:52 -0700534 private void initSwipeRefreshLayout() {
535 mSwipeRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh);
536 if (mSwipeRefreshLayout == null) {
537 return;
538 }
539
540 mSwipeRefreshLayout.setEnabled(true);
541 // Request sync contacts
542 mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
543 @Override
544 public void onRefresh() {
yaolu9d998592016-10-14 23:21:00 -0700545 mHandler.removeCallbacks(mCancelRefresh);
yaolu395c3f82016-10-18 11:13:39 -0700546
547 final boolean isNetworkConnected = SyncUtil.isNetworkConnected(getContext());
548 if (!isNetworkConnected) {
549 mSwipeRefreshLayout.setRefreshing(false);
550 ((PeopleActivity)getActivity()).showConnectionErrorMsg();
551 return;
552 }
553
yaolu9d998592016-10-14 23:21:00 -0700554 syncContacts(getFilter());
Walter Jangdf86ede2016-10-19 09:48:29 -0700555 mHandler.postDelayed(mCancelRefresh, Flags.getInstance()
yaolu9d998592016-10-14 23:21:00 -0700556 .getInteger(Experiments.PULL_TO_REFRESH_CANCEL_REFRESH_MILLIS));
Xi Chen947c23d2016-06-30 20:23:52 -0700557 }
558 });
559 mSwipeRefreshLayout.setColorSchemeResources(
560 R.color.swipe_refresh_color1,
561 R.color.swipe_refresh_color2,
562 R.color.swipe_refresh_color3,
563 R.color.swipe_refresh_color4);
564 mSwipeRefreshLayout.setDistanceToTriggerSync(
565 (int) getResources().getDimension(R.dimen.pull_to_refresh_distance));
566 }
567
Xi Chena455bd52016-07-25 17:00:22 -0700568 /**
569 * Request sync for the Google accounts (not include Google+ accounts) specified by the given
570 * filter.
571 */
Xi Chen947c23d2016-06-30 20:23:52 -0700572 private void syncContacts(ContactListFilter filter) {
573 if (filter == null) {
574 return;
575 }
yaolu395c3f82016-10-18 11:13:39 -0700576
Xi Chen947c23d2016-06-30 20:23:52 -0700577 final Bundle bundle = new Bundle();
578 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
579 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
580
Xi Chen4a7a9ea2016-07-20 11:23:30 -0700581 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(
Wenyi Wang79675452016-08-17 10:43:28 -0700582 getContext()).getAccounts(/* contactsWritableOnly */ true);
Xi Chen4a7a9ea2016-07-20 11:23:30 -0700583 final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
584 if (syncableAccounts != null && syncableAccounts.size() > 0) {
585 for (Account account : syncableAccounts) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700586 // We can prioritize Contacts sync if sync is not initialized yet.
Xi Chena455bd52016-07-25 17:00:22 -0700587 if (!SyncUtil.isSyncStatusPendingOrActive(account)
588 || SyncUtil.isUnsyncableGoogleAccount(account)) {
Xi Chen4a7a9ea2016-07-20 11:23:30 -0700589 ContentResolver.requestSync(account, ContactsContract.AUTHORITY, bundle);
Xi Chen947c23d2016-06-30 20:23:52 -0700590 }
591 }
592 }
593 }
594
yaolu491cca52016-09-30 14:04:40 -0700595 private void setSyncOffAlert() {
596 final ContactListFilter filter = getFilter();
597 final Account account = filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
598 && filter.isGoogleAccountType()
599 ? new Account(filter.accountName, filter.accountType) : null;
600
601 if (account == null && !filter.isContactsFilterType()) {
602 mAlertContainer.setVisibility(View.GONE);
603 } else {
604 mReasonSyncOff = SyncUtil.calculateReasonSyncOff(getContext(), account);
605 final boolean isAlertVisible =
606 SyncUtil.isAlertVisible(getContext(), account, mReasonSyncOff);
607 setSyncOffMsg(mReasonSyncOff);
608 mAlertContainer.setVisibility(isAlertVisible ? View.VISIBLE : View.GONE);
609 }
610 }
611
612 private void setSyncOffMsg(int reason) {
613 final Resources resources = getResources();
614 switch (reason) {
615 case SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF:
616 mAlertText.setText(resources.getString(R.string.auto_sync_off));
617 break;
618 case SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF:
619 mAlertText.setText(resources.getString(R.string.account_sync_off));
620 break;
621 default:
622 }
623 }
624
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700625 @Override
Wenyi Wang587b8f22016-08-17 17:40:45 -0700626 public void onActivityCreated(Bundle savedInstanceState) {
627 super.onActivityCreated(savedInstanceState);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700628
Wenyi Wang79675452016-08-17 10:43:28 -0700629 mActivity = (ContactsDrawerActivity) getActivity();
630 mActionBarAdapter = new ActionBarAdapter(mActivity, mActionBarListener,
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700631 mActivity.getSupportActionBar(), mActivity.getToolbar(),
632 R.string.enter_contact_name);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700633 mActionBarAdapter.setShowHomeIcon(true);
634 initializeActionBarAdapter(savedInstanceState);
635 if (isSearchMode()) {
636 mActionBarAdapter.setFocusOnSearchView();
637 }
638
639 setCheckBoxListListener(new CheckBoxListListener());
640 setOnContactListActionListener(new ContactBrowserActionListener());
Wenyi Wangcc8f2722016-09-07 14:52:32 -0700641 if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_DELETION_IN_PROGRESS)) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700642 deleteSelectedContacts();
643 }
Wenyi Wang72b0f452016-09-30 15:29:04 -0700644
645 setDirectorySearchMode();
James Laskey1842bb22016-10-20 14:38:04 -0700646 mCanSetActionBar = true;
Wenyi Wang587b8f22016-08-17 17:40:45 -0700647 }
648
649 public void initializeActionBarAdapter(Bundle savedInstanceState) {
650 if (mActionBarAdapter != null) {
651 mActionBarAdapter.initialize(savedInstanceState, mContactsRequest);
652 }
653 }
654
Wenyi Wanged9f9742016-09-08 13:50:39 -0700655 private void configureFragment() {
656 if (mFragmentInitialized && !mFromOnNewIntent) {
657 return;
658 }
659
660 mFragmentInitialized = true;
661
662 if (mFromOnNewIntent || !mIsRecreatedInstance) {
Wenyi Wanga730d9d2016-09-16 18:25:51 -0700663 mFromOnNewIntent = false;
Wenyi Wanged9f9742016-09-08 13:50:39 -0700664 configureFragmentForRequest();
665 }
666
667 configureContactListFragment();
668 }
669
670 private void configureFragmentForRequest() {
671 ContactListFilter filter = null;
672 final int actionCode = mContactsRequest.getActionCode();
673 boolean searchMode = mContactsRequest.isSearchMode();
674 switch (actionCode) {
675 case ContactsRequest.ACTION_ALL_CONTACTS:
676 filter = AccountFilterUtil.createContactsFilter(getContext());
677 break;
678 case ContactsRequest.ACTION_CONTACTS_WITH_PHONES:
679 filter = ContactListFilter.createFilterWithType(
680 ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY);
681 break;
682
683 case ContactsRequest.ACTION_FREQUENT:
684 case ContactsRequest.ACTION_STREQUENT:
685 case ContactsRequest.ACTION_STARRED:
686 case ContactsRequest.ACTION_VIEW_CONTACT:
687 default:
688 break;
689 }
690
691 if (filter != null) {
692 setContactListFilter(filter);
693 searchMode = false;
694 }
695
696 if (mContactsRequest.getContactUri() != null) {
697 searchMode = false;
698 }
699
700 mActionBarAdapter.setSearchMode(searchMode);
701 configureContactListFragmentForRequest();
702 }
703
704 private void configureContactListFragmentForRequest() {
705 final Uri contactUri = mContactsRequest.getContactUri();
706 if (contactUri != null) {
707 setSelectedContactUri(contactUri);
708 }
709
710 setQueryString(mActionBarAdapter.getQueryString(), true);
711 setVisibleScrollbarEnabled(!isSearchMode());
Wenyi Wang72b0f452016-09-30 15:29:04 -0700712 }
Wenyi Wanged9f9742016-09-08 13:50:39 -0700713
Wenyi Wang72b0f452016-09-30 15:29:04 -0700714 private void setDirectorySearchMode() {
Wenyi Wangc3857fa2016-11-07 17:10:07 -0800715 if (mContactsRequest != null && mContactsRequest.isDirectorySearchEnabled()) {
Wenyi Wanged9f9742016-09-08 13:50:39 -0700716 setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
717 } else {
718 setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
719 }
720 }
721
Wenyi Wang587b8f22016-08-17 17:40:45 -0700722 @Override
723 public void onResume() {
724 super.onResume();
Wenyi Wanged9f9742016-09-08 13:50:39 -0700725 configureFragment();
726 maybeShowHamburgerFeatureHighlight();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700727 // Re-register the listener, which may have been cleared when onSaveInstanceState was
728 // called. See also: onSaveInstanceState
729 mActionBarAdapter.setListener(mActionBarListener);
730 mDisableOptionItemSelected = false;
731 maybeHideCheckBoxes();
732 }
733
734 private void maybeHideCheckBoxes() {
735 if (!mActionBarAdapter.isSelectionMode()) {
736 displayCheckBoxes(false);
737 }
738 }
739
740 public ActionBarAdapter getActionBarAdapter(){
741 return mActionBarAdapter;
742 }
743
744 @Override
Makoto Onuki1db00f62011-10-19 12:40:12 -0700745 protected void setSearchMode(boolean flag) {
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700746 super.setSearchMode(flag);
747 checkHeaderViewVisibility();
Josh Gargusf4aaa472012-04-26 17:27:22 -0700748 if (!flag) showSearchProgress(false);
749 }
750
751 /** Show or hide the directory-search progress spinner. */
752 private void showSearchProgress(boolean show) {
Andrew Leee599bf02014-05-15 14:13:17 -0700753 if (mSearchProgress != null) {
754 mSearchProgress.setVisibility(show ? View.VISIBLE : View.GONE);
755 }
Dmitri Plotnikovfe3f7902010-06-08 14:24:29 -0700756 }
757
758 private void checkHeaderViewVisibility() {
Andrew Leee15828e2014-05-01 14:00:25 -0700759 // Hide the search header by default.
Dmitri Plotnikov17abbae2010-09-28 20:36:45 -0700760 if (mSearchHeaderView != null) {
761 mSearchHeaderView.setVisibility(View.GONE);
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700762 }
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700763 }
764
765 @Override
Wenyi Wangaad30f52016-05-12 14:45:22 -0700766 protected void setListHeader() {
767 if (!isSearchMode()) {
768 return;
Dmitri Plotnikov238d9842010-05-04 14:14:57 -0700769 }
Wenyi Wangaad30f52016-05-12 14:45:22 -0700770 ContactListAdapter adapter = getAdapter();
771 if (adapter == null) {
772 return;
773 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700774
Wenyi Wangaad30f52016-05-12 14:45:22 -0700775 // In search mode we only display the header if there is nothing found
776 if (TextUtils.isEmpty(getQueryString()) || !adapter.areAllPartitionsEmpty()) {
777 mSearchHeaderView.setVisibility(View.GONE);
778 showSearchProgress(false);
779 } else {
780 mSearchHeaderView.setVisibility(View.VISIBLE);
781 if (adapter.isLoading()) {
782 mSearchProgressText.setText(R.string.search_results_searching);
783 showSearchProgress(true);
784 } else {
785 mSearchProgressText.setText(R.string.listFoundAllContactsZero);
786 mSearchProgressText.sendAccessibilityEvent(
787 AccessibilityEvent.TYPE_VIEW_SELECTED);
788 showSearchProgress(false);
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700789 }
Wenyi Wangaad30f52016-05-12 14:45:22 -0700790 }
Isaac Katzenelsonead19c52011-07-29 18:24:53 -0700791 }
Xi Chen947c23d2016-06-30 20:23:52 -0700792
793 public SwipeRefreshLayout getSwipeRefreshLayout() {
794 return mSwipeRefreshLayout;
795 }
Wenyi Wang587b8f22016-08-17 17:40:45 -0700796
797 private final class CheckBoxListListener implements OnCheckBoxListActionListener {
798 @Override
799 public void onStartDisplayingCheckBoxes() {
800 mActionBarAdapter.setSelectionMode(true);
Wenyi Wang79675452016-08-17 10:43:28 -0700801 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700802 }
803
804 @Override
805 public void onSelectedContactIdsChanged() {
806 mActionBarAdapter.setSelectionCount(getSelectedContactIds().size());
Wenyi Wang79675452016-08-17 10:43:28 -0700807 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700808 }
809
810 @Override
811 public void onStopDisplayingCheckBoxes() {
812 mActionBarAdapter.setSelectionMode(false);
813 }
814 }
815
Wenyi Wang32912eb2016-09-29 17:17:07 -0700816 public void setFilterAndUpdateTitle(ContactListFilter filter) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700817 setFilterAndUpdateTitle(filter, true);
818 }
819
820 private void setFilterAndUpdateTitle(ContactListFilter filter, boolean restoreSelectedUri) {
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700821 setContactListFilter(filter);
822 updateListFilter(filter, restoreSelectedUri);
Wenyi Wangcfe94652016-10-03 21:05:04 -0700823 mActivity.setTitle(AccountFilterUtil.getActionBarTitleForFilter(mActivity, filter));
Wenyi Wang587b8f22016-08-17 17:40:45 -0700824
yaolu491cca52016-09-30 14:04:40 -0700825 // Alert user if sync is off and not dismissed before
826 setSyncOffAlert();
827
Wenyi Wang587b8f22016-08-17 17:40:45 -0700828 // Determine whether the account has pullToRefresh feature
Walter Jang9adc9ef2016-11-02 18:50:38 -0700829 setSwipeRefreshLayoutEnabledOrNot(filter);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700830 }
831
Wenyi Wanged9f9742016-09-08 13:50:39 -0700832 private void setSwipeRefreshLayoutEnabledOrNot(ContactListFilter filter) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700833 final SwipeRefreshLayout swipeRefreshLayout = getSwipeRefreshLayout();
834 if (swipeRefreshLayout == null) {
835 if (Log.isLoggable(TAG, Log.DEBUG)) {
836 Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
837 }
838 return;
839 }
840
841 swipeRefreshLayout.setRefreshing(false);
842 swipeRefreshLayout.setEnabled(false);
843
844 if (filter != null && !mActionBarAdapter.isSearchMode()
845 && !mActionBarAdapter.isSelectionMode()) {
846 final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(getContext())
847 .getAccounts(/* contactsWritableOnly */ true);
848 if (filter.isSyncable(accounts)) {
849 swipeRefreshLayout.setEnabled(true);
850 }
851 }
852 }
853
Wenyi Wanged9f9742016-09-08 13:50:39 -0700854 private void configureContactListFragment() {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700855 // Filter may be changed when activity is in background.
856 setFilterAndUpdateTitle(getFilter());
857 setVerticalScrollbarPosition(getScrollBarPosition());
858 setSelectionVisible(false);
Wenyi Wang79675452016-08-17 10:43:28 -0700859 mActivity.invalidateOptionsMenu();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700860 }
861
862 private int getScrollBarPosition() {
863 final Locale locale = Locale.getDefault();
864 final boolean isRTL =
865 TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL;
866 return isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
867 }
868
869 private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
870 ContactBrowserActionListener() {}
871
872 @Override
873 public void onSelectionChange() {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700874 }
875
876 @Override
877 public void onViewContactAction(int position, Uri contactLookupUri,
878 boolean isEnterpriseContact) {
879 if (isEnterpriseContact) {
880 // No implicit intent as user may have a different contacts app in work profile.
881 ContactsContract.QuickContact.showQuickContact(getContext(), new Rect(),
882 contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED, null);
883 } else {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700884 final int previousScreen;
885 if (isSearchMode()) {
886 previousScreen = ScreenEvent.ScreenType.SEARCH;
887 } else {
Wenyi Wangdf3e8492016-09-09 11:10:07 -0700888 if (isAllContactsFilter(getFilter())) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700889 if (position < getAdapter().getNumberOfFavorites()) {
890 previousScreen = ScreenEvent.ScreenType.FAVORITES;
891 } else {
892 previousScreen = ScreenEvent.ScreenType.ALL_CONTACTS;
893 }
894 } else {
895 previousScreen = ScreenEvent.ScreenType.LIST_ACCOUNT;
896 }
897 }
Walter Jang0396cf72016-09-21 13:23:29 -0700898
Wenyi Wang587b8f22016-08-17 17:40:45 -0700899 Logger.logListEvent(ListEvent.ActionType.CLICK,
900 /* listType */ getListTypeIncludingSearch(),
901 /* count */ getAdapter().getCount(),
902 /* clickedIndex */ position, /* numSelected */ 0);
Walter Jang0396cf72016-09-21 13:23:29 -0700903
904 ImplicitIntentsUtil.startQuickContact(
905 getActivity(), contactLookupUri, previousScreen);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700906 }
907 }
908
909 @Override
910 public void onDeleteContactAction(Uri contactUri) {
Wenyi Wang79675452016-08-17 10:43:28 -0700911 ContactDeletionInteraction.start(mActivity, contactUri, false);
Wenyi Wang587b8f22016-08-17 17:40:45 -0700912 }
913
914 @Override
915 public void onFinishAction() {
Wenyi Wang79675452016-08-17 10:43:28 -0700916 mActivity.onBackPressed();
Wenyi Wang587b8f22016-08-17 17:40:45 -0700917 }
918
919 @Override
920 public void onInvalidSelection() {
921 ContactListFilter filter;
922 ContactListFilter currentFilter = getFilter();
923 if (currentFilter != null
924 && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
925 filter = AccountFilterUtil.createContactsFilter(getContext());
926 setFilterAndUpdateTitle(filter);
927 } else {
928 filter = ContactListFilter.createFilterWithType(
929 ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
930 setFilterAndUpdateTitle(filter, /* restoreSelectedUri */ false);
931 }
932 setContactListFilter(filter);
933 }
934 }
935
936 private boolean isAllContactsFilter(ContactListFilter filter) {
937 return filter != null && filter.isContactsFilterType();
938 }
939
940 public void setContactsAvailable(boolean contactsAvailable) {
941 mContactsAvailable = contactsAvailable;
942 }
943
944 /**
945 * Set filter via ContactListFilterController
946 */
Wenyi Wanged9f9742016-09-08 13:50:39 -0700947 private void setContactListFilter(ContactListFilter filter) {
Wenyi Wang587b8f22016-08-17 17:40:45 -0700948 mContactListFilterController.setContactListFilter(filter,
949 /* persistent */ isAllContactsFilter(filter));
950 }
951
952 @Override
953 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
Wenyi Wang79675452016-08-17 10:43:28 -0700954 if (!mContactsAvailable || mActivity.isInSecondLevel()) {
955 // If contacts aren't available or this fragment is not visible, hide all menu items.
Wenyi Wang587b8f22016-08-17 17:40:45 -0700956 return;
957 }
958 super.onCreateOptionsMenu(menu, inflater);
959 inflater.inflate(R.menu.people_options, menu);
960 }
961
962 @Override
963 public void onPrepareOptionsMenu(Menu menu) {
964 mOptionsMenuContactsAvailable = mContactsAvailable;
965 if (!mOptionsMenuContactsAvailable) {
966 return;
967 }
968
969 final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode()
970 || mActionBarAdapter.isSelectionMode();
971 makeMenuItemVisible(menu, R.id.menu_search, !isSearchOrSelectionMode);
972
973 final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode()
974 && getSelectedContactIds().size() != 0;
975 makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions);
976 makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions);
977 final boolean showLinkContactsOptions = mActionBarAdapter.isSelectionMode()
978 && getSelectedContactIds().size() > 1;
979 makeMenuItemVisible(menu, R.id.menu_join, showLinkContactsOptions);
980
981 // Debug options need to be visible even in search mode.
982 makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions &&
983 hasExportIntentHandler());
John Shaobd9ef3c2016-12-15 12:42:03 -0800984
985 // Light tint the icons for normal mode, dark tint for search or selection mode.
986 for (int i = 0; i < menu.size(); ++i) {
987 final Drawable icon = menu.getItem(i).getIcon();
988 if (icon != null && !isSearchOrSelectionMode) {
989 icon.mutate().setColorFilter(ContextCompat.getColor(getContext(),
990 R.color.actionbar_icon_color), PorterDuff.Mode.SRC_ATOP);
991 }
992 }
Wenyi Wang587b8f22016-08-17 17:40:45 -0700993 }
994
995 private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) {
996 final MenuItem item = menu.findItem(itemId);
997 if (item != null) {
998 item.setVisible(visible);
999 }
1000 }
1001
1002 private boolean hasExportIntentHandler() {
1003 final Intent intent = new Intent();
1004 intent.setAction("com.android.providers.contacts.DUMP_DATABASE");
1005 final List<ResolveInfo> receivers =
1006 getContext().getPackageManager().queryIntentActivities(intent,
1007 PackageManager.MATCH_DEFAULT_ONLY);
1008 return receivers != null && receivers.size() > 0;
1009 }
1010
1011 @Override
1012 public boolean onOptionsItemSelected(MenuItem item) {
1013 if (mDisableOptionItemSelected) {
1014 return false;
1015 }
1016
1017 switch (item.getItemId()) {
1018 case android.R.id.home: {
1019 // The home icon on the action bar is pressed
1020 if (mActionBarAdapter.isUpShowing()) {
1021 // "UP" icon press -- should be treated as "back".
Wenyi Wang79675452016-08-17 10:43:28 -07001022 mActivity.onBackPressed();
Wenyi Wang587b8f22016-08-17 17:40:45 -07001023 }
1024 return true;
1025 }
1026 case R.id.menu_search: {
1027 if (!mActionBarAdapter.isSelectionMode()) {
1028 mActionBarAdapter.setSearchMode(true);
1029 }
1030 return true;
1031 }
1032 case R.id.menu_share: {
1033 shareSelectedContacts();
1034 return true;
1035 }
1036 case R.id.menu_join: {
1037 Logger.logListEvent(ListEvent.ActionType.LINK,
1038 /* listType */ getListTypeIncludingSearch(),
1039 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
1040 /* numSelected */ getAdapter().getSelectedContactIds().size());
1041 joinSelectedContacts();
1042 return true;
1043 }
1044 case R.id.menu_delete: {
1045 deleteSelectedContacts();
1046 return true;
1047 }
1048 case R.id.export_database: {
1049 final Intent intent = new Intent("com.android.providers.contacts.DUMP_DATABASE");
1050 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
1051 ImplicitIntentsUtil.startActivityOutsideApp(getContext(), intent);
1052 return true;
1053 }
1054 }
1055 return super.onOptionsItemSelected(item);
1056 }
1057
1058 /**
Wenyi Wanged9f9742016-09-08 13:50:39 -07001059 * Share all contacts that are currently selected. This method is pretty inefficient for
1060 * handling large numbers of contacts. I don't expect this to be a problem.
Wenyi Wang587b8f22016-08-17 17:40:45 -07001061 */
1062 private void shareSelectedContacts() {
1063 final StringBuilder uriListBuilder = new StringBuilder();
1064 for (Long contactId : getSelectedContactIds()) {
1065 final Uri contactUri = ContentUris.withAppendedId(
1066 ContactsContract.Contacts.CONTENT_URI, contactId);
1067 final Uri lookupUri = ContactsContract.Contacts.getLookupUri(
1068 getContext().getContentResolver(), contactUri);
1069 if (lookupUri == null) {
1070 continue;
1071 }
1072 final List<String> pathSegments = lookupUri.getPathSegments();
1073 if (pathSegments.size() < 2) {
1074 continue;
1075 }
1076 final String lookupKey = pathSegments.get(pathSegments.size() - 2);
1077 if (uriListBuilder.length() > 0) {
1078 uriListBuilder.append(':');
1079 }
1080 uriListBuilder.append(Uri.encode(lookupKey));
1081 }
1082 if (uriListBuilder.length() == 0) {
1083 return;
1084 }
1085 final Uri uri = Uri.withAppendedPath(
1086 ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI,
1087 Uri.encode(uriListBuilder.toString()));
1088 final Intent intent = new Intent(Intent.ACTION_SEND);
1089 intent.setType(ContactsContract.Contacts.CONTENT_VCARD_TYPE);
1090 intent.putExtra(Intent.EXTRA_STREAM, uri);
1091 try {
1092 startActivityForResult(Intent.createChooser(intent, getResources().getQuantityString(
1093 R.plurals.title_share_via,/* quantity */ getSelectedContactIds().size()))
1094 , ACTIVITY_REQUEST_CODE_SHARE);
1095 } catch (final ActivityNotFoundException ex) {
1096 Toast.makeText(getContext(), R.string.share_error, Toast.LENGTH_SHORT).show();
1097 }
1098 }
1099
1100 private void joinSelectedContacts() {
1101 final Context context = getContext();
1102 final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(
1103 context, getSelectedContactIdsArray());
1104 context.startService(intent);
1105
1106 mActionBarAdapter.setSelectionMode(false);
1107 }
1108
1109 private void deleteSelectedContacts() {
Wenyi Wangcc8f2722016-09-07 14:52:32 -07001110 final ContactMultiDeletionInteraction multiDeletionInteraction =
Wenyi Wang587b8f22016-08-17 17:40:45 -07001111 ContactMultiDeletionInteraction.start(this, getSelectedContactIds());
Wenyi Wangcc8f2722016-09-07 14:52:32 -07001112 multiDeletionInteraction.setListener(new MultiDeleteListener());
Wenyi Wang587b8f22016-08-17 17:40:45 -07001113 mIsDeletionInProgress = true;
1114 }
1115
1116 private final class MultiDeleteListener implements MultiContactDeleteListener {
1117 @Override
1118 public void onDeletionFinished() {
1119 // The parameters count and numSelected are both the number of contacts before deletion.
1120 Logger.logListEvent(ListEvent.ActionType.DELETE,
1121 /* listType */ getListTypeIncludingSearch(),
1122 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
1123 /* numSelected */ getSelectedContactIds().size());
1124 mActionBarAdapter.setSelectionMode(false);
1125 mIsDeletionInProgress = false;
1126 }
1127 }
1128
1129 private int getListTypeIncludingSearch() {
1130 return isSearchMode() ? ListEvent.ListType.SEARCH_RESULT : getListType();
1131 }
1132
Wenyi Wanged9f9742016-09-08 13:50:39 -07001133 public void setParameters(ContactsRequest contactsRequest, boolean fromOnNewIntent) {
Wenyi Wang587b8f22016-08-17 17:40:45 -07001134 mContactsRequest = contactsRequest;
Wenyi Wanged9f9742016-09-08 13:50:39 -07001135 mFromOnNewIntent = fromOnNewIntent;
Wenyi Wang587b8f22016-08-17 17:40:45 -07001136 }
1137
1138 @Override
1139 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1140 switch (requestCode) {
1141 // TODO: Using the new startActivityWithResultFromFragment API this should not be needed
1142 // anymore
1143 case ContactEntryListFragment.ACTIVITY_REQUEST_CODE_PICKER:
1144 if (resultCode == Activity.RESULT_OK) {
1145 onPickerResult(data);
1146 }
1147 case ACTIVITY_REQUEST_CODE_SHARE:
1148 Logger.logListEvent(ListEvent.ActionType.SHARE,
1149 /* listType */ getListTypeIncludingSearch(),
1150 /* count */ getAdapter().getCount(), /* clickedIndex */ -1,
1151 /* numSelected */ getAdapter().getSelectedContactIds().size());
1152
1153// TODO fix or remove multipicker code: ag/54762
1154// else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) {
1155// // Finish the activity if the sub activity was canceled as back key is used
1156// // to confirm user selection in MODE_PICK_MULTIPLE_PHONES.
1157// finish();
1158// }
1159// break;
1160 }
1161 }
1162
1163 public boolean getOptionsMenuContactsAvailable() {
1164 return mOptionsMenuContactsAvailable;
1165 }
1166
1167 @Override
1168 public void onSaveInstanceState(Bundle outState) {
1169 super.onSaveInstanceState(outState);
1170 // Clear the listener to make sure we don't get callbacks after onSaveInstanceState,
1171 // in order to avoid doing fragment transactions after it.
1172 // TODO Figure out a better way to deal with the issue (ag/120686).
1173 if (mActionBarAdapter != null) {
1174 mActionBarAdapter.setListener(null);
1175 mActionBarAdapter.onSaveInstanceState(outState);
1176 }
1177 mDisableOptionItemSelected = true;
1178 outState.putBoolean(KEY_DELETION_IN_PROGRESS, mIsDeletionInProgress);
1179 }
1180
1181 @Override
1182 public void onPause() {
1183 mOptionsMenuContactsAvailable = false;
1184 super.onPause();
1185 }
1186
1187 @Override
1188 public void onDestroy() {
1189 if (mActionBarAdapter != null) {
1190 mActionBarAdapter.setListener(null);
1191 }
Wenyi Wang587b8f22016-08-17 17:40:45 -07001192 super.onDestroy();
1193 }
Wenyi Wanged9f9742016-09-08 13:50:39 -07001194
1195 public boolean onKeyDown(int unicodeChar) {
1196 if (mActionBarAdapter.isSelectionMode()) {
1197 // Ignore keyboard input when in selection mode.
1198 return true;
1199 }
1200
1201 if (!mActionBarAdapter.isSearchMode()) {
1202 final String query = new String(new int[]{unicodeChar}, 0, 1);
1203 mActionBarAdapter.setSearchMode(true);
1204 mActionBarAdapter.setQueryString(query);
1205 return true;
1206 }
1207
1208 return false;
1209 }
James Laskey1842bb22016-10-20 14:38:04 -07001210
1211 public boolean canSetActionBar() {
1212 return mCanSetActionBar;
1213 }
Tingting Wang5d612da2016-08-08 10:00:45 -07001214}