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.car.dialer.ui.contact; 18 19 import android.graphics.Bitmap; 20 import android.graphics.drawable.Drawable; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.view.View; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.core.graphics.drawable.RoundedBitmapDrawable; 28 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; 29 import androidx.lifecycle.LiveData; 30 import androidx.lifecycle.ViewModelProvider; 31 32 import com.android.car.apps.common.LetterTileDrawable; 33 import com.android.car.arch.common.FutureData; 34 import com.android.car.dialer.R; 35 import com.android.car.dialer.ui.common.DialerListBaseFragment; 36 import com.android.car.telephony.common.Contact; 37 import com.android.car.telephony.common.PhoneNumber; 38 import com.android.car.telephony.common.TelecomUtils; 39 import com.android.car.ui.core.CarUi; 40 import com.android.car.ui.toolbar.Toolbar; 41 import com.android.car.ui.toolbar.ToolbarController; 42 43 import com.bumptech.glide.Glide; 44 import com.bumptech.glide.request.RequestOptions; 45 import com.bumptech.glide.request.target.SimpleTarget; 46 import com.bumptech.glide.request.transition.Transition; 47 48 import javax.inject.Inject; 49 50 import dagger.hilt.android.AndroidEntryPoint; 51 52 /** 53 * A fragment that shows the name of the contact, the photo and all listed phone numbers. It is 54 * primarily used to respond to the results of search queries but supplyig it with the content:// 55 * uri of a contact should work too. 56 */ 57 @AndroidEntryPoint(DialerListBaseFragment.class) 58 public class ContactDetailsFragment extends Hilt_ContactDetailsFragment implements 59 ContactDetailsAdapter.PhoneNumberPresenter { 60 private static final String TAG = "CD.ContactDetailsFragment"; 61 public static final String FRAGMENT_TAG = "CONTACT_DETAIL_FRAGMENT_TAG"; 62 63 // Key to load and save the contact entity instance. 64 private static final String KEY_CONTACT_ENTITY = "ContactEntity"; 65 66 @Inject ContactDetailsAdapter.Factory mContactDetailsAdapterFactory; 67 private Contact mContact; 68 private LiveData<FutureData<Contact>> mContactDetailsLiveData; 69 private ContactDetailsViewModel mContactDetailsViewModel; 70 71 private boolean mShowActionBarView; 72 private boolean mShowActionBarAvatar; 73 74 /** 75 * Creates a new ContactDetailsFragment using a {@link Contact}. 76 */ newInstance(Contact contact)77 public static ContactDetailsFragment newInstance(Contact contact) { 78 ContactDetailsFragment fragment = new ContactDetailsFragment(); 79 Bundle args = new Bundle(); 80 args.putParcelable(KEY_CONTACT_ENTITY, contact); 81 fragment.setArguments(args); 82 return fragment; 83 } 84 85 @Override onCreate(Bundle savedInstanceState)86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 89 mContact = getArguments() == null ? null : getArguments().getParcelable(KEY_CONTACT_ENTITY); 90 if (mContact == null && savedInstanceState != null) { 91 mContact = savedInstanceState.getParcelable(KEY_CONTACT_ENTITY); 92 } 93 mContactDetailsViewModel = new ViewModelProvider(this).get( 94 ContactDetailsViewModel.class); 95 mContactDetailsLiveData = mContactDetailsViewModel.getContactDetails(mContact); 96 97 mShowActionBarView = getResources().getBoolean( 98 R.bool.config_show_contact_details_action_bar_view); 99 mShowActionBarAvatar = getResources().getBoolean( 100 R.bool.config_show_contact_details_action_bar_avatar); 101 } 102 103 @Override onViewCreated(View view, Bundle savedInstanceState)104 public void onViewCreated(View view, Bundle savedInstanceState) { 105 super.onViewCreated(view, savedInstanceState); 106 ContactDetailsAdapter contactDetailsAdapter = mContactDetailsAdapterFactory.create(mContact, 107 this); 108 getRecyclerView().setAdapter(contactDetailsAdapter); 109 mContactDetailsLiveData.observe(this, contact -> { 110 if (contact.isLoading()) { 111 showLoading(); 112 } else { 113 onContactChanged(contact.getData()); 114 contactDetailsAdapter.setContact(contact.getData()); 115 showContent(); 116 } 117 }); 118 } 119 onContactChanged(Contact contact)120 private void onContactChanged(Contact contact) { 121 if (getArguments() != null) { 122 getArguments().clear(); 123 } 124 ToolbarController toolbar = CarUi.getToolbar(getActivity()); 125 // Null check to have unit tests to pass. 126 if (toolbar == null) { 127 return; 128 } 129 toolbar.setTitle(null); 130 toolbar.setLogo(null); 131 if (mShowActionBarView) { 132 toolbar.setTitle(contact == null ? getString(R.string.error_contact_deleted) 133 : contact.getDisplayName()); 134 if (mShowActionBarAvatar) { 135 int avatarSize = getResources().getDimensionPixelSize( 136 R.dimen.contact_details_action_bar_avatar_size); 137 LetterTileDrawable letterTile = TelecomUtils.createLetterTile(getContext(), 138 contact == null ? null : contact.getInitials(), 139 contact == null ? null : contact.getDisplayName()); 140 Uri avatarUri = contact == null ? null : contact.getAvatarUri(); 141 Glide.with(this) 142 .asBitmap() 143 .load(avatarUri) 144 .apply(new RequestOptions().override(avatarSize).error(letterTile)) 145 .into(new SimpleTarget<Bitmap>() { 146 @Override 147 public void onResourceReady(Bitmap bitmap, 148 Transition<? super Bitmap> transition) { 149 RoundedBitmapDrawable roundedBitmapDrawable = createFromBitmap( 150 bitmap, avatarSize); 151 toolbar.setLogo(roundedBitmapDrawable); 152 } 153 154 @Override 155 public void onLoadFailed(@Nullable Drawable errorDrawable) { 156 RoundedBitmapDrawable roundedBitmapDrawable = createFromLetterTile( 157 letterTile, avatarSize); 158 toolbar.setLogo(roundedBitmapDrawable); 159 } 160 }); 161 } 162 } 163 } 164 165 @Override setupToolbar(@onNull ToolbarController toolbar)166 protected void setupToolbar(@NonNull ToolbarController toolbar) { 167 toolbar.setState(getToolbarState()); 168 toolbar.setMenuItems(null); 169 } 170 171 @Override getToolbarState()172 protected Toolbar.State getToolbarState() { 173 return Toolbar.State.SUBPAGE; 174 } 175 176 @Override onSaveInstanceState(Bundle outState)177 public void onSaveInstanceState(Bundle outState) { 178 super.onSaveInstanceState(outState); 179 outState.putParcelable(KEY_CONTACT_ENTITY, mContact); 180 } 181 182 @Override onClick(Contact contact, PhoneNumber phoneNumber)183 public void onClick(Contact contact, PhoneNumber phoneNumber) { 184 boolean isFavorite = phoneNumber.isFavorite(); 185 if (isFavorite) { 186 mContactDetailsViewModel.removeFromFavorite(contact, phoneNumber); 187 } else { 188 mContactDetailsViewModel.addToFavorite(contact, phoneNumber); 189 } 190 } 191 createFromLetterTile(LetterTileDrawable letterTileDrawable, int avatarSize)192 private RoundedBitmapDrawable createFromLetterTile(LetterTileDrawable letterTileDrawable, 193 int avatarSize) { 194 return createFromBitmap(letterTileDrawable.toBitmap(avatarSize), avatarSize); 195 } 196 createFromBitmap(Bitmap bitmap, int avatarSize)197 private RoundedBitmapDrawable createFromBitmap(Bitmap bitmap, int avatarSize) { 198 RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create( 199 getResources(), bitmap); 200 float radiusPercent = getResources() 201 .getFloat(R.dimen.contact_avatar_corner_radius_percent); 202 float radius = avatarSize * radiusPercent; 203 roundedBitmapDrawable.setCornerRadius(radius); 204 return roundedBitmapDrawable; 205 } 206 } 207