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