• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.dialer.contactsfragment;
18 
19 import android.app.Fragment;
20 import android.app.LoaderManager.LoaderCallbacks;
21 import android.content.Loader;
22 import android.content.pm.PackageManager;
23 import android.database.Cursor;
24 import android.os.Bundle;
25 import android.provider.ContactsContract.Contacts;
26 import android.support.annotation.Nullable;
27 import android.support.v13.app.FragmentCompat;
28 import android.support.v7.widget.LinearLayoutManager;
29 import android.support.v7.widget.RecyclerView;
30 import android.support.v7.widget.RecyclerView.Recycler;
31 import android.support.v7.widget.RecyclerView.State;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.View.OnScrollChangeListener;
35 import android.view.ViewGroup;
36 import android.widget.TextView;
37 import com.android.contacts.common.preference.ContactsPreferences;
38 import com.android.contacts.common.preference.ContactsPreferences.ChangeListener;
39 import com.android.dialer.common.Assert;
40 import com.android.dialer.common.LogUtil;
41 import com.android.dialer.performancereport.PerformanceReport;
42 import com.android.dialer.util.DialerUtils;
43 import com.android.dialer.util.IntentUtil;
44 import com.android.dialer.util.PermissionsUtil;
45 import com.android.dialer.widget.EmptyContentView;
46 import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
47 import java.util.Arrays;
48 
49 /** Fragment containing a list of all contacts. */
50 public class ContactsFragment extends Fragment
51     implements LoaderCallbacks<Cursor>,
52         OnScrollChangeListener,
53         OnEmptyViewActionButtonClickedListener,
54         ChangeListener {
55 
56   public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1;
57 
58   private FastScroller fastScroller;
59   private TextView anchoredHeader;
60   private RecyclerView recyclerView;
61   private LinearLayoutManager manager;
62   private ContactsAdapter adapter;
63   private EmptyContentView emptyContentView;
64 
65   private ContactsPreferences contactsPrefs;
66 
67   @Override
onCreate(@ullable Bundle savedInstanceState)68   public void onCreate(@Nullable Bundle savedInstanceState) {
69     super.onCreate(savedInstanceState);
70     contactsPrefs = new ContactsPreferences(getContext());
71     contactsPrefs.registerChangeListener(this);
72   }
73 
74   @Nullable
75   @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)76   public View onCreateView(
77       LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
78     View view = inflater.inflate(R.layout.fragment_contacts, container, false);
79     fastScroller = view.findViewById(R.id.fast_scroller);
80     anchoredHeader = view.findViewById(R.id.header);
81     recyclerView = view.findViewById(R.id.recycler_view);
82 
83     emptyContentView = view.findViewById(R.id.empty_list_view);
84     emptyContentView.setImage(R.drawable.empty_contacts);
85     emptyContentView.setActionClickedListener(this);
86 
87     if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
88       getLoaderManager().initLoader(0, null, this);
89     } else {
90       emptyContentView.setDescription(R.string.permission_no_contacts);
91       emptyContentView.setActionLabel(R.string.permission_single_turn_on);
92       emptyContentView.setVisibility(View.VISIBLE);
93     }
94 
95     return view;
96   }
97 
98   @Override
onChange()99   public void onChange() {
100     if (getActivity() != null && isAdded()) {
101       getLoaderManager().restartLoader(0, null, this);
102     }
103   }
104 
105   /** @return a loader according to sort order and display order. */
106   @Override
onCreateLoader(int id, Bundle args)107   public Loader<Cursor> onCreateLoader(int id, Bundle args) {
108     boolean sortOrderPrimary =
109         (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY);
110     boolean displayOrderPrimary =
111         (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY);
112 
113     String sortKey = sortOrderPrimary ? Contacts.SORT_KEY_PRIMARY : Contacts.SORT_KEY_ALTERNATIVE;
114     return displayOrderPrimary
115         ? ContactsCursorLoader.createInstanceDisplayNamePrimary(getContext(), sortKey)
116         : ContactsCursorLoader.createInstanceDisplayNameAlternative(getContext(), sortKey);
117   }
118 
119   @Override
onLoadFinished(Loader<Cursor> loader, Cursor cursor)120   public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
121     if (cursor.getCount() == 0) {
122       emptyContentView.setDescription(R.string.all_contacts_empty);
123       emptyContentView.setActionLabel(R.string.all_contacts_empty_add_contact_action);
124       emptyContentView.setVisibility(View.VISIBLE);
125       recyclerView.setVisibility(View.GONE);
126     } else {
127       emptyContentView.setVisibility(View.GONE);
128       recyclerView.setVisibility(View.VISIBLE);
129       adapter = new ContactsAdapter(getContext(), cursor);
130       manager =
131           new LinearLayoutManager(getContext()) {
132             @Override
133             public void onLayoutChildren(Recycler recycler, State state) {
134               super.onLayoutChildren(recycler, state);
135               int itemsShown = findLastVisibleItemPosition() - findFirstVisibleItemPosition() + 1;
136               if (adapter.getItemCount() > itemsShown) {
137                 fastScroller.setVisibility(View.VISIBLE);
138                 recyclerView.setOnScrollChangeListener(ContactsFragment.this);
139               } else {
140                 fastScroller.setVisibility(View.GONE);
141               }
142             }
143           };
144 
145       recyclerView.setLayoutManager(manager);
146       recyclerView.setAdapter(adapter);
147       PerformanceReport.logOnScrollStateChange(recyclerView);
148       fastScroller.setup(adapter, manager);
149     }
150   }
151 
152   @Override
onLoaderReset(Loader<Cursor> loader)153   public void onLoaderReset(Loader<Cursor> loader) {
154     recyclerView.setAdapter(null);
155     recyclerView.setOnScrollChangeListener(null);
156     adapter = null;
157     contactsPrefs.unregisterChangeListener();
158   }
159 
160   /*
161    * When our recycler view updates, we need to ensure that our row headers and anchored header
162    * are in the correct state.
163    *
164    * The general rule is, when the row headers are shown, our anchored header is hidden. When the
165    * recycler view is scrolling through a sublist that has more than one element, we want to show
166    * out anchored header, to create the illusion that our row header has been anchored. In all
167    * other situations, we want to hide the anchor because that means we are transitioning between
168    * two sublists.
169    */
170   @Override
onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY)171   public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
172     fastScroller.updateContainerAndScrollBarPosition(recyclerView);
173     int firstVisibleItem = manager.findFirstVisibleItemPosition();
174     int firstCompletelyVisible = manager.findFirstCompletelyVisibleItemPosition();
175     if (firstCompletelyVisible == RecyclerView.NO_POSITION) {
176       // No items are visible, so there are no headers to update.
177       return;
178     }
179     String anchoredHeaderString = adapter.getHeaderString(firstCompletelyVisible);
180 
181     // If the user swipes to the top of the list very quickly, there is some strange behavior
182     // between this method updating headers and adapter#onBindViewHolder updating headers.
183     // To overcome this, we refresh the headers to ensure they are correct.
184     if (firstVisibleItem == firstCompletelyVisible && firstVisibleItem == 0) {
185       adapter.refreshHeaders();
186       anchoredHeader.setVisibility(View.INVISIBLE);
187     } else if (firstVisibleItem != 0) { // skip the add contact row
188       if (adapter.getHeaderString(firstVisibleItem).equals(anchoredHeaderString)) {
189         anchoredHeader.setText(anchoredHeaderString);
190         anchoredHeader.setVisibility(View.VISIBLE);
191         getContactHolder(firstVisibleItem).getHeaderView().setVisibility(View.INVISIBLE);
192         getContactHolder(firstCompletelyVisible).getHeaderView().setVisibility(View.INVISIBLE);
193       } else {
194         anchoredHeader.setVisibility(View.INVISIBLE);
195         getContactHolder(firstVisibleItem).getHeaderView().setVisibility(View.VISIBLE);
196         getContactHolder(firstCompletelyVisible).getHeaderView().setVisibility(View.VISIBLE);
197       }
198     }
199   }
200 
getContactHolder(int position)201   private ContactViewHolder getContactHolder(int position) {
202     return ((ContactViewHolder) recyclerView.findViewHolderForAdapterPosition(position));
203   }
204 
205   @Override
onEmptyViewActionButtonClicked()206   public void onEmptyViewActionButtonClicked() {
207     if (emptyContentView.getActionLabel() == R.string.permission_single_turn_on) {
208       String[] deniedPermissions =
209           PermissionsUtil.getPermissionsCurrentlyDenied(
210               getContext(), PermissionsUtil.allContactsGroupPermissionsUsedInDialer);
211       if (deniedPermissions.length > 0) {
212         LogUtil.i(
213             "ContactsFragment.onEmptyViewActionButtonClicked",
214             "Requesting permissions: " + Arrays.toString(deniedPermissions));
215         FragmentCompat.requestPermissions(
216             this, deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
217       }
218 
219     } else if (emptyContentView.getActionLabel()
220         == R.string.all_contacts_empty_add_contact_action) {
221       // Add new contact
222       DialerUtils.startActivityWithErrorToast(
223           getContext(), IntentUtil.getNewContactIntent(), R.string.add_contact_not_available);
224     } else {
225       throw Assert.createIllegalStateFailException("Invalid empty content view action label.");
226     }
227   }
228 
229   @Override
onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults)230   public void onRequestPermissionsResult(
231       int requestCode, String[] permissions, int[] grantResults) {
232     if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) {
233       if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
234         // Force a refresh of the data since we were missing the permission before this.
235         emptyContentView.setVisibility(View.GONE);
236         getLoaderManager().initLoader(0, null, this);
237       }
238     }
239   }
240 }
241