| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.car.dialer; |
| |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.ContactsContract; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| |
| import com.android.car.dialer.telecom.TelecomUtils; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import androidx.annotation.ColorInt; |
| import androidx.annotation.Nullable; |
| import androidx.car.utils.ListItemBackgroundResolver; |
| import androidx.car.widget.DayNightStyle; |
| import androidx.car.widget.PagedListView; |
| import androidx.fragment.app.Fragment; |
| import androidx.loader.app.LoaderManager; |
| import androidx.loader.content.CursorLoader; |
| import androidx.loader.content.Loader; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| /** |
| * A fragment that shows the name of the contact, the photo and all listed phone numbers. It is |
| * primarily used to respond to the results of search queries but supplyig it with the content:// |
| * uri of a contact should work too. |
| */ |
| public class ContactDetailsFragment extends Fragment |
| implements LoaderManager.LoaderCallbacks<Cursor> { |
| private static final String TAG = "ContactDetailsFragment"; |
| private static final String TELEPHONE_URI_PREFIX = "tel:"; |
| |
| private static final int DETAILS_LOADER_QUERY_ID = 1; |
| private static final int PHONE_LOADER_QUERY_ID = 2; |
| |
| private static final String KEY_URI = "uri"; |
| |
| private static final String[] CONTACT_DETAILS_PROJECTION = { |
| ContactsContract.Contacts._ID, |
| ContactsContract.Contacts.DISPLAY_NAME, |
| ContactsContract.Contacts.PHOTO_URI, |
| ContactsContract.Contacts.HAS_PHONE_NUMBER |
| }; |
| |
| private PagedListView mListView; |
| private List<RecyclerView.OnScrollListener> mOnScrollListeners = new ArrayList<>(); |
| |
| public static ContactDetailsFragment newInstance(Uri uri, |
| @Nullable RecyclerView.OnScrollListener listener) { |
| ContactDetailsFragment fragment = new ContactDetailsFragment(); |
| if (listener != null) { |
| fragment.addOnScrollListener(listener); |
| } |
| |
| Bundle args = new Bundle(); |
| args.putParcelable(KEY_URI, uri); |
| fragment.setArguments(args); |
| |
| return fragment; |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| return inflater.inflate(R.layout.contact_details, container, false); |
| } |
| |
| @Override |
| public void onViewCreated(View view, Bundle savedInstanceState) { |
| mListView = view.findViewById(R.id.list_view); |
| mListView.setDayNightStyle(DayNightStyle.ALWAYS_LIGHT); |
| |
| RecyclerView recyclerView = mListView.getRecyclerView(); |
| for (RecyclerView.OnScrollListener listener : mOnScrollListeners) { |
| recyclerView.addOnScrollListener(listener); |
| } |
| |
| mOnScrollListeners.clear(); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| getLoaderManager().initLoader(DETAILS_LOADER_QUERY_ID, null, this); |
| } |
| |
| /** |
| * Adds a {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener} to be notified when |
| * the contact details are scrolled. |
| * |
| * @see RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener) |
| */ |
| public void addOnScrollListener(RecyclerView.OnScrollListener onScrollListener) { |
| // If the view has not been created yet, then queue the setting of the scroll listener. |
| if (mListView == null) { |
| mOnScrollListeners.add(onScrollListener); |
| return; |
| } |
| |
| mListView.getRecyclerView().addOnScrollListener(onScrollListener); |
| } |
| |
| @Override |
| public void onDestroy() { |
| // Clear all scroll listeners. |
| mListView.getRecyclerView().removeOnScrollListener(null); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| if (vdebug()) { |
| Log.d(TAG, "onCreateLoader id=" + id); |
| } |
| |
| if (id != DETAILS_LOADER_QUERY_ID) { |
| return null; |
| } |
| |
| Uri contactUri = getArguments().getParcelable(KEY_URI); |
| return new CursorLoader(getContext(), contactUri, CONTACT_DETAILS_PROJECTION, |
| null /* selection */, null /* selectionArgs */, null /* sortOrder */); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { |
| if (vdebug()) { |
| Log.d(TAG, "onLoadFinished"); |
| } |
| |
| if (cursor.moveToFirst()) { |
| mListView.setAdapter(new ContactDetailsAdapter(cursor)); |
| } |
| } |
| |
| @Override |
| public void onLoaderReset(Loader loader) { |
| } |
| |
| private boolean vdebug() { |
| return Log.isLoggable(TAG, Log.DEBUG); |
| } |
| |
| private class ContactDetailViewHolder extends RecyclerView.ViewHolder { |
| public View card; |
| public ImageView leftIcon; |
| public TextView title; |
| public TextView text; |
| public ImageView avatar; |
| public View divier; |
| |
| public ContactDetailViewHolder(View v) { |
| super(v); |
| card = v.findViewById(R.id.card); |
| leftIcon = v.findViewById(R.id.icon); |
| title = v.findViewById(R.id.title); |
| text = v.findViewById(R.id.text); |
| avatar = v.findViewById(R.id.avatar); |
| divier = v.findViewById(R.id.divider); |
| } |
| } |
| |
| private class ContactDetailsAdapter extends RecyclerView.Adapter<ContactDetailViewHolder> |
| implements PagedListView.ItemCap { |
| |
| private static final int ID_HEADER = 1; |
| private static final int ID_CONTENT = 2; |
| |
| private final String mContactName; |
| @ColorInt |
| private int mIconTint; |
| |
| private List<Pair<String, String>> mPhoneNumbers = new ArrayList<>(); |
| |
| public ContactDetailsAdapter(Cursor cursor) { |
| super(); |
| |
| mIconTint = getContext().getColor(R.color.contact_details_icon_tint); |
| |
| int idColIdx = cursor.getColumnIndex(ContactsContract.Contacts._ID); |
| String contactId = cursor.getString(idColIdx); |
| int nameColIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); |
| mContactName = cursor.getString(nameColIdx); |
| int hasPhoneColIdx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER); |
| boolean hasPhoneNumber = Integer.parseInt(cursor.getString(hasPhoneColIdx)) > 0; |
| |
| if (!hasPhoneNumber) { |
| return; |
| } |
| |
| // Fetch the phone number from the contacts db using another loader. |
| LoaderManager.getInstance(ContactDetailsFragment.this).initLoader(PHONE_LOADER_QUERY_ID, |
| null, |
| new LoaderManager.LoaderCallbacks<Cursor>() { |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| return new CursorLoader(getContext(), |
| ContactsContract.CommonDataKinds.Phone.CONTENT_URI, |
| null, /* All columns **/ |
| ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", |
| new String[]{contactId}, |
| null /* sortOrder */); |
| } |
| |
| public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { |
| while (cursor.moveToNext()) { |
| int typeColIdx = cursor.getColumnIndex( |
| ContactsContract.CommonDataKinds.Phone.TYPE); |
| int type = cursor.getInt(typeColIdx); |
| int numberColIdx = cursor.getColumnIndex( |
| ContactsContract.CommonDataKinds.Phone.NUMBER); |
| String number = cursor.getString(numberColIdx); |
| String numberType; |
| switch (type) { |
| case ContactsContract.CommonDataKinds.Phone.TYPE_HOME: |
| numberType = getString(R.string.type_home); |
| break; |
| case ContactsContract.CommonDataKinds.Phone.TYPE_WORK: |
| numberType = getString(R.string.type_work); |
| break; |
| case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: |
| numberType = getString(R.string.type_mobile); |
| break; |
| default: |
| numberType = getString(R.string.type_other); |
| } |
| mPhoneNumbers.add(new Pair<>(numberType, |
| TelecomUtils.getFormattedNumber(getContext(), number))); |
| notifyItemInserted(mPhoneNumbers.size()); |
| } |
| notifyDataSetChanged(); |
| } |
| |
| public void onLoaderReset(Loader loader) { |
| } |
| }); |
| } |
| |
| @Override |
| public int getItemViewType(int position) { |
| return position == 0 ? ID_HEADER : ID_CONTENT; |
| } |
| |
| @Override |
| public void setMaxItems(int maxItems) { |
| // Ignore. |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mPhoneNumbers.size() + 1; // +1 for the header row. |
| } |
| |
| @Override |
| public ContactDetailViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| int layoutResId; |
| switch (viewType) { |
| case ID_HEADER: |
| layoutResId = R.layout.contact_detail_name_image; |
| break; |
| case ID_CONTENT: |
| layoutResId = R.layout.contact_details_number; |
| break; |
| default: |
| Log.e(TAG, "Unknown view type " + viewType); |
| return null; |
| } |
| |
| View view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, |
| false); |
| return new ContactDetailViewHolder(view); |
| } |
| |
| @Override |
| public void onBindViewHolder(ContactDetailViewHolder viewHolder, int position) { |
| switch (viewHolder.getItemViewType()) { |
| case ID_HEADER: |
| viewHolder.title.setText(mContactName); |
| if (!mPhoneNumbers.isEmpty()) { |
| String firstNumber = mPhoneNumbers.get(0).second; |
| TelecomUtils.setContactBitmapAsync(getContext(), viewHolder.avatar, |
| mContactName, firstNumber); |
| } |
| // Just in case a viewholder object gets recycled. |
| viewHolder.card.setOnClickListener(null); |
| break; |
| case ID_CONTENT: |
| Pair<String, String> data = mPhoneNumbers.get(position - 1); |
| viewHolder.title.setText(data.second); // Type. |
| viewHolder.text.setText(data.first); // Number. |
| viewHolder.leftIcon.setImageResource(R.drawable.ic_phone); |
| viewHolder.leftIcon.setColorFilter(mIconTint); |
| viewHolder.card.setOnClickListener(v -> { |
| Intent callIntent = new Intent(Intent.ACTION_CALL); |
| callIntent.setData(Uri.parse(TELEPHONE_URI_PREFIX + data.second)); |
| getContext().startActivity(callIntent); |
| }); |
| break; |
| default: |
| Log.e(TAG, "Unknown view type " + viewHolder.getItemViewType()); |
| return; |
| } |
| |
| if (position == (getItemCount() - 1)) { |
| // hide divider for last item. |
| viewHolder.divier.setVisibility(View.GONE); |
| } else { |
| viewHolder.divier.setVisibility(View.VISIBLE); |
| } |
| ListItemBackgroundResolver.setBackground(viewHolder.card, |
| viewHolder.getAdapterPosition(), getItemCount()); |
| } |
| } |
| } |