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