blob: 72de11dafea6f7b3e45af813b4464d29fdc5d82e [file] [log] [blame]
/*
* 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());
}
}
}