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