1 /*
2  * Copyright (C) 2015 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.messaging.ui;
17 
18 import android.content.Context;
19 import android.content.Intent;
20 import androidx.core.text.BidiFormatter;
21 import androidx.core.text.TextDirectionHeuristicsCompat;
22 import android.text.TextUtils;
23 import android.util.AttributeSet;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.View.OnLayoutChangeListener;
27 import android.widget.LinearLayout;
28 import android.widget.TextView;
29 
30 import com.android.messaging.R;
31 import com.android.messaging.datamodel.binding.BindingBase;
32 import com.android.messaging.datamodel.binding.DetachableBinding;
33 import com.android.messaging.datamodel.data.PersonItemData;
34 import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
35 import com.android.messaging.util.AccessibilityUtil;
36 import com.android.messaging.util.UiUtils;
37 
38 /**
39  * Shows a view for a "person" - could be a contact or a participant. This always shows a
40  * contact icon on the left, and the person's display name on the right.
41  *
42  * This view is always bound to an abstract PersonItemData class, so to use it for a specific
43  * scenario, all you need to do is to create a concrete PersonItemData subclass that bridges
44  * between the underlying data (e.g. ParticipantData) and what the UI wants (e.g. display name).
45  */
46 public class PersonItemView extends LinearLayout implements PersonItemDataListener,
47         OnLayoutChangeListener {
48     public interface PersonItemViewListener {
onPersonClicked(PersonItemData data)49         void onPersonClicked(PersonItemData data);
onPersonLongClicked(PersonItemData data)50         boolean onPersonLongClicked(PersonItemData data);
51     }
52 
53     protected final DetachableBinding<PersonItemData> mBinding;
54     private TextView mNameTextView;
55     private TextView mDetailsTextView;
56     private ContactIconView mContactIconView;
57     private View mDetailsContainer;
58     private PersonItemViewListener mListener;
59     private boolean mAvatarOnly;
60 
PersonItemView(final Context context, final AttributeSet attrs)61     public PersonItemView(final Context context, final AttributeSet attrs) {
62         super(context, attrs);
63         mBinding = BindingBase.createDetachableBinding(this);
64         LayoutInflater.from(getContext()).inflate(R.layout.person_item_view, this, true);
65     }
66 
67     @Override
onFinishInflate()68     protected void onFinishInflate() {
69         mNameTextView = (TextView) findViewById(R.id.name);
70         mDetailsTextView = (TextView) findViewById(R.id.details);
71         mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
72         mDetailsContainer = findViewById(R.id.details_container);
73         mNameTextView.addOnLayoutChangeListener(this);
74     }
75 
76     @Override
onDetachedFromWindow()77     protected void onDetachedFromWindow() {
78         super.onDetachedFromWindow();
79         if (mBinding.isBound()) {
80             mBinding.detach();
81         }
82     }
83 
84     @Override
onAttachedToWindow()85     protected void onAttachedToWindow() {
86         super.onAttachedToWindow();
87         mBinding.reAttachIfPossible();
88     }
89 
90     /**
91      * Binds to a person item data which will provide us info to be displayed.
92      * @param personData the PersonItemData to be bound to.
93      */
bind(final PersonItemData personData)94     public void bind(final PersonItemData personData) {
95         if (mBinding.isBound()) {
96             if (mBinding.getData().equals(personData)) {
97                 // Don't rebind if we are requesting the same data.
98                 return;
99             }
100             mBinding.unbind();
101         }
102 
103         if (personData != null) {
104             mBinding.bind(personData);
105             mBinding.getData().setListener(this);
106 
107             // Accessibility reason : in case phone numbers are mixed in the display name,
108             // we need to vocalize it for talkback.
109             final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
110                     getResources(), getDisplayName());
111             mNameTextView.setContentDescription(vocalizedDisplayName);
112         }
113         updateViewAppearance();
114     }
115 
116     /**
117      * @return Display name, possibly comma-ellipsized.
118      */
getDisplayName()119     private String getDisplayName() {
120         final int width = mNameTextView.getMeasuredWidth();
121         final String displayName = mBinding.getData().getDisplayName();
122         if (width == 0 || TextUtils.isEmpty(displayName) || !displayName.contains(",")) {
123             return displayName;
124         }
125         final String plusOneString = getContext().getString(R.string.plus_one);
126         final String plusNString = getContext().getString(R.string.plus_n);
127         return BidiFormatter.getInstance().unicodeWrap(
128                 UiUtils.commaEllipsize(
129                         displayName,
130                         mNameTextView.getPaint(),
131                         width,
132                         plusOneString,
133                         plusNString).toString(),
134                 TextDirectionHeuristicsCompat.LTR);
135     }
136 
137     @Override
onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, final int oldLeft, final int oldTop, final int oldRight, final int oldBottom)138     public void onLayoutChange(final View v, final int left, final int top, final int right,
139             final int bottom, final int oldLeft, final int oldTop, final int oldRight,
140             final int oldBottom) {
141         if (mBinding.isBound() && v == mNameTextView) {
142             setNameTextView();
143         }
144     }
145 
146     /**
147      * When set to true, we display only the avatar of the person and hide everything else.
148      */
setAvatarOnly(final boolean avatarOnly)149     public void setAvatarOnly(final boolean avatarOnly) {
150         mAvatarOnly = avatarOnly;
151         mDetailsContainer.setVisibility(avatarOnly ? GONE : VISIBLE);
152     }
153 
isAvatarOnly()154     public boolean isAvatarOnly() {
155         return mAvatarOnly;
156     }
157 
setNameTextColor(final int color)158     public void setNameTextColor(final int color) {
159         mNameTextView.setTextColor(color);
160     }
161 
setDetailsTextColor(final int color)162     public void setDetailsTextColor(final int color) {
163         mDetailsTextView.setTextColor(color);
164     }
165 
setListener(final PersonItemViewListener listener)166     public void setListener(final PersonItemViewListener listener) {
167         mListener = listener;
168         if (mListener == null) {
169             return;
170         }
171         setOnClickListener(new OnClickListener() {
172             @Override
173             public void onClick(final View v) {
174                 if (mListener != null && mBinding.isBound()) {
175                     mListener.onPersonClicked(mBinding.getData());
176                 }
177             }
178         });
179         final OnLongClickListener onLongClickListener = new OnLongClickListener() {
180             @Override
181             public boolean onLongClick(View v) {
182                 if (mListener != null && mBinding.isBound()) {
183                     return mListener.onPersonLongClicked(mBinding.getData());
184                 }
185                 return false;
186             }
187         };
188         setOnLongClickListener(onLongClickListener);
189         mContactIconView.setOnLongClickListener(onLongClickListener);
190     }
191 
performClickOnAvatar()192     public void performClickOnAvatar() {
193         mContactIconView.performClick();
194     }
195 
updateViewAppearance()196     protected void updateViewAppearance() {
197         if (mBinding.isBound()) {
198             setNameTextView();
199 
200             final String details = mBinding.getData().getDetails();
201             if (TextUtils.isEmpty(details)) {
202                 mDetailsTextView.setVisibility(GONE);
203             } else {
204                 mDetailsTextView.setVisibility(VISIBLE);
205                 mDetailsTextView.setText(details);
206             }
207 
208             mContactIconView.setImageResourceUri(mBinding.getData().getAvatarUri(),
209                     mBinding.getData().getContactId(), mBinding.getData().getLookupKey(),
210                     mBinding.getData().getNormalizedDestination());
211         } else {
212             mNameTextView.setText("");
213             mContactIconView.setImageResourceUri(null);
214         }
215     }
216 
setNameTextView()217     private void setNameTextView() {
218         final String displayName = getDisplayName();
219         if (TextUtils.isEmpty(displayName)) {
220             mNameTextView.setVisibility(GONE);
221         } else {
222             mNameTextView.setVisibility(VISIBLE);
223             mNameTextView.setText(displayName);
224         }
225     }
226 
227     @Override
onPersonDataUpdated(final PersonItemData data)228     public void onPersonDataUpdated(final PersonItemData data) {
229         mBinding.ensureBound(data);
230         updateViewAppearance();
231     }
232 
233     @Override
onPersonDataFailed(final PersonItemData data, final Exception exception)234     public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
235         mBinding.ensureBound(data);
236         updateViewAppearance();
237     }
238 
getClickIntent()239     public Intent getClickIntent() {
240         return mBinding.getData().getClickIntent();
241     }
242 }
243