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