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