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