• 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 package com.android.car.dialer;
17 
18 import android.content.Intent;
19 import android.database.Cursor;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.provider.ContactsContract;
23 import android.support.annotation.ColorInt;
24 import android.support.annotation.Nullable;
25 import android.support.v4.app.Fragment;
26 import android.support.v4.app.LoaderManager;
27 import android.support.v4.content.CursorLoader;
28 import android.support.v4.content.Loader;
29 import android.support.v7.widget.RecyclerView;
30 import android.util.Log;
31 import android.util.Pair;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.ImageView;
36 import android.widget.TextView;
37 
38 import com.android.car.dialer.telecom.TelecomUtils;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 import androidx.car.utils.ListItemBackgroundResolver;
44 import androidx.car.widget.DayNightStyle;
45 import androidx.car.widget.PagedListView;
46 
47 /**
48  * A fragment that shows the name of the contact, the photo and all listed phone numbers. It is
49  * primarily used to respond to the results of search queries but supplyig it with the content://
50  * uri of a contact should work too.
51  */
52 public class ContactDetailsFragment extends Fragment
53         implements LoaderManager.LoaderCallbacks<Cursor> {
54     private static final String TAG = "ContactDetailsFragment";
55     private static final String TELEPHONE_URI_PREFIX = "tel:";
56 
57     private static final int DETAILS_LOADER_QUERY_ID = 1;
58     private static final int PHONE_LOADER_QUERY_ID = 2;
59 
60     private static final String KEY_URI = "uri";
61 
62     private static final String[] CONTACT_DETAILS_PROJECTION = {
63             ContactsContract.Contacts._ID,
64             ContactsContract.Contacts.DISPLAY_NAME,
65             ContactsContract.Contacts.PHOTO_URI,
66             ContactsContract.Contacts.HAS_PHONE_NUMBER
67     };
68 
69     private PagedListView mListView;
70     private List<RecyclerView.OnScrollListener> mOnScrollListeners = new ArrayList<>();
71 
newInstance(Uri uri, @Nullable RecyclerView.OnScrollListener listener)72     public static ContactDetailsFragment newInstance(Uri uri,
73             @Nullable RecyclerView.OnScrollListener listener) {
74         ContactDetailsFragment fragment = new ContactDetailsFragment();
75         if (listener != null) {
76             fragment.addOnScrollListener(listener);
77         }
78 
79         Bundle args = new Bundle();
80         args.putParcelable(KEY_URI, uri);
81         fragment.setArguments(args);
82 
83         return fragment;
84     }
85 
86     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)87     public View onCreateView(LayoutInflater inflater, ViewGroup container,
88             Bundle savedInstanceState) {
89         return inflater.inflate(R.layout.contact_details, container, false);
90     }
91 
92     @Override
onViewCreated(View view, Bundle savedInstanceState)93     public void onViewCreated(View view, Bundle savedInstanceState) {
94         mListView = view.findViewById(R.id.list_view);
95         mListView.setDayNightStyle(DayNightStyle.ALWAYS_LIGHT);
96 
97         RecyclerView recyclerView = mListView.getRecyclerView();
98         for (RecyclerView.OnScrollListener listener : mOnScrollListeners) {
99             recyclerView.addOnScrollListener(listener);
100         }
101 
102         mOnScrollListeners.clear();
103     }
104 
105     @Override
onActivityCreated(Bundle savedInstanceState)106     public void onActivityCreated(Bundle savedInstanceState) {
107         super.onActivityCreated(savedInstanceState);
108         getLoaderManager().initLoader(DETAILS_LOADER_QUERY_ID, null, this);
109     }
110 
111     /**
112      * Adds a {@link android.support.v7.widget.RecyclerView.OnScrollListener} to be notified when
113      * the contact details are scrolled.
114      *
115      * @see RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener)
116      */
addOnScrollListener(RecyclerView.OnScrollListener onScrollListener)117     public void addOnScrollListener(RecyclerView.OnScrollListener onScrollListener) {
118         // If the view has not been created yet, then queue the setting of the scroll listener.
119         if (mListView == null) {
120             mOnScrollListeners.add(onScrollListener);
121             return;
122         }
123 
124         mListView.getRecyclerView().addOnScrollListener(onScrollListener);
125     }
126 
127     @Override
onDestroy()128     public void onDestroy() {
129         // Clear all scroll listeners.
130         mListView.getRecyclerView().removeOnScrollListener(null);
131         super.onDestroy();
132     }
133 
134     @Override
onCreateLoader(int id, Bundle args)135     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
136         if (vdebug()) {
137             Log.d(TAG, "onCreateLoader id=" + id);
138         }
139 
140         if (id != DETAILS_LOADER_QUERY_ID) {
141             return null;
142         }
143 
144         Uri contactUri = getArguments().getParcelable(KEY_URI);
145         return new CursorLoader(getContext(), contactUri, CONTACT_DETAILS_PROJECTION,
146                 null /* selection */, null /* selectionArgs */, null /* sortOrder */);
147     }
148 
149     @Override
onLoadFinished(Loader<Cursor> loader, Cursor cursor)150     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
151         if (vdebug()) {
152             Log.d(TAG, "onLoadFinished");
153         }
154 
155         if (cursor.moveToFirst()) {
156             mListView.setAdapter(new ContactDetailsAdapter(cursor));
157         }
158     }
159 
160     @Override
onLoaderReset(Loader loader)161     public void onLoaderReset(Loader loader) {
162     }
163 
vdebug()164     private boolean vdebug() {
165         return Log.isLoggable(TAG, Log.DEBUG);
166     }
167 
168     private class ContactDetailViewHolder extends RecyclerView.ViewHolder {
169         public View card;
170         public ImageView leftIcon;
171         public TextView title;
172         public TextView text;
173         public ImageView avatar;
174         public View divier;
175 
ContactDetailViewHolder(View v)176         public ContactDetailViewHolder(View v) {
177             super(v);
178             card = v.findViewById(R.id.card);
179             leftIcon = v.findViewById(R.id.icon);
180             title = v.findViewById(R.id.title);
181             text = v.findViewById(R.id.text);
182             avatar = v.findViewById(R.id.avatar);
183             divier = v.findViewById(R.id.divider);
184         }
185     }
186 
187     private class ContactDetailsAdapter extends RecyclerView.Adapter<ContactDetailViewHolder>
188             implements PagedListView.ItemCap {
189 
190         private static final int ID_HEADER = 1;
191         private static final int ID_CONTENT = 2;
192 
193         private final String mContactName;
194         @ColorInt
195         private int mIconTint;
196 
197         private List<Pair<String, String>> mPhoneNumbers = new ArrayList<>();
198 
ContactDetailsAdapter(Cursor cursor)199         public ContactDetailsAdapter(Cursor cursor) {
200             super();
201 
202             mIconTint = getContext().getColor(R.color.contact_details_icon_tint);
203 
204             int idColIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID);
205             String contactId = cursor.getString(idColIdx);
206             int nameColIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
207             mContactName = cursor.getString(nameColIdx);
208             int hasPhoneColIdx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
209             boolean hasPhoneNumber = Integer.parseInt(cursor.getString(hasPhoneColIdx)) > 0;
210 
211             if (!hasPhoneNumber) {
212                 return;
213             }
214 
215             // Fetch the phone number from the contacts db using another loader.
216             LoaderManager.getInstance(ContactDetailsFragment.this).initLoader(PHONE_LOADER_QUERY_ID,
217                     null,
218                     new LoaderManager.LoaderCallbacks<Cursor>() {
219                         @Override
220                         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
221                             return new CursorLoader(getContext(),
222                                     ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
223                                     null, /* All columns **/
224                                     ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
225                                     new String[]{contactId},
226                                     null /* sortOrder */);
227                         }
228 
229                         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
230                             while (cursor.moveToNext()) {
231                                 int typeColIdx = cursor.getColumnIndex(
232                                         ContactsContract.CommonDataKinds.Phone.TYPE);
233                                 int type = cursor.getInt(typeColIdx);
234                                 int numberColIdx = cursor.getColumnIndex(
235                                         ContactsContract.CommonDataKinds.Phone.NUMBER);
236                                 String number = cursor.getString(numberColIdx);
237                                 String numberType;
238                                 switch (type) {
239                                     case ContactsContract.CommonDataKinds.Phone.TYPE_HOME:
240                                         numberType = getString(R.string.type_home);
241                                         break;
242                                     case ContactsContract.CommonDataKinds.Phone.TYPE_WORK:
243                                         numberType = getString(R.string.type_work);
244                                         break;
245                                     case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE:
246                                         numberType = getString(R.string.type_mobile);
247                                         break;
248                                     default:
249                                         numberType = getString(R.string.type_other);
250                                 }
251                                 mPhoneNumbers.add(new Pair<>(numberType,
252                                         TelecomUtils.getFormattedNumber(getContext(), number)));
253                                 notifyItemInserted(mPhoneNumbers.size());
254                             }
255                             notifyDataSetChanged();
256                         }
257 
258                         public void onLoaderReset(Loader loader) {
259                         }
260                     });
261         }
262 
263         @Override
getItemViewType(int position)264         public int getItemViewType(int position) {
265             return position == 0 ? ID_HEADER : ID_CONTENT;
266         }
267 
268         @Override
setMaxItems(int maxItems)269         public void setMaxItems(int maxItems) {
270             // Ignore.
271         }
272 
273         @Override
getItemCount()274         public int getItemCount() {
275             return mPhoneNumbers.size() + 1;  // +1 for the header row.
276         }
277 
278         @Override
onCreateViewHolder(ViewGroup parent, int viewType)279         public ContactDetailViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
280             int layoutResId;
281             switch (viewType) {
282                 case ID_HEADER:
283                     layoutResId = R.layout.contact_detail_name_image;
284                     break;
285                 case ID_CONTENT:
286                     layoutResId = R.layout.contact_details_number;
287                     break;
288                 default:
289                     Log.e(TAG, "Unknown view type " + viewType);
290                     return null;
291             }
292 
293             View view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent,
294                     false);
295             return new ContactDetailViewHolder(view);
296         }
297 
298         @Override
onBindViewHolder(ContactDetailViewHolder viewHolder, int position)299         public void onBindViewHolder(ContactDetailViewHolder viewHolder, int position) {
300             switch (viewHolder.getItemViewType()) {
301                 case ID_HEADER:
302                     viewHolder.title.setText(mContactName);
303                     if (!mPhoneNumbers.isEmpty()) {
304                         String firstNumber = mPhoneNumbers.get(0).second;
305                         TelecomUtils.setContactBitmapAsync(getContext(), viewHolder.avatar,
306                                 mContactName, firstNumber);
307                     }
308                     // Just in case a viewholder object gets recycled.
309                     viewHolder.card.setOnClickListener(null);
310                     break;
311                 case ID_CONTENT:
312                     Pair<String, String> data = mPhoneNumbers.get(position - 1);
313                     viewHolder.title.setText(data.second);  // Type.
314                     viewHolder.text.setText(data.first);  // Number.
315                     viewHolder.leftIcon.setImageResource(R.drawable.ic_phone);
316                     viewHolder.leftIcon.setColorFilter(mIconTint);
317                     viewHolder.card.setOnClickListener(v -> {
318                         Intent callIntent = new Intent(Intent.ACTION_CALL);
319                         callIntent.setData(Uri.parse(TELEPHONE_URI_PREFIX + data.second));
320                         getContext().startActivity(callIntent);
321                     });
322                     break;
323                 default:
324                     Log.e(TAG, "Unknown view type " + viewHolder.getItemViewType());
325                     return;
326             }
327 
328             if (position == (getItemCount() - 1)) {
329                 // hide divider for last item.
330                 viewHolder.divier.setVisibility(View.GONE);
331             } else {
332                 viewHolder.divier.setVisibility(View.VISIBLE);
333             }
334             ListItemBackgroundResolver.setBackground(viewHolder.card,
335                     viewHolder.getAdapterPosition(), getItemCount());
336         }
337     }
338 }
339