1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.contacts.activities;
18 
19 import android.Manifest;
20 import android.accounts.Account;
21 import android.app.Fragment;
22 import android.app.FragmentManager;
23 import android.app.FragmentTransaction;
24 import android.content.BroadcastReceiver;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.SyncStatusObserver;
31 import android.content.res.Configuration;
32 import android.graphics.Color;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.provider.ContactsContract;
37 import android.provider.ContactsContract.Intents;
38 import android.provider.ContactsContract.ProviderStatus;
39 import androidx.annotation.LayoutRes;
40 import androidx.coordinatorlayout.widget.CoordinatorLayout;
41 import com.google.android.material.snackbar.Snackbar;
42 import androidx.core.content.ContextCompat;
43 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
44 import androidx.core.view.GravityCompat;
45 import androidx.drawerlayout.widget.DrawerLayout;
46 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
47 import androidx.appcompat.app.ActionBarDrawerToggle;
48 import androidx.appcompat.app.AppCompatActivity;
49 import androidx.appcompat.widget.Toolbar;
50 import android.util.Log;
51 import android.view.KeyCharacterMap;
52 import android.view.KeyEvent;
53 import android.view.LayoutInflater;
54 import android.view.View;
55 import android.view.ViewGroup;
56 import android.view.accessibility.AccessibilityEvent;
57 import android.view.accessibility.AccessibilityManager;
58 import android.widget.ImageButton;
59 import android.widget.Toast;
60 
61 import com.android.contacts.AppCompatContactsActivity;
62 import com.android.contacts.ContactSaveService;
63 import com.android.contacts.R;
64 import com.android.contacts.compat.CompatUtils;
65 import com.android.contacts.drawer.DrawerFragment;
66 import com.android.contacts.drawer.DrawerFragment.DrawerFragmentListener;
67 import com.android.contacts.editor.ContactEditorFragment;
68 import com.android.contacts.editor.SelectAccountDialogFragment;
69 import com.android.contacts.group.GroupListItem;
70 import com.android.contacts.group.GroupMembersFragment;
71 import com.android.contacts.group.GroupNameEditDialogFragment;
72 import com.android.contacts.group.GroupUtil;
73 import com.android.contacts.list.AccountFilterActivity;
74 import com.android.contacts.list.ContactListFilter;
75 import com.android.contacts.list.ContactListFilterController;
76 import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
77 import com.android.contacts.list.ContactsIntentResolver;
78 import com.android.contacts.list.ContactsRequest;
79 import com.android.contacts.list.ContactsUnavailableFragment;
80 import com.android.contacts.list.DefaultContactBrowseListFragment;
81 import com.android.contacts.list.MultiSelectContactsListFragment;
82 import com.android.contacts.list.ProviderStatusWatcher;
83 import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
84 import com.android.contacts.logging.Logger;
85 import com.android.contacts.logging.ScreenEvent.ScreenType;
86 import com.android.contacts.model.AccountTypeManager;
87 import com.android.contacts.model.account.AccountInfo;
88 import com.android.contacts.model.account.AccountWithDataSet;
89 import com.android.contacts.preference.ContactsPreferenceActivity;
90 import com.android.contacts.util.AccountFilterUtil;
91 import com.android.contacts.util.Constants;
92 import com.android.contacts.util.ImplicitIntentsUtil;
93 import com.android.contacts.util.MaterialColorMapUtils;
94 import com.android.contacts.util.PermissionsUtil;
95 import com.android.contacts.util.SharedPreferenceUtil;
96 import com.android.contacts.util.SyncUtil;
97 import com.android.contacts.util.ViewUtil;
98 import com.android.contacts.widget.FloatingActionButtonController;
99 import com.android.contactsbind.FeatureHighlightHelper;
100 import com.android.contactsbind.HelpUtils;
101 import com.android.contactsbind.ObjectFactory;
102 
103 import com.google.common.util.concurrent.Futures;
104 
105 import java.util.Collections;
106 import java.util.List;
107 import java.util.concurrent.atomic.AtomicInteger;
108 
109 /**
110  * Displays a list to browse contacts.
111  */
112 public class PeopleActivity extends AppCompatContactsActivity implements
113         DrawerFragmentListener,
114         SelectAccountDialogFragment.Listener {
115 
116     /** Possible views of Contacts app. */
117     public enum ContactsView {
118         NONE,
119         ALL_CONTACTS,
120         ASSISTANT,
121         GROUP_VIEW,
122         ACCOUNT_VIEW,
123     }
124 
125     private static final String TAG = "PeopleActivity";
126     private static final String TAG_ALL = "contacts-all";
127     private static final String TAG_UNAVAILABLE = "contacts-unavailable";
128     private static final String TAG_GROUP_VIEW = "contacts-groups";
129     private static final String TAG_SELECT_ACCOUNT_DIALOG = "selectAccountDialog";
130     private static final String TAG_GROUP_NAME_EDIT_DIALOG = "groupNameEditDialog";
131 
132     public static final String TAG_ASSISTANT = "contacts-assistant";
133     public static final String TAG_SECOND_LEVEL = "second-level";
134     public static final String TAG_THIRD_LEVEL = "third-level";
135     public static final String TAG_ASSISTANT_HELPER = "assistant-helper";
136     public static final String TAG_DUPLICATES = "DuplicatesFragment";
137     public static final String TAG_DUPLICATES_UTIL = "DuplicatesUtilFragment";
138 
139     private static final String KEY_GROUP_URI = "groupUri";
140     private static final String KEY_CONTACTS_VIEW = "contactsView";
141     private static final String KEY_NEW_GROUP_ACCOUNT = "newGroupAccount";
142 
143     private static final long DRAWER_CLOSE_DELAY = 300L;
144 
145     private ContactsIntentResolver mIntentResolver;
146     private ContactsRequest mRequest;
147     private AccountTypeManager mAccountTypeManager;
148 
149     private FloatingActionButtonController mFloatingActionButtonController;
150     private View mFloatingActionButtonContainer;
151     private boolean wasLastFabAnimationScaleIn = false;
152 
153     private ProviderStatusWatcher mProviderStatusWatcher;
154     private Integer mProviderStatus;
155 
156     private BroadcastReceiver mSaveServiceListener;
157 
158     private boolean mShouldSwitchToGroupView;
159 
160     private ContactsView mCurrentView;
161 
162     private CoordinatorLayout mLayoutRoot;
163 
164     /**
165      * Showing a list of Contacts. Also used for showing search results in search mode.
166      */
167     private DefaultContactBrowseListFragment mContactsListFragment;
168 
169     private GroupMembersFragment mMembersFragment;
170     private Uri mGroupUri;
171 
172     /**
173      * True if this activity instance is a re-created one.  i.e. set true after orientation change.
174      */
175     private boolean mIsRecreatedInstance;
176 
177     private boolean mShouldSwitchToAllContacts;
178 
179     /** Sequential ID assigned to each instance; used for logging */
180     private final int mInstanceId;
181     private static final AtomicInteger sNextInstanceId = new AtomicInteger();
182 
183     private ContactListFilterController mContactListFilterController;
184 
185     /** Navigation drawer related */
186     private DrawerLayout mDrawerLayout;
187     private DrawerFragment mDrawerFragment;
188     private ContactsActionBarDrawerToggle mToggle;
189     private Toolbar mToolbar;
190 
191     // The account the new group will be created under.
192     private AccountWithDataSet mNewGroupAccount;
193 
194     private Object mStatusChangeListenerHandle;
195 
196     private final Handler mHandler = new Handler();
197 
198     private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
199         public void onStatusChanged(int which) {
200             mHandler.post(new Runnable() {
201                 public void run() {
202                     onSyncStateUpdated();
203                 }
204             });
205         }
206     };
207 
208     // Update sync status for accounts in current ContactListFilter
onSyncStateUpdated()209     private void onSyncStateUpdated() {
210         if (isListFragmentInSearchMode() || isListFragmentInSelectionMode()) {
211             return;
212         }
213 
214         final ContactListFilter filter = mContactListFilterController.getFilter();
215         if (filter != null) {
216             final SwipeRefreshLayout swipeRefreshLayout =
217                     mContactsListFragment.getSwipeRefreshLayout();
218             if (swipeRefreshLayout == null) {
219                 if (Log.isLoggable(TAG, Log.DEBUG)) {
220                     Log.d(TAG, "Can not load swipeRefreshLayout, swipeRefreshLayout is null");
221                 }
222                 return;
223             }
224 
225             final List<AccountWithDataSet> accounts;
226             if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT &&
227                     filter.isGoogleAccountType()) {
228                 accounts = Collections.singletonList(new AccountWithDataSet(filter.accountName,
229                         filter.accountType, null));
230             } else if (filter.shouldShowSyncState()) {
231                 accounts = AccountInfo.extractAccounts(
232                         mAccountTypeManager.getWritableGoogleAccounts());
233             } else {
234                 accounts = Collections.emptyList();
235             }
236             if (SyncUtil.isAnySyncing(accounts)) {
237                 return;
238             }
239             swipeRefreshLayout.setRefreshing(false);
240         }
241     }
242 
showConnectionErrorMsg()243     public void showConnectionErrorMsg() {
244         Snackbar.make(mLayoutRoot, R.string.connection_error_message, Snackbar.LENGTH_LONG).show();
245     }
246 
247     private final ContactListFilterListener mFilterListener = new ContactListFilterListener() {
248         @Override
249         public void onContactListFilterChanged() {
250             final ContactListFilter filter = mContactListFilterController.getFilter();
251             handleFilterChangeForFragment(filter);
252             handleFilterChangeForActivity(filter);
253         }
254     };
255 
256     private final ProviderStatusListener mProviderStatusListener = new ProviderStatusListener() {
257         @Override
258         public void onProviderStatusChange() {
259             // TODO see if it works with drawer fragment.
260             updateViewConfiguration(false);
261         }
262     };
263 
264     private class ContactsActionBarDrawerToggle extends ActionBarDrawerToggle {
265         private boolean mMenuClickedBefore = SharedPreferenceUtil.getHamburgerMenuClickedBefore(
266                 PeopleActivity.this);
267 
ContactsActionBarDrawerToggle(AppCompatActivity activity, DrawerLayout drawerLayout, Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes)268         public ContactsActionBarDrawerToggle(AppCompatActivity activity, DrawerLayout drawerLayout,
269                 Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes) {
270             super(activity, drawerLayout, toolbar, openDrawerContentDescRes,
271                     closeDrawerContentDescRes);
272         }
273 
274         @Override
onDrawerOpened(View drawerView)275         public void onDrawerOpened(View drawerView) {
276             super.onDrawerOpened(drawerView);
277             if (!mMenuClickedBefore) {
278                 SharedPreferenceUtil.setHamburgerMenuClickedBefore(PeopleActivity.this);
279                 mMenuClickedBefore = true;
280             }
281             drawerView.requestFocus();
282             invalidateOptionsMenu();
283             // Stop search and selection mode like Gmail and Keep. Otherwise, if user switches to
284             // another fragment in navigation drawer, the current search/selection mode will be
285             // overlaid by the action bar of the newly-created fragment.
286             stopSearchAndSelection();
287             updateStatusBarBackground();
288         }
289 
stopSearchAndSelection()290         private void stopSearchAndSelection() {
291             final MultiSelectContactsListFragment listFragment;
292             if (isAllContactsView() || isAccountView()) {
293                 listFragment = getListFragment();
294             } else if (isGroupView()) {
295                 listFragment = getGroupFragment();
296             } else {
297                 listFragment = null;
298             }
299             if (listFragment == null) {
300                 return;
301             }
302             final ActionBarAdapter actionBarAdapter = listFragment.getActionBarAdapter();
303             if (actionBarAdapter == null) {
304                 return;
305             }
306             if (actionBarAdapter.isSearchMode()) {
307                 actionBarAdapter.setSearchMode(false);
308             } else if (actionBarAdapter.isSelectionMode()) {
309                 actionBarAdapter.setSelectionMode(false);
310             }
311         }
312 
313         @Override
onDrawerClosed(View view)314         public void onDrawerClosed(View view) {
315             super.onDrawerClosed(view);
316             invalidateOptionsMenu();
317         }
318 
319         @Override
onDrawerStateChanged(int newState)320         public void onDrawerStateChanged(int newState) {
321             super.onDrawerStateChanged(newState);
322             // Set transparent status bar when drawer starts to move.
323             if (newState != DrawerLayout.STATE_IDLE) {
324                 updateStatusBarBackground();
325             }
326         }
327     }
328 
329 
PeopleActivity()330     public PeopleActivity() {
331         mInstanceId = sNextInstanceId.getAndIncrement();
332         mIntentResolver = new ContactsIntentResolver(this);
333         mProviderStatusWatcher = ProviderStatusWatcher.getInstance(this);
334     }
335 
336     @Override
toString()337     public String toString() {
338         // Shown on logcat
339         return String.format("%s@%d", getClass().getSimpleName(), mInstanceId);
340     }
341 
areContactsAvailable()342     private boolean areContactsAvailable() {
343         return (mProviderStatus != null) && mProviderStatus.equals(ProviderStatus.STATUS_NORMAL);
344     }
345 
346     @Override
onCreate(Bundle savedState)347     protected void onCreate(Bundle savedState) {
348         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
349             Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
350         }
351 
352         // Make sure this is *before* calling super.onCreate
353         setTheme(R.style.PeopleActivityTheme);
354         super.onCreate(savedState);
355 
356         mAccountTypeManager = AccountTypeManager.getInstance(this);
357         mContactListFilterController = ContactListFilterController.getInstance(this);
358 
359         RequestPermissionsActivity.startPermissionActivityIfNeeded(this);
360 
361         if (!processIntent(false)) {
362             finish();
363             return;
364         }
365 
366         mContactListFilterController.checkFilterValidity(false);
367 
368         super.setContentView(R.layout.contacts_drawer_activity);
369 
370         // Set up the action bar.
371         mToolbar = getView(R.id.toolbar);
372         setSupportActionBar(mToolbar);
373 
374         // Add shadow under toolbar.
375         ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
376 
377         // Set up hamburger button.
378         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
379         mDrawerFragment = (DrawerFragment) getFragmentManager().findFragmentById(R.id.drawer);
380         mToggle = new ContactsActionBarDrawerToggle(this, mDrawerLayout, mToolbar,
381                 R.string.navigation_drawer_open, R.string.navigation_drawer_close);
382         mDrawerLayout.setDrawerListener(mToggle);
383         // Set fallback handler for when drawer is disabled.
384         mToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
385             @Override
386             public void onClick(View v) {
387                 onBackPressed();
388             }
389         });
390 
391         // Set up navigation mode.
392         if (savedState != null) {
393             mCurrentView = ContactsView.values()[savedState.getInt(KEY_CONTACTS_VIEW)];
394         } else {
395             mCurrentView = ContactsView.ALL_CONTACTS;
396         }
397 
398         if (savedState != null && savedState.containsKey(KEY_NEW_GROUP_ACCOUNT)) {
399             mNewGroupAccount = AccountWithDataSet.unstringify(
400                     savedState.getString(KEY_NEW_GROUP_ACCOUNT));
401         }
402 
403         mContactListFilterController.addListener(mFilterListener);
404         mProviderStatusWatcher.addListener(mProviderStatusListener);
405 
406         mIsRecreatedInstance = (savedState != null);
407 
408         if (mIsRecreatedInstance) {
409             mGroupUri = savedState.getParcelable(KEY_GROUP_URI);
410         }
411 
412         createViewsAndFragments();
413 
414         if (!PermissionsUtil.hasPermission(this, Manifest.permission.POST_NOTIFICATIONS)) {
415             requestPermissions(new String[] { Manifest.permission.POST_NOTIFICATIONS }, 1);
416         }
417 
418         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
419             Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
420         }
421         getWindow().setBackgroundDrawable(null);
422     }
423 
424     @SuppressWarnings("MissingSuperCall") // TODO: Fix me
425     @Override
onNewIntent(Intent intent)426     protected void onNewIntent(Intent intent) {
427         final String action = intent.getAction();
428         if (GroupUtil.ACTION_CREATE_GROUP.equals(action)) {
429             mGroupUri = intent.getData();
430             if (mGroupUri == null) {
431                 toast(R.string.groupSavedErrorToast);
432                 return;
433             }
434             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + mGroupUri);
435             switchView(ContactsView.GROUP_VIEW);
436             mMembersFragment.toastForSaveAction(action);
437             return;
438         }
439 
440         if (isGroupSaveAction(action)) {
441             mGroupUri = intent.getData();
442             if (mGroupUri == null) {
443                 popSecondLevel();
444                 toast(R.string.groupSavedErrorToast);
445                 return;
446             }
447             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + mGroupUri);
448             // ACTION_REMOVE_FROM_GROUP doesn't reload data, so it shouldn't cause b/32223934
449             // but it's necessary to use the previous fragment since
450             // GroupMembersFragment#mIsEditMode needs to be persisted between remove actions.
451             if (GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(action)) {
452                 switchToOrUpdateGroupView(action);
453             } else {
454                 switchView(ContactsView.GROUP_VIEW);
455             }
456             mMembersFragment.toastForSaveAction(action);
457         }
458 
459         setIntent(intent);
460 
461         if (!processIntent(true)) {
462             finish();
463             return;
464         }
465 
466         mContactListFilterController.checkFilterValidity(false);
467 
468         if (!isInSecondLevel()) {
469             // Re-initialize ActionBarAdapter because {@link #onNewIntent(Intent)} doesn't invoke
470             // {@link Fragment#onActivityCreated(Bundle)} where we initialize ActionBarAdapter
471             // initially.
472             mContactsListFragment.setParameters(/* ContactsRequest */ mRequest,
473                     /* fromOnNewIntent */ true);
474             mContactsListFragment.initializeActionBarAdapter(null);
475         }
476 
477         initializeFabVisibility();
478         invalidateOptionsMenuIfNeeded();
479     }
480 
isGroupSaveAction(String action)481     private static boolean isGroupSaveAction(String action) {
482         return GroupUtil.ACTION_UPDATE_GROUP.equals(action)
483                 || GroupUtil.ACTION_ADD_TO_GROUP.equals(action)
484                 || GroupUtil.ACTION_REMOVE_FROM_GROUP.equals(action);
485     }
486 
toast(int resId)487     private void toast(int resId) {
488         if (resId >= 0) {
489             Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();
490         }
491     }
492 
493     /**
494      * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect
495      * is needed.
496      *
497      * @param forNewIntent set true if it's called from {@link #onNewIntent(Intent)}.
498      * @return {@code true} if {@link PeopleActivity} should continue running.  {@code false}
499      *         if it shouldn't, in which case the caller should finish() itself and shouldn't do
500      *         farther initialization.
501      */
processIntent(boolean forNewIntent)502     private boolean processIntent(boolean forNewIntent) {
503         // Extract relevant information from the intent
504         mRequest = mIntentResolver.resolveIntent(getIntent());
505         if (Log.isLoggable(TAG, Log.DEBUG)) {
506             Log.d(TAG, this + " processIntent: forNewIntent=" + forNewIntent
507                     + " intent=" + getIntent() + " request=" + mRequest);
508         }
509         if (!mRequest.isValid()) {
510             setResult(RESULT_CANCELED);
511             return false;
512         }
513 
514         switch (mRequest.getActionCode()) {
515             case ContactsRequest.ACTION_VIEW_CONTACT: {
516                 ImplicitIntentsUtil.startQuickContact(
517                         this, mRequest.getContactUri(), ScreenType.UNKNOWN);
518                 return false;
519             }
520             case ContactsRequest.ACTION_INSERT_GROUP: {
521                 onCreateGroupMenuItemClicked();
522                 return true;
523             }
524             case ContactsRequest.ACTION_VIEW_GROUP:
525             case ContactsRequest.ACTION_EDIT_GROUP: {
526                 mShouldSwitchToGroupView = true;
527                 return true;
528             }
529         }
530         return true;
531     }
532 
createViewsAndFragments()533     private void createViewsAndFragments() {
534         setContentView(R.layout.people_activity);
535 
536         final FragmentManager fragmentManager = getFragmentManager();
537 
538         setUpListFragment(fragmentManager);
539 
540         mMembersFragment = (GroupMembersFragment) fragmentManager.findFragmentByTag(TAG_GROUP_VIEW);
541 
542         // Configure floating action button
543         mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
544         final ImageButton floatingActionButton
545                 = (ImageButton) findViewById(R.id.floating_action_button);
546         floatingActionButton.setOnClickListener(new View.OnClickListener() {
547             @Override
548             public void onClick(View v) {
549                 AccountFilterUtil.startEditorIntent(PeopleActivity.this, getIntent(),
550                         mContactListFilterController.getFilter());
551             }
552         });
553         mFloatingActionButtonController = new FloatingActionButtonController(this,
554                 mFloatingActionButtonContainer, floatingActionButton);
555 
556         invalidateOptionsMenuIfNeeded();
557 
558         mLayoutRoot = (CoordinatorLayout) findViewById(R.id.root);
559 
560         if (mShouldSwitchToGroupView && !mIsRecreatedInstance) {
561             mGroupUri = mRequest.getContactUri();
562             switchToOrUpdateGroupView(GroupUtil.ACTION_SWITCH_GROUP);
563             mShouldSwitchToGroupView = false;
564         }
565     }
566 
567     @Override
setContentView(@ayoutRes int layoutResID)568     public void setContentView(@LayoutRes int layoutResID) {
569         final ViewGroup parent = (ViewGroup) findViewById(R.id.content_frame);
570         if (parent != null) {
571             parent.removeAllViews();
572         }
573         LayoutInflater.from(this).inflate(layoutResID, parent);
574     }
575 
setUpListFragment(FragmentManager fragmentManager)576     private void setUpListFragment(FragmentManager fragmentManager) {
577         mContactsListFragment = (DefaultContactBrowseListFragment)
578                 fragmentManager.findFragmentByTag(TAG_ALL);
579 
580         if (mContactsListFragment == null) {
581             mContactsListFragment = new DefaultContactBrowseListFragment();
582             mContactsListFragment.setAnimateOnLoad(true);
583             fragmentManager.beginTransaction()
584                     .add(R.id.contacts_list_container, mContactsListFragment, TAG_ALL)
585                     .commit();
586             fragmentManager.executePendingTransactions();
587         }
588 
589         mContactsListFragment.setContactsAvailable(areContactsAvailable());
590         mContactsListFragment.setListType(mContactListFilterController.getFilterListType());
591         mContactsListFragment.setParameters(/* ContactsRequest */ mRequest,
592                 /* fromOnNewIntent */ false);
593     }
594 
595     @Override
onPause()596     protected void onPause() {
597         mProviderStatusWatcher.stop();
598 
599         LocalBroadcastManager.getInstance(this).unregisterReceiver(mSaveServiceListener);
600 
601         super.onPause();
602 
603         ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
604         onSyncStateUpdated();
605     }
606 
607     @SuppressWarnings("MissingSuperCall") // TODO: Fix me
608     @Override
onMultiWindowModeChanged(boolean entering)609     public void onMultiWindowModeChanged(boolean entering) {
610         initializeHomeVisibility();
611     }
612 
613     @Override
onResume()614     protected void onResume() {
615         super.onResume();
616 
617         if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
618             updateStatusBarBackground();
619         }
620 
621         if (mShouldSwitchToAllContacts) {
622             switchToAllContacts();
623         }
624 
625         mProviderStatusWatcher.start();
626         updateViewConfiguration(true);
627 
628         mStatusChangeListenerHandle = ContentResolver.addStatusChangeListener(
629                 ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
630                         | ContentResolver.SYNC_OBSERVER_TYPE_PENDING
631                         | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS,
632                 mSyncStatusObserver);
633         onSyncStateUpdated();
634 
635         initializeFabVisibility();
636         initializeHomeVisibility();
637 
638         mSaveServiceListener = new SaveServiceListener();
639         LocalBroadcastManager.getInstance(this).registerReceiver(mSaveServiceListener,
640                 new IntentFilter(ContactSaveService.BROADCAST_GROUP_DELETED));
641     }
642 
updateStatusBarBackground()643     public void updateStatusBarBackground() {
644         updateStatusBarBackground(/* color */ -1);
645     }
646 
updateStatusBarBackground(int color)647     public void updateStatusBarBackground(int color) {
648         if (!CompatUtils.isLollipopCompatible()) return;
649         if (color == -1) {
650             mDrawerLayout.setStatusBarBackgroundColor(
651                     MaterialColorMapUtils.getStatusBarColor(this));
652         } else {
653             mDrawerLayout.setStatusBarBackgroundColor(color);
654         }
655         mDrawerLayout.invalidate();
656         getWindow().setStatusBarColor(Color.TRANSPARENT);
657     }
658 
659     @Override
onDestroy()660     protected void onDestroy() {
661         mProviderStatusWatcher.removeListener(mProviderStatusListener);
662         mContactListFilterController.removeListener(mFilterListener);
663         super.onDestroy();
664     }
665 
initializeFabVisibility()666     private void initializeFabVisibility() {
667         mFloatingActionButtonContainer.setVisibility(shouldHideFab() ? View.GONE : View.VISIBLE);
668         mFloatingActionButtonController.resetIn();
669         wasLastFabAnimationScaleIn = !shouldHideFab();
670     }
671 
initializeHomeVisibility()672     private void initializeHomeVisibility() {
673         // Remove the navigation icon if we return to the fragment in a search or select state
674         if (getToolbar() != null && (isListFragmentInSelectionMode()
675                 || isListFragmentInSearchMode() || isGroupsFragmentInSelectionMode()
676                 || isGroupsFragmentInSearchMode())) {
677             getToolbar().setNavigationIcon(null);
678         }
679     }
680 
shouldHideFab()681     private boolean shouldHideFab() {
682         if (mContactsListFragment != null && mContactsListFragment.getActionBarAdapter() == null
683                 || isInSecondLevel()) {
684             return true;
685         }
686         return isListFragmentInSearchMode()
687                 || isListFragmentInSelectionMode();
688     }
689 
showFabWithAnimation(boolean showFab)690     public void showFabWithAnimation(boolean showFab) {
691         if (mFloatingActionButtonContainer == null) {
692             return;
693         }
694         if (showFab) {
695             if (!wasLastFabAnimationScaleIn) {
696                 mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
697                 mFloatingActionButtonController.scaleIn(0);
698             }
699             wasLastFabAnimationScaleIn = true;
700 
701         } else {
702             if (wasLastFabAnimationScaleIn) {
703                 mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
704                 mFloatingActionButtonController.scaleOut();
705             }
706             wasLastFabAnimationScaleIn = false;
707         }
708     }
709 
updateViewConfiguration(boolean forceUpdate)710     private void updateViewConfiguration(boolean forceUpdate) {
711         int providerStatus = mProviderStatusWatcher.getProviderStatus();
712         if (!forceUpdate && (mProviderStatus != null)
713                 && (mProviderStatus.equals(providerStatus))) return;
714         mProviderStatus = providerStatus;
715 
716         final FragmentManager fragmentManager= getFragmentManager();
717         final FragmentTransaction transaction = fragmentManager.beginTransaction();
718 
719         // Change in CP2's provider status may not take effect immediately, see b/30566908.
720         // So we need to handle the case where provider status is STATUS_EMPTY and there is
721         // actually at least one real account (not "local" account) on device.
722         if (shouldShowList()) {
723             if (mContactsListFragment != null) {
724                 final Fragment unavailableFragment = fragmentManager
725                         .findFragmentByTag(TAG_UNAVAILABLE);
726                 if (unavailableFragment != null) {
727                     transaction.remove(unavailableFragment);
728                 }
729                 if (mContactsListFragment.isHidden()) {
730                     transaction.show(mContactsListFragment);
731                 }
732                 mContactsListFragment.setContactsAvailable(areContactsAvailable());
733                 mContactsListFragment.setEnabled(true);
734             }
735         } else {
736             // Setting up the page so that the user can still use the app
737             // even without an account.
738             if (mContactsListFragment != null) {
739                 mContactsListFragment.setEnabled(false);
740             }
741             final ContactsUnavailableFragment fragment = new ContactsUnavailableFragment();
742             transaction.hide(mContactsListFragment);
743             transaction.replace(R.id.contacts_unavailable_container, fragment, TAG_UNAVAILABLE);
744             fragment.updateStatus(mProviderStatus);
745         }
746         if (!transaction.isEmpty()) {
747             transaction.commit();
748             fragmentManager.executePendingTransactions();
749         }
750 
751         invalidateOptionsMenuIfNeeded();
752     }
753 
shouldShowList()754     private boolean shouldShowList() {
755         return mProviderStatus != null
756                 && ((mProviderStatus.equals(ProviderStatus.STATUS_EMPTY)
757                 && mAccountTypeManager.hasNonLocalAccount())
758                 || mProviderStatus.equals(ProviderStatus.STATUS_NORMAL));
759     }
760 
invalidateOptionsMenuIfNeeded()761     private void invalidateOptionsMenuIfNeeded() {
762         if (mContactsListFragment != null
763                 && mContactsListFragment.getOptionsMenuContactsAvailable()
764                 != areContactsAvailable()) {
765             invalidateOptionsMenu();
766         }
767     }
768 
769     @Override
onKeyDown(int keyCode, KeyEvent event)770     public boolean onKeyDown(int keyCode, KeyEvent event) {
771         // If the drawer is open, consume KEYCODE_BACK event only.
772         if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
773             if (keyCode == KeyEvent.KEYCODE_BACK) {
774                 // Should eventually go to onBackPressed().
775                 return super.onKeyDown(keyCode, event);
776             }
777             return false;
778         }
779         // Bring up the search UI if the user starts typing
780         final int unicodeChar = event.getUnicodeChar();
781         if ((unicodeChar != 0)
782                 // If COMBINING_ACCENT is set, it's not a unicode character.
783                 && ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0)
784                 && !Character.isWhitespace(unicodeChar)) {
785             if (mContactsListFragment.onKeyDown(unicodeChar)) {
786                 return true;
787             }
788         }
789 
790         return super.onKeyDown(keyCode, event);
791     }
792 
793     @Override
onBackPressed()794     public void onBackPressed() {
795         if (!isSafeToCommitTransactions()) {
796             return;
797         }
798 
799         // Handle the back event in drawer first.
800         if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
801             closeDrawer();
802             return;
803         }
804 
805         // Handle the back event in "second level".
806         if (isGroupView()) {
807             onBackPressedGroupView();
808             return;
809         }
810 
811         if (isAssistantView()) {
812             onBackPressedAssistantView();
813             return;
814         }
815 
816         // If feature highlight is present, let it handle the back event before
817         // mContactsListFragment.
818         if (FeatureHighlightHelper.tryRemoveHighlight(this)) {
819             return;
820         }
821 
822         // Handle the back event in "first level" - mContactsListFragment.
823         if (maybeHandleInListFragment()) {
824             return;
825         }
826 
827         super.onBackPressed();
828     }
829 
onBackPressedGroupView()830     private void onBackPressedGroupView() {
831         if (mMembersFragment.isEditMode()) {
832             mMembersFragment.exitEditMode();
833         } else if (mMembersFragment.getActionBarAdapter().isSelectionMode()) {
834             mMembersFragment.getActionBarAdapter().setSelectionMode(false);
835             mMembersFragment.displayCheckBoxes(false);
836         } else if (mMembersFragment.getActionBarAdapter().isSearchMode()) {
837             mMembersFragment.getActionBarAdapter().setSearchMode(false);
838         } else {
839             switchToAllContacts();
840         }
841     }
842 
onBackPressedAssistantView()843     private void onBackPressedAssistantView() {
844         if (!isInThirdLevel()) {
845             switchToAllContacts();
846         } else {
847             setDrawerLockMode(/* enabled */ true);
848             super.onBackPressed();
849         }
850     }
851 
852     // Returns true if back event is handled in this method.
maybeHandleInListFragment()853     private boolean maybeHandleInListFragment() {
854         if (isListFragmentInSelectionMode()) {
855             mContactsListFragment.getActionBarAdapter().setSelectionMode(false);
856             return true;
857         }
858 
859         if (isListFragmentInSearchMode()) {
860             mContactsListFragment.getActionBarAdapter().setSearchMode(false);
861             if (mContactsListFragment.wasSearchResultClicked()) {
862                 mContactsListFragment.resetSearchResultClicked();
863             } else {
864                 Logger.logScreenView(this, ScreenType.SEARCH_EXIT);
865                 Logger.logSearchEvent(mContactsListFragment.createSearchState());
866             }
867             return true;
868         }
869 
870         if (!AccountFilterUtil.isAllContactsFilter(mContactListFilterController.getFilter())
871                 && !mContactsListFragment.isHidden()) {
872             // If mContactsListFragment is hidden, then mContactsUnavailableFragment is visible so we
873             // don't need to switch to all contacts.
874             switchToAllContacts();
875             return true;
876         }
877 
878         return false;
879     }
880 
isListFragmentInSelectionMode()881     private boolean isListFragmentInSelectionMode() {
882         return mContactsListFragment != null && mContactsListFragment.getActionBarAdapter() != null
883                 && mContactsListFragment.getActionBarAdapter().isSelectionMode();
884     }
885 
isListFragmentInSearchMode()886     private boolean isListFragmentInSearchMode() {
887         return mContactsListFragment != null && mContactsListFragment.getActionBarAdapter() != null
888                 && mContactsListFragment.getActionBarAdapter().isSearchMode();
889     }
890 
isGroupsFragmentInSelectionMode()891     private boolean isGroupsFragmentInSelectionMode() {
892         return mMembersFragment != null && mMembersFragment.getActionBarAdapter() != null
893                 && mMembersFragment.getActionBarAdapter().isSelectionMode();
894     }
895 
isGroupsFragmentInSearchMode()896     private boolean isGroupsFragmentInSearchMode() {
897         return mMembersFragment != null && mMembersFragment.getActionBarAdapter() != null
898                 && mMembersFragment.getActionBarAdapter().isSearchMode();
899     }
900 
901     @Override
onSaveInstanceState(Bundle outState)902     protected void onSaveInstanceState(Bundle outState) {
903         super.onSaveInstanceState(outState);
904         if (mNewGroupAccount != null) {
905             outState.putString(KEY_NEW_GROUP_ACCOUNT, mNewGroupAccount.stringify());
906         }
907         outState.putInt(KEY_CONTACTS_VIEW, mCurrentView.ordinal());
908         outState.putParcelable(KEY_GROUP_URI, mGroupUri);
909     }
910 
911     @Override
onRestoreInstanceState(Bundle savedInstanceState)912     protected void onRestoreInstanceState(Bundle savedInstanceState) {
913         super.onRestoreInstanceState(savedInstanceState);
914         mGroupUri = savedInstanceState.getParcelable(KEY_GROUP_URI);
915     }
916 
onGroupDeleted(final Intent intent)917     private void onGroupDeleted(final Intent intent) {
918         if (!ContactSaveService.canUndo(intent)) return;
919 
920         final AccessibilityManager am =
921                 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
922         //TODO set to INDEFINITE and track user interaction to dismiss b/33208886
923         final int accessibilityLength = 15000;
924         final int length = am.isEnabled() ? accessibilityLength : Snackbar.LENGTH_LONG;
925         final String message = getString(R.string.groupDeletedToast);
926 
927         final Snackbar snackbar = Snackbar.make(mLayoutRoot, message, length)
928                 .setAction(R.string.undo, new View.OnClickListener() {
929                     @Override
930                     public void onClick(View v) {
931                         ContactSaveService.startService(PeopleActivity.this,
932                                 ContactSaveService.createUndoIntent(PeopleActivity.this, intent));
933                     }
934                 }).setActionTextColor(ContextCompat.getColor(this, R.color.snackbar_action_text));
935 
936         // Announce for a11y talkback
937         mLayoutRoot.announceForAccessibility(message);
938         mLayoutRoot.announceForAccessibility(getString(R.string.undo));
939 
940         snackbar.show();
941     }
942 
943     private class SaveServiceListener extends BroadcastReceiver {
944         @Override
onReceive(Context context, Intent intent)945         public void onReceive(Context context, Intent intent) {
946             switch (intent.getAction()) {
947                 case ContactSaveService.BROADCAST_GROUP_DELETED:
948                     onGroupDeleted(intent);
949                     break;
950             }
951         }
952     }
953 
onGroupMenuItemClicked(long groupId)954     private void onGroupMenuItemClicked(long groupId) {
955         if (isGroupView() && mMembersFragment != null
956                 && mMembersFragment.isCurrentGroup(groupId)) {
957             return;
958         }
959         mGroupUri = ContentUris.withAppendedId(ContactsContract.Groups.CONTENT_URI, groupId);
960         switchToOrUpdateGroupView(GroupUtil.ACTION_SWITCH_GROUP);
961     }
962 
onFilterMenuItemClicked(Intent intent)963     private void onFilterMenuItemClicked(Intent intent) {
964         // We must pop second level first to "restart" mContactsListFragment before changing filter.
965         if (isInSecondLevel()) {
966             popSecondLevel();
967             showFabWithAnimation(/* showFab */ true);
968             // HACK: swap the current filter to force listeners to update because the group
969             // member view no longer changes the filter. Fix for b/32223767
970             final ContactListFilter current = mContactListFilterController.getFilter();
971             mContactListFilterController.setContactListFilter(
972                     AccountFilterUtil.createContactsFilter(this), false);
973             mContactListFilterController.setContactListFilter(current, false);
974         }
975         mCurrentView = ContactsView.ACCOUNT_VIEW;
976         AccountFilterUtil.handleAccountFilterResult(mContactListFilterController,
977                 AppCompatActivity.RESULT_OK, intent);
978     }
979 
switchToOrUpdateGroupView(String action)980     private void switchToOrUpdateGroupView(String action) {
981         // If group fragment is active and visible, we simply update it.
982         if (mMembersFragment != null && !mMembersFragment.isInactive()) {
983             mMembersFragment.updateExistingGroupFragment(mGroupUri, action);
984         } else {
985             switchView(ContactsView.GROUP_VIEW);
986         }
987     }
988 
launchAssistant()989     protected void launchAssistant() {
990         switchView(ContactsView.ASSISTANT);
991     }
992 
switchView(ContactsView contactsView)993     private void switchView(ContactsView contactsView) {
994         mCurrentView = contactsView;
995 
996         final FragmentManager fragmentManager =  getFragmentManager();
997         final FragmentTransaction transaction = fragmentManager.beginTransaction();
998         popSecondLevel();
999         if (isGroupView()) {
1000             mMembersFragment = GroupMembersFragment.newInstance(mGroupUri);
1001             transaction.replace(
1002                     R.id.contacts_list_container, mMembersFragment, TAG_GROUP_VIEW);
1003         } else if (isAssistantView()) {
1004             Fragment uiFragment = fragmentManager.findFragmentByTag(TAG_ASSISTANT);
1005             Fragment unavailableFragment = fragmentManager.findFragmentByTag(TAG_UNAVAILABLE);
1006             if (uiFragment == null) {
1007                 uiFragment = ObjectFactory.getAssistantFragment();
1008             }
1009             if (unavailableFragment != null) {
1010                 transaction.remove(unavailableFragment);
1011             }
1012             transaction.replace(R.id.contacts_list_container, uiFragment, TAG_ASSISTANT);
1013             resetToolBarStatusBarColor();
1014         }
1015         transaction.addToBackStack(TAG_SECOND_LEVEL);
1016         transaction.commit();
1017         fragmentManager.executePendingTransactions();
1018 
1019         showFabWithAnimation(/* showFab */ false);
1020     }
1021 
switchToAllContacts()1022     public void switchToAllContacts() {
1023         popSecondLevel();
1024         mShouldSwitchToAllContacts = false;
1025         mCurrentView = ContactsView.ALL_CONTACTS;
1026         mDrawerFragment.setNavigationItemChecked(ContactsView.ALL_CONTACTS);
1027         showFabWithAnimation(/* showFab */ true);
1028         mContactsListFragment.scrollToTop();
1029         resetFilter();
1030         setTitle(getString(R.string.contactsList));
1031     }
1032 
resetFilter()1033     private void resetFilter() {
1034         final Intent intent = new Intent();
1035         final ContactListFilter filter = AccountFilterUtil.createContactsFilter(this);
1036         intent.putExtra(AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER, filter);
1037         AccountFilterUtil.handleAccountFilterResult(
1038                 mContactListFilterController, AppCompatActivity.RESULT_OK, intent);
1039     }
1040 
1041     // Reset toolbar and status bar color to Contacts theme color.
resetToolBarStatusBarColor()1042     private void resetToolBarStatusBarColor() {
1043         findViewById(R.id.toolbar_frame).setBackgroundColor(
1044                 ContextCompat.getColor(this, R.color.primary_color));
1045         updateStatusBarBackground(ContextCompat.getColor(this, R.color.primary_color_dark));
1046     }
1047 
getListFragment()1048     protected DefaultContactBrowseListFragment getListFragment() {
1049         return mContactsListFragment;
1050     }
1051 
getGroupFragment()1052     protected GroupMembersFragment getGroupFragment() {
1053         return mMembersFragment;
1054     }
1055 
handleFilterChangeForFragment(ContactListFilter filter)1056     private void handleFilterChangeForFragment(ContactListFilter filter) {
1057         if (mContactsListFragment.canSetActionBar()) {
1058             mContactsListFragment.setFilterAndUpdateTitle(filter);
1059             // Scroll to top after filter is changed.
1060             mContactsListFragment.scrollToTop();
1061         }
1062     }
1063 
handleFilterChangeForActivity(ContactListFilter filter)1064     private void handleFilterChangeForActivity(ContactListFilter filter) {
1065         // The filter was changed while this activity was in the background. If we're in the
1066         // assistant view Switch to the main contacts list when we resume to prevent
1067         // b/31838582 and b/31829161
1068         // TODO: this is a hack; we need to do some cleanup of the contact list filter stuff
1069         if (isAssistantView() && filter.isContactsFilterType()) {
1070             mShouldSwitchToAllContacts = true;
1071         }
1072 
1073         if (CompatUtils.isNCompatible()) {
1074             getWindow().getDecorView()
1075                     .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1076         }
1077         invalidateOptionsMenu();
1078     }
1079 
updateDrawerGroupMenu(long groupId)1080     public void updateDrawerGroupMenu(long groupId) {
1081         if (mDrawerFragment != null) {
1082             mDrawerFragment.updateGroupMenu(groupId);
1083         }
1084     }
1085 
setDrawerLockMode(boolean enabled)1086     public void setDrawerLockMode(boolean enabled) {
1087         // Prevent drawer from being opened by sliding from the start of screen.
1088         mDrawerLayout.setDrawerLockMode(enabled ? DrawerLayout.LOCK_MODE_UNLOCKED
1089                 : DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
1090 
1091         // Order of these statements matter.
1092         // Display back button and disable drawer indicator.
1093         if (enabled) {
1094             getSupportActionBar().setDisplayHomeAsUpEnabled(false);
1095             mToggle.setDrawerIndicatorEnabled(true);
1096         } else {
1097             mToggle.setDrawerIndicatorEnabled(false);
1098             getSupportActionBar().setDisplayHomeAsUpEnabled(true);
1099         }
1100     }
1101 
getToolbar()1102     public Toolbar getToolbar() {
1103         return mToolbar;
1104     }
1105 
1106     @Override
onPostCreate(Bundle savedInstanceState)1107     protected void onPostCreate(Bundle savedInstanceState) {
1108         super.onPostCreate(savedInstanceState);
1109         mToggle.syncState();
1110     }
1111 
1112     @Override
onConfigurationChanged(Configuration newConfig)1113     public void onConfigurationChanged(Configuration newConfig) {
1114         super.onConfigurationChanged(newConfig);
1115         mToggle.onConfigurationChanged(newConfig);
1116     }
1117 
onCreateGroupMenuItemClicked()1118     protected void onCreateGroupMenuItemClicked() {
1119         // Select the account to create the group
1120         final Bundle extras = getIntent().getExtras();
1121         final Account account = extras == null ? null :
1122                 (Account) extras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
1123         if (account == null) {
1124             selectAccountForNewGroup();
1125         } else {
1126             final String dataSet = extras == null
1127                     ? null : extras.getString(Intents.Insert.EXTRA_DATA_SET);
1128             final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
1129                     account.name, account.type, dataSet);
1130             onAccountChosen(accountWithDataSet, /* extraArgs */ null);
1131         }
1132     }
1133 
selectAccountForNewGroup()1134     private void selectAccountForNewGroup() {
1135         // This should never block because the DrawerFragment loads the accounts and the
1136         // "Create Label" item only exists when that loading finishes
1137         final List<AccountInfo> accounts = Futures.getUnchecked(AccountTypeManager.getInstance(this)
1138                 .filterAccountsAsync(AccountTypeManager.AccountFilter.GROUPS_WRITABLE));
1139         if (accounts.isEmpty()) {
1140             // We shouldn't present the add group button if there are no writable accounts
1141             // but check it since it's possible we are started with an Intent.
1142             Toast.makeText(this, R.string.groupCreateFailedToast, Toast.LENGTH_SHORT).show();
1143             return;
1144         }
1145         // If there is a single writable account, use it w/o showing a dialog.
1146         if (accounts.size() == 1) {
1147             onAccountChosen(accounts.get(0).getAccount(), /* extraArgs */ null);
1148             return;
1149         }
1150         SelectAccountDialogFragment.show(getFragmentManager(), R.string.dialog_new_group_account,
1151                 AccountTypeManager.AccountFilter.GROUPS_WRITABLE, /* extraArgs */ null,
1152                 TAG_SELECT_ACCOUNT_DIALOG);
1153     }
1154 
1155     // Implementation of SelectAccountDialogFragment.Listener
1156     @Override
onAccountChosen(AccountWithDataSet account, Bundle extraArgs)1157     public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
1158         mNewGroupAccount = account;
1159         GroupNameEditDialogFragment.newInstanceForCreation(
1160                 mNewGroupAccount, GroupUtil.ACTION_CREATE_GROUP)
1161                 .show(getFragmentManager(), TAG_GROUP_NAME_EDIT_DIALOG);
1162     }
1163 
1164     @Override
onAccountSelectorCancelled()1165     public void onAccountSelectorCancelled() {
1166     }
1167 
1168     // Implementation of DrawerFragmentListener
1169     @Override
onDrawerItemClicked()1170     public void onDrawerItemClicked(){
1171         closeDrawer();
1172     }
1173 
1174     @Override
onContactsViewSelected(ContactsView mode)1175     public void onContactsViewSelected(ContactsView mode) {
1176         if (mode == ContactsView.ALL_CONTACTS) {
1177             switchToAllContacts();
1178         } else if (mode == ContactsView.ASSISTANT) {
1179             launchAssistant();
1180         } else {
1181             throw new IllegalStateException("Unknown view " + mode);
1182         }
1183     }
1184 
1185     @Override
onCreateLabelButtonClicked()1186     public void onCreateLabelButtonClicked() {
1187         onCreateGroupMenuItemClicked();
1188     }
1189 
1190     @Override
onOpenSettings()1191     public void onOpenSettings() {
1192         new Handler().postDelayed(new Runnable() {
1193             @Override
1194             public void run() {
1195                 startActivity(createPreferenceIntent());
1196             }
1197         }, DRAWER_CLOSE_DELAY);
1198     }
1199 
1200     @Override
onLaunchHelpFeedback()1201     public void onLaunchHelpFeedback() {
1202         HelpUtils.launchHelpAndFeedbackForMainScreen(this);
1203     }
1204 
1205     @Override
onGroupViewSelected(GroupListItem groupListItem)1206     public void onGroupViewSelected(GroupListItem groupListItem) {
1207         onGroupMenuItemClicked(groupListItem.getGroupId());
1208     }
1209 
1210     @Override
onAccountViewSelected(ContactListFilter filter)1211     public void onAccountViewSelected(ContactListFilter filter) {
1212         final Intent intent = new Intent();
1213         intent.putExtra(AccountFilterActivity.EXTRA_CONTACT_LIST_FILTER, filter);
1214         onFilterMenuItemClicked(intent);
1215     }
1216 
isGroupView()1217     public boolean isGroupView() {
1218         return mCurrentView == ContactsView.GROUP_VIEW;
1219     }
1220 
isAssistantView()1221     protected boolean isAssistantView() {
1222         return mCurrentView == ContactsView.ASSISTANT;
1223     }
1224 
isAllContactsView()1225     protected boolean isAllContactsView() {
1226         return mCurrentView == ContactsView.ALL_CONTACTS;
1227     }
1228 
isAccountView()1229     protected boolean isAccountView() {
1230         return mCurrentView == ContactsView.ACCOUNT_VIEW;
1231     }
1232 
isInSecondLevel()1233     public boolean isInSecondLevel() {
1234         return isGroupView() || isAssistantView();
1235     }
1236 
isInThirdLevel()1237     private boolean isInThirdLevel() {
1238         return isLastBackStackTag(TAG_THIRD_LEVEL);
1239     }
1240 
isLastBackStackTag(String tag)1241     private boolean isLastBackStackTag(String tag) {
1242         final int count = getFragmentManager().getBackStackEntryCount();
1243         if (count > 0) {
1244             final FragmentManager.BackStackEntry last =
1245                     getFragmentManager().getBackStackEntryAt(count - 1);
1246             if (tag == null) {
1247                 return last.getName() == null;
1248             }
1249             return tag.equals(last.getName());
1250         }
1251         return false;
1252     }
1253 
popSecondLevel()1254     private void popSecondLevel() {
1255         getFragmentManager().popBackStackImmediate(
1256                 TAG_ASSISTANT_HELPER, FragmentManager.POP_BACK_STACK_INCLUSIVE);
1257         getFragmentManager().popBackStackImmediate(
1258                 TAG_SECOND_LEVEL, FragmentManager.POP_BACK_STACK_INCLUSIVE);
1259         mMembersFragment = null;
1260         resetToolBarStatusBarColor();
1261     }
1262 
closeDrawer()1263     public void closeDrawer() {
1264         mDrawerLayout.closeDrawer(GravityCompat.START);
1265     }
1266 
createPreferenceIntent()1267     private Intent createPreferenceIntent() {
1268         final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
1269         intent.putExtra(ContactsPreferenceActivity.EXTRA_NEW_LOCAL_PROFILE,
1270                 ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE);
1271         return intent;
1272     }
1273 
1274 
1275 }
1276