Home | History | Annotate | Download | only in util
      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.util;
     17 
     18 import android.graphics.Color;
     19 import android.net.Uri;
     20 import android.net.Uri.Builder;
     21 import android.support.annotation.NonNull;
     22 import android.support.annotation.Nullable;
     23 import android.text.TextUtils;
     24 
     25 import com.android.messaging.datamodel.data.ParticipantData;
     26 
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 
     30 /**
     31  * A helper utility for creating {@link android.net.Uri}s to describe what avatar to fetch or
     32  * generate and will help verify and extract information from avatar {@link android.net.Uri}s.
     33  *
     34  * There are three types of avatar {@link android.net.Uri}.
     35  *
     36  * 1) Group Avatars - These are avatars which are used to represent a group conversation. Group
     37  * avatars uris are basically multiple avatar uri which can be any of the below types but not
     38  * another group avatar. The group avatars can hold anywhere from two to four avatars uri and can
     39  * be in any of the following format
     40  * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>
     41  * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>
     42  * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>&p=<avatarUri4>
     43  *
     44  * 2) Local Resource - A local resource avatar is use when there is a profile photo for the
     45  * participant. This can be any local resource.
     46  *
     47  * 3) Letter Tile - A letter tile is used when a participant has a name but no profile photo. A
     48  * letter tile will contain the first code point of the participant's name and a background color
     49  * based on the hash of the participant's full name. Letter tiles will be in the following format.
     50  * messaging://avatar/l?n=<fullName>
     51  *
     52  * 4) Default Avatars - These are avatars are used when the participant has no profile photo or
     53  * name. In these cases we use the default person icon with a color background. The color
     54  * background is based on a hash of the normalized phone number.
     55  *
     56  * 5) Default Background Avatars - This is a special case for Default Avatars where we use the
     57  * default background color for the default avatar.
     58  *
     59  * 6) SIM Selector Avatars - These are avatars used in the SIM selector. This may either be a
     60  * regular local resource avatar (2) or an avatar with a SIM identifier (i.e. SIM background with
     61  * a letter or a slot number).
     62  */
     63 public class AvatarUriUtil {
     64     private static final int MAX_GROUP_PARTICIPANTS = 4;
     65 
     66     public static final String TYPE_GROUP_URI = "g";
     67     public static final String TYPE_LOCAL_RESOURCE_URI = "r";
     68     public static final String TYPE_LETTER_TILE_URI = "l";
     69     public static final String TYPE_DEFAULT_URI = "d";
     70     public static final String TYPE_DEFAULT_BACKGROUND_URI = "b";
     71     public static final String TYPE_SIM_SELECTOR_URI = "s";
     72 
     73     private static final String SCHEME = "messaging";
     74     private static final String AUTHORITY = "avatar";
     75     private static final String PARAM_NAME = "n";
     76     private static final String PARAM_PRIMARY_URI = "m";
     77     private static final String PARAM_FALLBACK_URI = "f";
     78     private static final String PARAM_PARTICIPANT = "p";
     79     private static final String PARAM_IDENTIFIER = "i";
     80     private static final String PARAM_SIM_COLOR = "c";
     81     private static final String PARAM_SIM_SELECTED = "s";
     82     private static final String PARAM_SIM_INCOMING = "g";
     83 
     84     public static final Uri DEFAULT_BACKGROUND_AVATAR = new Uri.Builder().scheme(SCHEME)
     85             .authority(AUTHORITY).appendPath(TYPE_DEFAULT_BACKGROUND_URI).build();
     86 
     87     private static final Uri BLANK_SIM_INDICATOR_INCOMING_URI = createSimIconUri("",
     88             false /* selected */, Color.TRANSPARENT, true /* incoming */);
     89     private static final Uri BLANK_SIM_INDICATOR_OUTGOING_URI = createSimIconUri("",
     90             false /* selected */, Color.TRANSPARENT, false /* incoming */);
     91 
     92     /**
     93      * Creates an avatar uri based on a list of ParticipantData. The list of participants may not
     94      * be null or empty. Depending on the size of the list either a group avatar uri will be create
     95      * or an individual's avatar will be created. This will never return a null uri.
     96      */
     97     public static Uri createAvatarUri(@NonNull final List<ParticipantData> participants) {
     98         Assert.notNull(participants);
     99         Assert.isTrue(!participants.isEmpty());
    100 
    101         if (participants.size() == 1) {
    102             return createAvatarUri(participants.get(0));
    103         }
    104 
    105         final int numParticipants = Math.min(participants.size(), MAX_GROUP_PARTICIPANTS);
    106         final ArrayList<Uri> avatarUris = new ArrayList<Uri>(numParticipants);
    107         for (int i = 0; i < numParticipants; i++) {
    108             avatarUris.add(createAvatarUri(participants.get(i)));
    109         }
    110         return AvatarUriUtil.joinAvatarUriToGroup(avatarUris);
    111     }
    112 
    113     /**
    114      * Joins together a list of valid avatar uri into a group uri.The list of participants may not
    115      * be null or empty. If a lit of one is given then the first element will be return back
    116      * instead of a group avatar uri. All uris in the list must be a valid avatar uri. This will
    117      * never return a null uri.
    118      */
    119     public static Uri joinAvatarUriToGroup(@NonNull final List<Uri> avatarUris) {
    120         Assert.notNull(avatarUris);
    121         Assert.isTrue(!avatarUris.isEmpty());
    122 
    123         if (avatarUris.size() == 1) {
    124             final Uri firstAvatar = avatarUris.get(0);
    125             Assert.isTrue(AvatarUriUtil.isAvatarUri(firstAvatar));
    126             return firstAvatar;
    127         }
    128 
    129         final Builder builder = new Builder();
    130         builder.scheme(SCHEME);
    131         builder.authority(AUTHORITY);
    132         builder.appendPath(TYPE_GROUP_URI);
    133         final int numParticipants = Math.min(avatarUris.size(), MAX_GROUP_PARTICIPANTS);
    134         for (int i = 0; i < numParticipants; i++) {
    135             final Uri uri = avatarUris.get(i);
    136             Assert.notNull(uri);
    137             Assert.isTrue(UriUtil.isLocalResourceUri(uri) || AvatarUriUtil.isAvatarUri(uri));
    138             builder.appendQueryParameter(PARAM_PARTICIPANT, uri.toString());
    139         }
    140         return builder.build();
    141     }
    142 
    143     /**
    144      * Creates an avatar uri based on ParticipantData which may not be null and expected to have
    145      * profilePhotoUri, fullName and normalizedDestination populated. This will never return a null
    146      * uri.
    147      */
    148     public static Uri createAvatarUri(@NonNull final ParticipantData participant) {
    149         Assert.notNull(participant);
    150         final String photoUriString = participant.getProfilePhotoUri();
    151         final Uri profilePhotoUri = (photoUriString == null) ? null : Uri.parse(photoUriString);
    152         final String name = participant.getFullName();
    153         final String destination = participant.getNormalizedDestination();
    154         final String contactLookupKey = participant.getLookupKey();
    155         return createAvatarUri(profilePhotoUri, name, destination, contactLookupKey);
    156     }
    157 
    158     /**
    159      * Creates an avatar uri based on a the input data.
    160      */
    161     public static Uri createAvatarUri(final Uri profilePhotoUri, final CharSequence name,
    162             final String defaultIdentifier, final String contactLookupKey) {
    163         Uri generatedUri;
    164         if (!TextUtils.isEmpty(name) && isValidFirstCharacter(name)) {
    165             generatedUri = AvatarUriUtil.fromName(name, contactLookupKey);
    166         } else {
    167             final String identifier = TextUtils.isEmpty(contactLookupKey)
    168                     ? defaultIdentifier : contactLookupKey;
    169             generatedUri = AvatarUriUtil.fromIdentifier(identifier);
    170         }
    171 
    172         if (profilePhotoUri != null) {
    173             if (UriUtil.isLocalResourceUri(profilePhotoUri)) {
    174                 return fromLocalResourceWithFallback(profilePhotoUri, generatedUri);
    175             } else {
    176                 return profilePhotoUri;
    177             }
    178         } else {
    179             return generatedUri;
    180         }
    181     }
    182 
    183     public static boolean isValidFirstCharacter(final CharSequence name) {
    184         final char c = name.charAt(0);
    185         return c != '+';
    186     }
    187 
    188     /**
    189      * Creates an avatar URI used for the SIM selector.
    190      * @param participantData the self participant data for an <i>active</i> SIM
    191      * @param slotIdentifier when null, this will simply use a regular avatar; otherwise, the
    192      *        first letter of slotIdentifier will be used for the icon.
    193      * @param selected is this the currently selected SIM?
    194      * @param incoming is this for an incoming message or outgoing message?
    195      */
    196     public static Uri createAvatarUri(@NonNull final ParticipantData participantData,
    197             @Nullable final String slotIdentifier, final boolean selected, final boolean incoming) {
    198         Assert.notNull(participantData);
    199         Assert.isTrue(participantData.isActiveSubscription());
    200         Assert.isTrue(!TextUtils.isEmpty(slotIdentifier) ||
    201                 !TextUtils.isEmpty(participantData.getProfilePhotoUri()));
    202         if (TextUtils.isEmpty(slotIdentifier)) {
    203             return createAvatarUri(participantData);
    204         }
    205 
    206         return createSimIconUri(slotIdentifier, selected, participantData.getSubscriptionColor(),
    207                 incoming);
    208     }
    209 
    210     private static Uri createSimIconUri(final String slotIdentifier, final boolean selected,
    211             final int subColor, final boolean incoming) {
    212         final Builder builder = new Builder();
    213         builder.scheme(SCHEME);
    214         builder.authority(AUTHORITY);
    215         builder.appendPath(TYPE_SIM_SELECTOR_URI);
    216         builder.appendQueryParameter(PARAM_IDENTIFIER, slotIdentifier);
    217         builder.appendQueryParameter(PARAM_SIM_COLOR, String.valueOf(subColor));
    218         builder.appendQueryParameter(PARAM_SIM_SELECTED, String.valueOf(selected));
    219         builder.appendQueryParameter(PARAM_SIM_INCOMING, String.valueOf(incoming));
    220         return builder.build();
    221     }
    222 
    223     public static Uri getBlankSimIndicatorUri(final boolean incoming) {
    224         return incoming ? BLANK_SIM_INDICATOR_INCOMING_URI : BLANK_SIM_INDICATOR_OUTGOING_URI;
    225     }
    226 
    227     /**
    228      * Creates an avatar uri from the given local resource Uri, followed by a fallback Uri in case
    229      * the local resource one could not be loaded.
    230      */
    231     private static Uri fromLocalResourceWithFallback(@NonNull final Uri profilePhotoUri,
    232             @NonNull Uri fallbackUri) {
    233         Assert.notNull(profilePhotoUri);
    234         Assert.notNull(fallbackUri);
    235         final Builder builder = new Builder();
    236         builder.scheme(SCHEME);
    237         builder.authority(AUTHORITY);
    238         builder.appendPath(TYPE_LOCAL_RESOURCE_URI);
    239         builder.appendQueryParameter(PARAM_PRIMARY_URI, profilePhotoUri.toString());
    240         builder.appendQueryParameter(PARAM_FALLBACK_URI, fallbackUri.toString());
    241         return builder.build();
    242     }
    243 
    244     private static Uri fromName(@NonNull final CharSequence name, final String contactLookupKey) {
    245         Assert.notNull(name);
    246         final Builder builder = new Builder();
    247         builder.scheme(SCHEME);
    248         builder.authority(AUTHORITY);
    249         builder.appendPath(TYPE_LETTER_TILE_URI);
    250         final String nameString = String.valueOf(name);
    251         builder.appendQueryParameter(PARAM_NAME, nameString);
    252         final String identifier =
    253                 TextUtils.isEmpty(contactLookupKey) ? nameString : contactLookupKey;
    254         builder.appendQueryParameter(PARAM_IDENTIFIER, identifier);
    255         return builder.build();
    256     }
    257 
    258     private static Uri fromIdentifier(@NonNull final String identifier) {
    259         final Builder builder = new Builder();
    260         builder.scheme(SCHEME);
    261         builder.authority(AUTHORITY);
    262         builder.appendPath(TYPE_DEFAULT_URI);
    263         builder.appendQueryParameter(PARAM_IDENTIFIER, identifier);
    264         return builder.build();
    265     }
    266 
    267     public static boolean isAvatarUri(@NonNull final Uri uri) {
    268         Assert.notNull(uri);
    269         return uri != null && TextUtils.equals(SCHEME, uri.getScheme()) &&
    270                 TextUtils.equals(AUTHORITY, uri.getAuthority());
    271     }
    272 
    273     public static String getAvatarType(@NonNull final Uri uri) {
    274         Assert.notNull(uri);
    275         final List<String> path = uri.getPathSegments();
    276         return path.isEmpty() ? null : path.get(0);
    277     }
    278 
    279     public static String getIdentifier(@NonNull final Uri uri) {
    280         Assert.notNull(uri);
    281         return uri.getQueryParameter(PARAM_IDENTIFIER);
    282     }
    283 
    284     public static String getName(@NonNull final Uri uri) {
    285         Assert.notNull(uri);
    286         return uri.getQueryParameter(PARAM_NAME);
    287     }
    288 
    289     public static List<String> getGroupParticipantUris(@NonNull final Uri uri) {
    290         Assert.notNull(uri);
    291         return uri.getQueryParameters(PARAM_PARTICIPANT);
    292     }
    293 
    294     public static int getSimColor(@NonNull final Uri uri) {
    295         Assert.notNull(uri);
    296         return Integer.valueOf(uri.getQueryParameter(PARAM_SIM_COLOR));
    297     }
    298 
    299     public static boolean getSimSelected(@NonNull final Uri uri) {
    300         Assert.notNull(uri);
    301         return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_SELECTED));
    302     }
    303 
    304     public static boolean getSimIncoming(@NonNull final Uri uri) {
    305         Assert.notNull(uri);
    306         return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_INCOMING));
    307     }
    308 
    309     public static Uri getPrimaryUri(@NonNull final Uri uri) {
    310         Assert.notNull(uri);
    311         final String primaryUriString = uri.getQueryParameter(PARAM_PRIMARY_URI);
    312         return primaryUriString == null ? null : Uri.parse(primaryUriString);
    313     }
    314 
    315     public static Uri getFallbackUri(@NonNull final Uri uri) {
    316         Assert.notNull(uri);
    317         final String fallbackUriString = uri.getQueryParameter(PARAM_FALLBACK_URI);
    318         return fallbackUriString == null ? null : Uri.parse(fallbackUriString);
    319     }
    320 }
    321