summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/contactsfragment/ContactsFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/contactsfragment/ContactsFragment.java')
-rw-r--r--java/com/android/dialer/contactsfragment/ContactsFragment.java227
1 files changed, 205 insertions, 22 deletions
diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java
index ea662fc89..ddf00b358 100644
--- a/java/com/android/dialer/contactsfragment/ContactsFragment.java
+++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java
@@ -19,60 +19,194 @@ package com.android.dialer.contactsfragment;
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Loader;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
+import android.support.v13.app.FragmentCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Recycler;
+import android.support.v7.widget.RecyclerView.State;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnScrollChangeListener;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.common.preference.ContactsPreferences.ChangeListener;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.performancereport.PerformanceReport;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.IntentUtil;
import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.widget.EmptyContentView;
+import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
/** Fragment containing a list of all contacts. */
public class ContactsFragment extends Fragment
- implements LoaderCallbacks<Cursor>, OnScrollChangeListener {
+ implements LoaderCallbacks<Cursor>,
+ OnScrollChangeListener,
+ OnEmptyViewActionButtonClickedListener,
+ ChangeListener {
+ /** IntDef to define the OnClick action for contact rows. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ClickAction.INVALID, ClickAction.OPEN_CONTACT_CARD})
+ public @interface ClickAction {
+ int INVALID = 0;
+ /** Open contact card on click. */
+ int OPEN_CONTACT_CARD = 1;
+ }
+
+ /** An enum for the different types of headers that be inserted at position 0 in the list. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Header.NONE, Header.ADD_CONTACT})
+ public @interface Header {
+ int NONE = 0;
+ /** Header that allows the user to add a new contact. */
+ int ADD_CONTACT = 1;
+ }
+
+ public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
+
+ private static final String EXTRA_HEADER = "extra_header";
+ private static final String EXTRA_CLICK_ACTION = "extra_click_action";
+
+ private FastScroller fastScroller;
private TextView anchoredHeader;
private RecyclerView recyclerView;
private LinearLayoutManager manager;
private ContactsAdapter adapter;
+ private EmptyContentView emptyContentView;
+
+ private ContactsPreferences contactsPrefs;
+ private @Header int header;
+ private @ClickAction int clickAction;
+
+ /**
+ * Used to get a configured instance of ContactsFragment.
+ *
+ * <p>Current example of this fragment are the contacts tab and in creating a new favorite
+ * contact. For example, the contacts tab we use:
+ *
+ * <ul>
+ * <li>{@link Header#ADD_CONTACT} to insert a header that allows users to add a contact
+ * <li>{@link ClickAction#OPEN_CONTACT_CARD} to open contact cards on click
+ * </ul>
+ *
+ * And for the add favorite contact screen we might use:
+ *
+ * <ul>
+ * <li>{@link Header#NONE} so that all rows are contacts (i.e. no header inserted)
+ * <li>{@link ClickAction#SET_RESULT_AND_FINISH} to send a selected contact to the previous
+ * activity.
+ * </ul>
+ *
+ * @param header determines the type of header inserted at position 0 in the contacts list
+ * @param clickAction defines the on click actions on rows that represent contacts
+ */
+ public static ContactsFragment newInstance(@Header int header, @ClickAction int clickAction) {
+ Assert.checkArgument(clickAction != ClickAction.INVALID, "Invalid click action");
+ ContactsFragment fragment = new ContactsFragment();
+ Bundle args = new Bundle();
+ args.putInt(EXTRA_HEADER, header);
+ args.putInt(EXTRA_CLICK_ACTION, clickAction);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @SuppressWarnings("WrongConstant")
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ contactsPrefs = new ContactsPreferences(getContext());
+ contactsPrefs.registerChangeListener(this);
+ header = getArguments().getInt(EXTRA_HEADER);
+ clickAction = getArguments().getInt(EXTRA_CLICK_ACTION);
+ }
@Nullable
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_contacts, container, false);
- anchoredHeader = (TextView) view.findViewById(R.id.header);
- manager = new LinearLayoutManager(getContext());
+ fastScroller = view.findViewById(R.id.fast_scroller);
+ anchoredHeader = view.findViewById(R.id.header);
+ recyclerView = view.findViewById(R.id.recycler_view);
- // TODO: Handle contacts permission denied view
- // TODO: Handle 0 contacts layout
- recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
- recyclerView.setLayoutManager(manager);
- getLoaderManager().initLoader(0, null, this);
+ emptyContentView = view.findViewById(R.id.empty_list_view);
+ emptyContentView.setImage(R.drawable.empty_contacts);
+ emptyContentView.setActionClickedListener(this);
if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
getLoaderManager().initLoader(0, null, this);
+ } else {
+ emptyContentView.setDescription(R.string.permission_no_contacts);
+ emptyContentView.setActionLabel(R.string.permission_single_turn_on);
+ emptyContentView.setVisibility(View.VISIBLE);
}
return view;
}
@Override
+ public void onChange() {
+ if (getActivity() != null && isAdded()) {
+ getLoaderManager().restartLoader(0, null, this);
+ }
+ }
+
+ /** @return a loader according to sort order and display order. */
+ @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return new ContactsCursorLoader(getContext());
+ boolean sortOrderPrimary =
+ (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY);
+ boolean displayOrderPrimary =
+ (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY);
+
+ String sortKey = sortOrderPrimary ? Contacts.SORT_KEY_PRIMARY : Contacts.SORT_KEY_ALTERNATIVE;
+ return displayOrderPrimary
+ ? ContactsCursorLoader.createInstanceDisplayNamePrimary(getContext(), sortKey)
+ : ContactsCursorLoader.createInstanceDisplayNameAlternative(getContext(), sortKey);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- // TODO setup fast scroller.
- adapter = new ContactsAdapter(getContext(), cursor);
- recyclerView.setAdapter(adapter);
- if (adapter.getItemCount() > 1) {
- recyclerView.setOnScrollChangeListener(this);
+ if (cursor.getCount() == 0) {
+ emptyContentView.setDescription(R.string.all_contacts_empty);
+ emptyContentView.setActionLabel(R.string.all_contacts_empty_add_contact_action);
+ emptyContentView.setVisibility(View.VISIBLE);
+ recyclerView.setVisibility(View.GONE);
+ } else {
+ emptyContentView.setVisibility(View.GONE);
+ recyclerView.setVisibility(View.VISIBLE);
+ adapter = new ContactsAdapter(getContext(), cursor, header, clickAction);
+ manager =
+ new LinearLayoutManager(getContext()) {
+ @Override
+ public void onLayoutChildren(Recycler recycler, State state) {
+ super.onLayoutChildren(recycler, state);
+ int itemsShown = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1;
+ if (adapter.getItemCount() > itemsShown) {
+ fastScroller.setVisibility(View.VISIBLE);
+ recyclerView.setOnScrollChangeListener(ContactsFragment.this);
+ } else {
+ fastScroller.setVisibility(View.GONE);
+ }
+ }
+ };
+
+ recyclerView.setLayoutManager(manager);
+ recyclerView.setAdapter(adapter);
+ PerformanceReport.logOnScrollStateChange(recyclerView);
+ fastScroller.setup(adapter, manager);
}
}
@@ -81,6 +215,7 @@ public class ContactsFragment extends Fragment
recyclerView.setAdapter(null);
recyclerView.setOnScrollChangeListener(null);
adapter = null;
+ contactsPrefs.unregisterChangeListener();
}
/*
@@ -95,8 +230,14 @@ public class ContactsFragment extends Fragment
*/
@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+ fastScroller.updateContainerAndScrollBarPosition(recyclerView);
int firstVisibleItem = manager.findFirstVisibleItemPosition();
int firstCompletelyVisible = manager.findFirstCompletelyVisibleItemPosition();
+ if (firstCompletelyVisible == RecyclerView.NO_POSITION) {
+ // No items are visible, so there are no headers to update.
+ return;
+ }
+ String anchoredHeaderString = adapter.getHeaderString(firstCompletelyVisible);
// If the user swipes to the top of the list very quickly, there is some strange behavior
// between this method updating headers and adapter#onBindViewHolder updating headers.
@@ -104,15 +245,57 @@ public class ContactsFragment extends Fragment
if (firstVisibleItem == firstCompletelyVisible && firstVisibleItem == 0) {
adapter.refreshHeaders();
anchoredHeader.setVisibility(View.INVISIBLE);
+ } else if (firstVisibleItem != 0) { // skip the add contact row
+ if (adapter.getHeaderString(firstVisibleItem).equals(anchoredHeaderString)) {
+ anchoredHeader.setText(anchoredHeaderString);
+ anchoredHeader.setVisibility(View.VISIBLE);
+ getContactHolder(firstVisibleItem).getHeaderView().setVisibility(View.INVISIBLE);
+ getContactHolder(firstCompletelyVisible).getHeaderView().setVisibility(View.INVISIBLE);
+ } else {
+ anchoredHeader.setVisibility(View.INVISIBLE);
+ getContactHolder(firstVisibleItem).getHeaderView().setVisibility(View.VISIBLE);
+ getContactHolder(firstCompletelyVisible).getHeaderView().setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ private ContactViewHolder getContactHolder(int position) {
+ return ((ContactViewHolder) recyclerView.findViewHolderForAdapterPosition(position));
+ }
+
+ @Override
+ public void onEmptyViewActionButtonClicked() {
+ if (emptyContentView.getActionLabel() == R.string.permission_single_turn_on) {
+ String[] deniedPermissions =
+ PermissionsUtil.getPermissionsCurrentlyDenied(
+ getContext(), PermissionsUtil.allContactsGroupPermissionsUsedInDialer);
+ if (deniedPermissions.length > 0) {
+ LogUtil.i(
+ "ContactsFragment.onEmptyViewActionButtonClicked",
+ "Requesting permissions: " + Arrays.toString(deniedPermissions));
+ FragmentCompat.requestPermissions(
+ this, deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
+ }
+
+ } else if (emptyContentView.getActionLabel()
+ == R.string.all_contacts_empty_add_contact_action) {
+ // Add new contact
+ DialerUtils.startActivityWithErrorToast(
+ getContext(), IntentUtil.getNewContactIntent(), R.string.add_contact_not_available);
} else {
- boolean showAnchor =
- adapter.getHeader(firstVisibleItem).equals(adapter.getHeader(firstCompletelyVisible));
- anchoredHeader.setText(adapter.getHeader(firstCompletelyVisible));
- anchoredHeader.setVisibility(showAnchor ? View.VISIBLE : View.INVISIBLE);
-
- int rowHeaderVisibility = showAnchor ? View.INVISIBLE : View.VISIBLE;
- adapter.setHeaderVisibility(firstVisibleItem, rowHeaderVisibility);
- adapter.setHeaderVisibility(firstCompletelyVisible, rowHeaderVisibility);
+ throw Assert.createIllegalStateFailException("Invalid empty content view action label.");
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, String[] permissions, int[] grantResults) {
+ if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) {
+ if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
+ // Force a refresh of the data since we were missing the permission before this.
+ emptyContentView.setVisibility(View.GONE);
+ getLoaderManager().initLoader(0, null, this);
+ }
}
}
}