1 /* 2 * Copyright (C) 2012 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.contacts.editor; 18 19 import static android.provider.ContactsContract.CommonDataKinds.GroupMembership; 20 import static android.provider.ContactsContract.CommonDataKinds.StructuredName; 21 22 import static com.android.contacts.util.MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors; 23 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapFactory; 28 import android.graphics.drawable.Drawable; 29 import android.media.RingtoneManager; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.provider.ContactsContract.CommonDataKinds.Email; 33 import android.provider.ContactsContract.CommonDataKinds.Event; 34 import android.provider.ContactsContract.CommonDataKinds.Im; 35 import android.provider.ContactsContract.CommonDataKinds.Note; 36 import android.provider.ContactsContract.CommonDataKinds.Organization; 37 import android.provider.ContactsContract.CommonDataKinds.Phone; 38 import android.provider.ContactsContract.CommonDataKinds.Photo; 39 import android.provider.ContactsContract.CommonDataKinds.Relation; 40 import android.provider.ContactsContract.CommonDataKinds.SipAddress; 41 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 42 import android.provider.ContactsContract.CommonDataKinds.Website; 43 import androidx.core.content.res.ResourcesCompat; 44 import android.text.TextUtils; 45 import android.widget.ImageView; 46 47 import com.android.contacts.ContactPhotoManager; 48 import com.android.contacts.ContactPhotoManager.DefaultImageProvider; 49 import com.android.contacts.ContactPhotoManager.DefaultImageRequest; 50 import com.android.contacts.ContactsUtils; 51 import com.android.contacts.R; 52 import com.android.contacts.model.ValuesDelta; 53 import com.android.contacts.model.account.AccountDisplayInfo; 54 import com.android.contacts.model.account.AccountInfo; 55 import com.android.contacts.model.dataitem.DataKind; 56 import com.android.contacts.util.ContactPhotoUtils; 57 import com.android.contacts.util.MaterialColorMapUtils.MaterialPalette; 58 import com.android.contacts.widget.QuickContactImageView; 59 60 import com.google.common.collect.ImmutableList; 61 import com.google.common.collect.Maps; 62 63 import java.io.FileNotFoundException; 64 import java.util.HashMap; 65 66 /** 67 * Utility methods for creating contact editor. 68 */ 69 public class EditorUiUtils { 70 71 // Maps DataKind.mimeType to editor view layouts. 72 private static final HashMap<String, Integer> mimetypeLayoutMap = Maps.newHashMap(); 73 static { 74 // Generally there should be a layout mapped to each existing DataKind mimetype but lots of 75 // them use the default text_fields_editor_view which we return as default so they don't 76 // need to be mapped. 77 // 78 // Other possible mime mappings are: 79 // DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME 80 // Nickname.CONTENT_ITEM_TYPE 81 // Email.CONTENT_ITEM_TYPE 82 // StructuredPostal.CONTENT_ITEM_TYPE 83 // Im.CONTENT_ITEM_TYPE 84 // Note.CONTENT_ITEM_TYPE 85 // Organization.CONTENT_ITEM_TYPE 86 // Phone.CONTENT_ITEM_TYPE 87 // SipAddress.CONTENT_ITEM_TYPE 88 // Website.CONTENT_ITEM_TYPE 89 // Relation.CONTENT_ITEM_TYPE 90 // 91 // Un-supported mime types need to mapped with -1. mimetypeLayoutMap.put(StructuredName.CONTENT_ITEM_TYPE, R.layout.structured_name_editor_view)92 mimetypeLayoutMap.put(StructuredName.CONTENT_ITEM_TYPE, 93 R.layout.structured_name_editor_view); mimetypeLayoutMap.put(GroupMembership.CONTENT_ITEM_TYPE, -1)94 mimetypeLayoutMap.put(GroupMembership.CONTENT_ITEM_TYPE, -1); mimetypeLayoutMap.put(Photo.CONTENT_ITEM_TYPE, -1)95 mimetypeLayoutMap.put(Photo.CONTENT_ITEM_TYPE, -1); mimetypeLayoutMap.put(Event.CONTENT_ITEM_TYPE, R.layout.event_field_editor_view)96 mimetypeLayoutMap.put(Event.CONTENT_ITEM_TYPE, R.layout.event_field_editor_view); 97 } 98 99 public static final ImmutableList<String> LEGACY_MIME_TYPE = 100 ImmutableList.of(Im.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE); 101 102 /** 103 * Fetches a layout for a given mimetype. 104 * 105 * @param mimetype The mime type (e.g. StructuredName.CONTENT_ITEM_TYPE) 106 * @return The layout resource id. 107 */ getLayoutResourceId(String mimetype)108 public static int getLayoutResourceId(String mimetype) { 109 final Integer id = mimetypeLayoutMap.get(mimetype); 110 if (id == null) { 111 return R.layout.text_fields_editor_view; 112 } 113 return id; 114 } 115 116 getAccountHeaderLabelForMyProfile(Context context, AccountInfo accountInfo)117 public static String getAccountHeaderLabelForMyProfile(Context context, 118 AccountInfo accountInfo) { 119 if (accountInfo.isDeviceAccount()) { 120 return context.getString(R.string.local_profile_title); 121 } else { 122 return context.getString(R.string.external_profile_title, 123 accountInfo.getTypeLabel()); 124 } 125 } 126 getAccountTypeHeaderLabel(Context context, AccountDisplayInfo displayableAccount)127 public static String getAccountTypeHeaderLabel(Context context, AccountDisplayInfo 128 displayableAccount) { 129 if (displayableAccount.isDeviceAccount()) { 130 // Do nothing. Type label should be "Device" 131 return displayableAccount.getTypeLabel().toString(); 132 } else if (displayableAccount.isGoogleAccount()) { 133 return context.getString(R.string.google_account_type_format, 134 displayableAccount.getTypeLabel()); 135 } else { 136 return context.getString(R.string.account_type_format, 137 displayableAccount.getTypeLabel()); 138 } 139 } 140 141 /** 142 * Returns a content description String for the container of the account information 143 * returned by {@link #getAccountTypeHeaderLabel(Context, AccountDisplayInfo)}. 144 */ getAccountInfoContentDescription(CharSequence accountName, CharSequence accountType)145 public static String getAccountInfoContentDescription(CharSequence accountName, 146 CharSequence accountType) { 147 final StringBuilder builder = new StringBuilder(); 148 if (!TextUtils.isEmpty(accountType)) { 149 builder.append(accountType).append('\n'); 150 } 151 if (!TextUtils.isEmpty(accountName)) { 152 builder.append(accountName); 153 } 154 return builder.toString(); 155 } 156 157 /** 158 * Return an icon that represents {@param mimeType}. 159 */ getMimeTypeDrawable(Context context, String mimeType)160 public static Drawable getMimeTypeDrawable(Context context, String mimeType) { 161 switch (mimeType) { 162 case StructuredName.CONTENT_ITEM_TYPE: 163 return ResourcesCompat.getDrawable(context.getResources(), 164 R.drawable.quantum_ic_person_vd_theme_24, null); 165 case StructuredPostal.CONTENT_ITEM_TYPE: 166 return ResourcesCompat.getDrawable(context.getResources(), 167 R.drawable.quantum_ic_place_vd_theme_24, null); 168 case SipAddress.CONTENT_ITEM_TYPE: 169 return ResourcesCompat.getDrawable(context.getResources(), 170 R.drawable.quantum_ic_dialer_sip_vd_theme_24, null); 171 case Phone.CONTENT_ITEM_TYPE: 172 return ResourcesCompat.getDrawable(context.getResources(), 173 R.drawable.quantum_ic_phone_vd_theme_24, null); 174 case Im.CONTENT_ITEM_TYPE: 175 return ResourcesCompat.getDrawable(context.getResources(), 176 R.drawable.quantum_ic_message_vd_theme_24, null); 177 case Event.CONTENT_ITEM_TYPE: 178 return ResourcesCompat.getDrawable(context.getResources(), 179 R.drawable.quantum_ic_event_vd_theme_24, null); 180 case Email.CONTENT_ITEM_TYPE: 181 return ResourcesCompat.getDrawable(context.getResources(), 182 R.drawable.quantum_ic_email_vd_theme_24, null); 183 case Website.CONTENT_ITEM_TYPE: 184 return ResourcesCompat.getDrawable(context.getResources(), 185 R.drawable.quantum_ic_public_vd_theme_24, null); 186 case Photo.CONTENT_ITEM_TYPE: 187 return ResourcesCompat.getDrawable(context.getResources(), 188 R.drawable.quantum_ic_camera_alt_vd_theme_24, null); 189 case GroupMembership.CONTENT_ITEM_TYPE: 190 return ResourcesCompat.getDrawable(context.getResources(), 191 R.drawable.quantum_ic_label_vd_theme_24, null); 192 case Organization.CONTENT_ITEM_TYPE: 193 return ResourcesCompat.getDrawable(context.getResources(), 194 R.drawable.quantum_ic_business_vd_theme_24, null); 195 case Note.CONTENT_ITEM_TYPE: 196 return ResourcesCompat.getDrawable(context.getResources(), 197 R.drawable.quantum_ic_insert_comment_vd_theme_24, null); 198 case Relation.CONTENT_ITEM_TYPE: 199 return ResourcesCompat.getDrawable(context.getResources(), 200 R.drawable.quantum_ic_circles_ext_vd_theme_24, null); 201 default: 202 return null; 203 } 204 } 205 206 /** 207 * Returns a ringtone string based on the ringtone URI and version #. 208 */ getRingtoneStringFromUri(Uri pickedUri, int currentVersion)209 public static String getRingtoneStringFromUri(Uri pickedUri, int currentVersion) { 210 if (isNewerThanM(currentVersion)) { 211 if (pickedUri == null) return ""; // silent ringtone 212 if (RingtoneManager.isDefault(pickedUri)) return null; // default ringtone 213 } 214 if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) return null; 215 return pickedUri.toString(); 216 } 217 218 /** 219 * Returns a ringtone URI, based on the string and version #. 220 */ getRingtoneUriFromString(String str, int currentVersion)221 public static Uri getRingtoneUriFromString(String str, int currentVersion) { 222 if (str != null) { 223 if (isNewerThanM(currentVersion) && TextUtils.isEmpty(str)) return null; 224 return Uri.parse(str); 225 } 226 return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); 227 } 228 isNewerThanM(int currentVersion)229 private static boolean isNewerThanM(int currentVersion) { 230 return currentVersion > Build.VERSION_CODES.M; 231 } 232 233 /** Returns the {@link Photo#PHOTO_FILE_ID} from the given ValuesDelta. */ getPhotoFileId(ValuesDelta valuesDelta)234 public static Long getPhotoFileId(ValuesDelta valuesDelta) { 235 if (valuesDelta == null) return null; 236 if (valuesDelta.getAfter() == null || valuesDelta.getAfter().get(Photo.PHOTO) == null) { 237 return valuesDelta.getAsLong(Photo.PHOTO_FILE_ID); 238 } 239 return null; 240 } 241 242 /** Binds the full resolution image at the given Uri to the provided ImageView. */ loadPhoto(ContactPhotoManager contactPhotoManager, ImageView imageView, Uri photoUri)243 static void loadPhoto(ContactPhotoManager contactPhotoManager, ImageView imageView, 244 Uri photoUri) { 245 final DefaultImageProvider fallbackToPreviousImage = new DefaultImageProvider() { 246 @Override 247 public void applyDefaultImage(ImageView view, int extent, boolean darkTheme, 248 DefaultImageRequest defaultImageRequest) { 249 // Before we finish setting the full sized image, don't change the current 250 // image that is set in any way. 251 } 252 }; 253 contactPhotoManager.loadPhoto(imageView, photoUri, imageView.getWidth(), 254 /* darkTheme =*/ false, /* isCircular =*/ false, 255 /* defaultImageRequest =*/ null, fallbackToPreviousImage); 256 } 257 258 /** Decodes the Bitmap from the photo bytes from the given ValuesDelta. */ getPhotoBitmap(ValuesDelta valuesDelta)259 public static Bitmap getPhotoBitmap(ValuesDelta valuesDelta) { 260 if (valuesDelta == null) return null; 261 final byte[] bytes = valuesDelta.getAsByteArray(Photo.PHOTO); 262 if (bytes == null) return null; 263 return BitmapFactory.decodeByteArray(bytes, /* offset =*/ 0, bytes.length); 264 } 265 266 /** Binds the default avatar to the given ImageView and tints it to match QuickContacts. */ setDefaultPhoto(ImageView imageView , Resources resources, MaterialPalette materialPalette)267 public static void setDefaultPhoto(ImageView imageView , Resources resources, 268 MaterialPalette materialPalette) { 269 // Use the default avatar drawable 270 imageView.setImageDrawable(ContactPhotoManager.getDefaultAvatarDrawableForContact( 271 resources, /* hires =*/ false, /* defaultImageRequest =*/ null)); 272 273 // Tint it to match the quick contacts 274 if (imageView instanceof QuickContactImageView) { 275 ((QuickContactImageView) imageView).setTint(materialPalette == null 276 ? getDefaultPrimaryAndSecondaryColors(resources).mPrimaryColor 277 : materialPalette.mPrimaryColor); 278 } 279 } 280 281 /** Returns compressed bitmap bytes from the given Uri, scaled to the thumbnail dimensions. */ getCompressedThumbnailBitmapBytes(Context context, Uri uri)282 public static byte[] getCompressedThumbnailBitmapBytes(Context context, Uri uri) 283 throws FileNotFoundException { 284 final Bitmap bitmap = ContactPhotoUtils.getBitmapFromUri(context, uri); 285 final int size = ContactsUtils.getThumbnailSize(context); 286 final Bitmap bitmapScaled = Bitmap.createScaledBitmap( 287 bitmap, size, size, /* filter =*/ false); 288 return ContactPhotoUtils.compressBitmap(bitmapScaled); 289 } 290 291 } 292