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 import static com.android.contacts.common.util.MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors; 22 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.BitmapFactory; 27 import android.graphics.drawable.Drawable; 28 import android.provider.ContactsContract.CommonDataKinds.Email; 29 import android.provider.ContactsContract.CommonDataKinds.Event; 30 import android.provider.ContactsContract.CommonDataKinds.Im; 31 import android.provider.ContactsContract.CommonDataKinds.Note; 32 import android.provider.ContactsContract.CommonDataKinds.Organization; 33 import android.provider.ContactsContract.CommonDataKinds.Phone; 34 import android.provider.ContactsContract.CommonDataKinds.Photo; 35 import android.provider.ContactsContract.CommonDataKinds.Relation; 36 import android.provider.ContactsContract.CommonDataKinds.SipAddress; 37 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 38 import android.provider.ContactsContract.CommonDataKinds.Website; 39 import android.media.RingtoneManager; 40 import android.net.Uri; 41 import android.os.Build; 42 import android.text.TextUtils; 43 import android.util.Pair; 44 import android.widget.ImageView; 45 46 import com.android.contacts.R; 47 import com.android.contacts.common.ContactPhotoManager; 48 import com.android.contacts.common.ContactPhotoManager.DefaultImageProvider; 49 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; 50 import com.android.contacts.common.ContactsUtils; 51 import com.android.contacts.common.model.ValuesDelta; 52 import com.android.contacts.common.model.account.AccountType; 53 import com.android.contacts.common.model.account.GoogleAccountType; 54 import com.android.contacts.common.model.dataitem.DataKind; 55 import com.android.contacts.common.testing.NeededForTesting; 56 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; 57 import com.android.contacts.util.ContactPhotoUtils; 58 import com.android.contacts.widget.QuickContactImageView; 59 60 import com.google.common.collect.Maps; 61 62 import java.io.FileNotFoundException; 63 import java.util.HashMap; 64 65 /** 66 * Utility methods for creating contact editor. 67 */ 68 @NeededForTesting 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. 92 mimetypeLayoutMap.put(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME, R.layout.phonetic_name_editor_view)93 mimetypeLayoutMap.put(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME, 94 R.layout.phonetic_name_editor_view); mimetypeLayoutMap.put(StructuredName.CONTENT_ITEM_TYPE, R.layout.structured_name_editor_view)95 mimetypeLayoutMap.put(StructuredName.CONTENT_ITEM_TYPE, 96 R.layout.structured_name_editor_view); mimetypeLayoutMap.put(GroupMembership.CONTENT_ITEM_TYPE, -1)97 mimetypeLayoutMap.put(GroupMembership.CONTENT_ITEM_TYPE, -1); mimetypeLayoutMap.put(Photo.CONTENT_ITEM_TYPE, -1)98 mimetypeLayoutMap.put(Photo.CONTENT_ITEM_TYPE, -1); mimetypeLayoutMap.put(Event.CONTENT_ITEM_TYPE, R.layout.event_field_editor_view)99 mimetypeLayoutMap.put(Event.CONTENT_ITEM_TYPE, R.layout.event_field_editor_view); 100 } 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 /** 117 * Returns the account name and account type labels to display for local accounts. 118 */ 119 @NeededForTesting getLocalAccountInfo(Context context, String accountName, AccountType accountType)120 public static Pair<String,String> getLocalAccountInfo(Context context, 121 String accountName, AccountType accountType) { 122 if (TextUtils.isEmpty(accountName)) { 123 return new Pair<>( 124 /* accountName =*/ null, 125 context.getString(R.string.local_profile_title)); 126 } 127 return new Pair<>( 128 accountName, 129 context.getString(R.string.external_profile_title, 130 accountType.getDisplayLabel(context))); 131 } 132 133 /** 134 * Returns the account name and account type labels to display for the given account type. 135 */ 136 @NeededForTesting getAccountInfo(Context context, String accountName, AccountType accountType)137 public static Pair<String,String> getAccountInfo(Context context, String accountName, 138 AccountType accountType) { 139 CharSequence accountTypeDisplayLabel = accountType.getDisplayLabel(context); 140 if (TextUtils.isEmpty(accountTypeDisplayLabel)) { 141 accountTypeDisplayLabel = context.getString(R.string.account_phone); 142 } 143 144 if (TextUtils.isEmpty(accountName)) { 145 return new Pair<>( 146 /* accountName =*/ null, 147 context.getString(R.string.account_type_format, accountTypeDisplayLabel)); 148 } 149 150 final String accountNameDisplayLabel = 151 context.getString(R.string.from_account_format, accountName); 152 153 if (GoogleAccountType.ACCOUNT_TYPE.equals(accountType.accountType) 154 && accountType.dataSet == null) { 155 return new Pair<>( 156 accountNameDisplayLabel, 157 context.getString(R.string.google_account_type_format, accountTypeDisplayLabel)); 158 } 159 return new Pair<>( 160 accountNameDisplayLabel, 161 context.getString(R.string.account_type_format, accountTypeDisplayLabel)); 162 } 163 164 /** 165 * Returns a content description String for the container of the account information 166 * returned by {@link #getAccountInfo}. 167 */ getAccountInfoContentDescription(CharSequence accountName, CharSequence accountType)168 public static String getAccountInfoContentDescription(CharSequence accountName, 169 CharSequence accountType) { 170 final StringBuilder builder = new StringBuilder(); 171 if (!TextUtils.isEmpty(accountType)) { 172 builder.append(accountType).append('\n'); 173 } 174 if (!TextUtils.isEmpty(accountName)) { 175 builder.append(accountName); 176 } 177 return builder.toString(); 178 } 179 180 /** 181 * Return an icon that represents {@param mimeType}. 182 */ getMimeTypeDrawable(Context context, String mimeType)183 public static Drawable getMimeTypeDrawable(Context context, String mimeType) { 184 switch (mimeType) { 185 case StructuredName.CONTENT_ITEM_TYPE: 186 return context.getResources().getDrawable(R.drawable.ic_person_black_24dp); 187 case StructuredPostal.CONTENT_ITEM_TYPE: 188 return context.getResources().getDrawable(R.drawable.ic_place_24dp); 189 case SipAddress.CONTENT_ITEM_TYPE: 190 return context.getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp); 191 case Phone.CONTENT_ITEM_TYPE: 192 return context.getResources().getDrawable(R.drawable.ic_phone_24dp); 193 case Im.CONTENT_ITEM_TYPE: 194 return context.getResources().getDrawable(R.drawable.ic_message_24dp); 195 case Event.CONTENT_ITEM_TYPE: 196 return context.getResources().getDrawable(R.drawable.ic_event_24dp); 197 case Email.CONTENT_ITEM_TYPE: 198 return context.getResources().getDrawable(R.drawable.ic_email_24dp); 199 case Website.CONTENT_ITEM_TYPE: 200 return context.getResources().getDrawable(R.drawable.ic_public_black_24dp); 201 case Photo.CONTENT_ITEM_TYPE: 202 return context.getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp); 203 case GroupMembership.CONTENT_ITEM_TYPE: 204 return context.getResources().getDrawable(R.drawable.ic_people_black_24dp); 205 case Organization.CONTENT_ITEM_TYPE: 206 return context.getResources().getDrawable(R.drawable.ic_business_black_24dp); 207 case Note.CONTENT_ITEM_TYPE: 208 return context.getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp); 209 case Relation.CONTENT_ITEM_TYPE: 210 return context.getResources().getDrawable( 211 R.drawable.ic_circles_extended_black_24dp); 212 default: 213 return null; 214 } 215 } 216 217 /** 218 * Returns a ringtone string based on the ringtone URI and version #. 219 */ 220 @NeededForTesting getRingtoneStringFromUri(Uri pickedUri, int currentVersion)221 public static String getRingtoneStringFromUri(Uri pickedUri, int currentVersion) { 222 if (isNewerThanM(currentVersion)) { 223 if (pickedUri == null) return ""; // silent ringtone 224 if (RingtoneManager.isDefault(pickedUri)) return null; // default ringtone 225 } 226 if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) return null; 227 return pickedUri.toString(); 228 } 229 230 /** 231 * Returns a ringtone URI, based on the string and version #. 232 */ 233 @NeededForTesting getRingtoneUriFromString(String str, int currentVersion)234 public static Uri getRingtoneUriFromString(String str, int currentVersion) { 235 if (str != null) { 236 if (isNewerThanM(currentVersion) && TextUtils.isEmpty(str)) return null; 237 return Uri.parse(str); 238 } 239 return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); 240 } 241 isNewerThanM(int currentVersion)242 private static boolean isNewerThanM(int currentVersion) { 243 return currentVersion > Build.VERSION_CODES.M; 244 } 245 246 /** Returns the {@link Photo#PHOTO_FILE_ID} from the given ValuesDelta. */ getPhotoFileId(ValuesDelta valuesDelta)247 public static Long getPhotoFileId(ValuesDelta valuesDelta) { 248 if (valuesDelta == null) return null; 249 if (valuesDelta.getAfter() == null || valuesDelta.getAfter().get(Photo.PHOTO) == null) { 250 return valuesDelta.getAsLong(Photo.PHOTO_FILE_ID); 251 } 252 return null; 253 } 254 255 /** Binds the full resolution image at the given Uri to the provided ImageView. */ loadPhoto(ContactPhotoManager contactPhotoManager, ImageView imageView, Uri photoUri)256 static void loadPhoto(ContactPhotoManager contactPhotoManager, ImageView imageView, 257 Uri photoUri) { 258 final DefaultImageProvider fallbackToPreviousImage = new DefaultImageProvider() { 259 @Override 260 public void applyDefaultImage(ImageView view, int extent, boolean darkTheme, 261 DefaultImageRequest defaultImageRequest) { 262 // Before we finish setting the full sized image, don't change the current 263 // image that is set in any way. 264 } 265 }; 266 contactPhotoManager.loadPhoto(imageView, photoUri, imageView.getWidth(), 267 /* darkTheme =*/ false, /* isCircular =*/ false, 268 /* defaultImageRequest =*/ null, fallbackToPreviousImage); 269 } 270 271 /** Decodes the Bitmap from the photo bytes from the given ValuesDelta. */ getPhotoBitmap(ValuesDelta valuesDelta)272 public static Bitmap getPhotoBitmap(ValuesDelta valuesDelta) { 273 if (valuesDelta == null) return null; 274 final byte[] bytes = valuesDelta.getAsByteArray(Photo.PHOTO); 275 if (bytes == null) return null; 276 return BitmapFactory.decodeByteArray(bytes, /* offset =*/ 0, bytes.length); 277 } 278 279 /** Binds the default avatar to the given ImageView and tints it to match QuickContacts. */ setDefaultPhoto(ImageView imageView , Resources resources, MaterialPalette materialPalette)280 public static void setDefaultPhoto(ImageView imageView , Resources resources, 281 MaterialPalette materialPalette) { 282 // Use the default avatar drawable 283 imageView.setImageDrawable(ContactPhotoManager.getDefaultAvatarDrawableForContact( 284 resources, /* hires =*/ false, /* defaultImageRequest =*/ null)); 285 286 // Tint it to match the quick contacts 287 if (imageView instanceof QuickContactImageView) { 288 ((QuickContactImageView) imageView).setTint(materialPalette == null 289 ? getDefaultPrimaryAndSecondaryColors(resources).mPrimaryColor 290 : materialPalette.mPrimaryColor); 291 } 292 } 293 294 /** Returns compressed bitmap bytes from the given Uri, scaled to the thumbnail dimensions. */ getCompressedThumbnailBitmapBytes(Context context, Uri uri)295 public static byte[] getCompressedThumbnailBitmapBytes(Context context, Uri uri) 296 throws FileNotFoundException { 297 final Bitmap bitmap = ContactPhotoUtils.getBitmapFromUri(context, uri); 298 final int size = ContactsUtils.getThumbnailSize(context); 299 final Bitmap bitmapScaled = Bitmap.createScaledBitmap( 300 bitmap, size, size, /* filter =*/ false); 301 return ContactPhotoUtils.compressBitmap(bitmapScaled); 302 } 303 } 304