Home | History | Annotate | Download | only in data
      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 
     17 package com.android.messaging.datamodel.data;
     18 
     19 import android.content.ContentValues;
     20 import android.content.res.Resources;
     21 import android.database.Cursor;
     22 import android.graphics.Color;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.support.v7.mms.MmsManager;
     26 import android.telephony.SubscriptionInfo;
     27 import android.text.TextUtils;
     28 
     29 import com.android.ex.chips.RecipientEntry;
     30 import com.android.messaging.Factory;
     31 import com.android.messaging.R;
     32 import com.android.messaging.datamodel.DatabaseHelper;
     33 import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
     34 import com.android.messaging.datamodel.DatabaseWrapper;
     35 import com.android.messaging.sms.MmsSmsUtils;
     36 import com.android.messaging.util.Assert;
     37 import com.android.messaging.util.PhoneUtils;
     38 import com.android.messaging.util.TextUtil;
     39 
     40 /**
     41  * A class that encapsulates all of the data for a specific participant in a conversation.
     42  */
     43 public class ParticipantData implements Parcelable {
     44     // We always use -1 as default/invalid sub id although system may give us anything negative
     45     public static final int DEFAULT_SELF_SUB_ID = MmsManager.DEFAULT_SUB_ID;
     46 
     47     // This needs to be something apart from valid or DEFAULT_SELF_SUB_ID
     48     public static final int OTHER_THAN_SELF_SUB_ID = DEFAULT_SELF_SUB_ID - 1;
     49 
     50     // Active slot ids are non-negative. Using -1 to designate to inactive self participants.
     51     public static final int INVALID_SLOT_ID = -1;
     52 
     53     // TODO: may make sense to move this to common place?
     54     public static final long PARTICIPANT_CONTACT_ID_NOT_RESOLVED = -1;
     55     public static final long PARTICIPANT_CONTACT_ID_NOT_FOUND = -2;
     56 
     57     public static class ParticipantsQuery {
     58         public static final String[] PROJECTION = new String[] {
     59             ParticipantColumns._ID,
     60             ParticipantColumns.SUB_ID,
     61             ParticipantColumns.SIM_SLOT_ID,
     62             ParticipantColumns.NORMALIZED_DESTINATION,
     63             ParticipantColumns.SEND_DESTINATION,
     64             ParticipantColumns.DISPLAY_DESTINATION,
     65             ParticipantColumns.FULL_NAME,
     66             ParticipantColumns.FIRST_NAME,
     67             ParticipantColumns.PROFILE_PHOTO_URI,
     68             ParticipantColumns.CONTACT_ID,
     69             ParticipantColumns.LOOKUP_KEY,
     70             ParticipantColumns.BLOCKED,
     71             ParticipantColumns.SUBSCRIPTION_COLOR,
     72             ParticipantColumns.SUBSCRIPTION_NAME,
     73             ParticipantColumns.CONTACT_DESTINATION,
     74         };
     75 
     76         public static final int INDEX_ID                        = 0;
     77         public static final int INDEX_SUB_ID                    = 1;
     78         public static final int INDEX_SIM_SLOT_ID               = 2;
     79         public static final int INDEX_NORMALIZED_DESTINATION    = 3;
     80         public static final int INDEX_SEND_DESTINATION          = 4;
     81         public static final int INDEX_DISPLAY_DESTINATION       = 5;
     82         public static final int INDEX_FULL_NAME                 = 6;
     83         public static final int INDEX_FIRST_NAME                = 7;
     84         public static final int INDEX_PROFILE_PHOTO_URI         = 8;
     85         public static final int INDEX_CONTACT_ID                = 9;
     86         public static final int INDEX_LOOKUP_KEY                = 10;
     87         public static final int INDEX_BLOCKED                   = 11;
     88         public static final int INDEX_SUBSCRIPTION_COLOR        = 12;
     89         public static final int INDEX_SUBSCRIPTION_NAME         = 13;
     90         public static final int INDEX_CONTACT_DESTINATION       = 14;
     91     }
     92 
     93     /**
     94      * @return The MMS unknown sender participant entity
     95      */
     96     public static String getUnknownSenderDestination() {
     97         // This is a hard coded string rather than a localized one because we don't want it to
     98         // change when you change locale.
     99         return "\u02BCUNKNOWN_SENDER!\u02BC";
    100     }
    101 
    102     private String mParticipantId;
    103     private int mSubId;
    104     private int mSlotId;
    105     private String mNormalizedDestination;
    106     private String mSendDestination;
    107     private String mDisplayDestination;
    108     private String mContactDestination;
    109     private String mFullName;
    110     private String mFirstName;
    111     private String mProfilePhotoUri;
    112     private long mContactId;
    113     private String mLookupKey;
    114     private int mSubscriptionColor;
    115     private String mSubscriptionName;
    116     private boolean mIsEmailAddress;
    117     private boolean mBlocked;
    118 
    119     // Don't call constructor directly
    120     private ParticipantData() {
    121     }
    122 
    123     public static ParticipantData getFromCursor(final Cursor cursor) {
    124         final ParticipantData pd = new ParticipantData();
    125         pd.mParticipantId = cursor.getString(ParticipantsQuery.INDEX_ID);
    126         pd.mSubId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
    127         pd.mSlotId = cursor.getInt(ParticipantsQuery.INDEX_SIM_SLOT_ID);
    128         pd.mNormalizedDestination = cursor.getString(
    129                 ParticipantsQuery.INDEX_NORMALIZED_DESTINATION);
    130         pd.mSendDestination = cursor.getString(ParticipantsQuery.INDEX_SEND_DESTINATION);
    131         pd.mDisplayDestination = cursor.getString(ParticipantsQuery.INDEX_DISPLAY_DESTINATION);
    132         pd.mContactDestination = cursor.getString(ParticipantsQuery.INDEX_CONTACT_DESTINATION);
    133         pd.mFullName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
    134         pd.mFirstName = cursor.getString(ParticipantsQuery.INDEX_FIRST_NAME);
    135         pd.mProfilePhotoUri = cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
    136         pd.mContactId = cursor.getLong(ParticipantsQuery.INDEX_CONTACT_ID);
    137         pd.mLookupKey = cursor.getString(ParticipantsQuery.INDEX_LOOKUP_KEY);
    138         pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
    139         pd.mBlocked = cursor.getInt(ParticipantsQuery.INDEX_BLOCKED) != 0;
    140         pd.mSubscriptionColor = cursor.getInt(ParticipantsQuery.INDEX_SUBSCRIPTION_COLOR);
    141         pd.mSubscriptionName = cursor.getString(ParticipantsQuery.INDEX_SUBSCRIPTION_NAME);
    142         pd.maybeSetupUnknownSender();
    143         return pd;
    144     }
    145 
    146     public static ParticipantData getFromId(final DatabaseWrapper dbWrapper,
    147             final String participantId) {
    148         Cursor cursor = null;
    149         try {
    150             cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
    151                     ParticipantsQuery.PROJECTION,
    152                     ParticipantColumns._ID + " =?",
    153                     new String[] { participantId }, null, null, null);
    154 
    155             if (cursor.moveToFirst()) {
    156                 return ParticipantData.getFromCursor(cursor);
    157             } else {
    158                 return null;
    159             }
    160         } finally {
    161             if (cursor != null) {
    162                 cursor.close();
    163             }
    164         }
    165     }
    166 
    167     public static ParticipantData getFromRecipientEntry(final RecipientEntry recipientEntry) {
    168         final ParticipantData pd = new ParticipantData();
    169         pd.mParticipantId = null;
    170         pd.mSubId = OTHER_THAN_SELF_SUB_ID;
    171         pd.mSlotId = INVALID_SLOT_ID;
    172         pd.mSendDestination = TextUtil.replaceUnicodeDigits(recipientEntry.getDestination());
    173         pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
    174         pd.mNormalizedDestination = pd.mIsEmailAddress ?
    175                 pd.mSendDestination :
    176                 PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
    177         pd.mDisplayDestination = pd.mIsEmailAddress ?
    178                 pd.mNormalizedDestination :
    179                 PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
    180         pd.mFullName = recipientEntry.getDisplayName();
    181         pd.mFirstName = null;
    182         pd.mProfilePhotoUri = (recipientEntry.getPhotoThumbnailUri() == null) ? null :
    183                 recipientEntry.getPhotoThumbnailUri().toString();
    184         pd.mContactId = recipientEntry.getContactId();
    185         if (pd.mContactId < 0) {
    186             // ParticipantData only supports real contact ids (>=0) based on faith that the contacts
    187             // provider will continue to only use non-negative ids.  The UI uses contactId < 0 for
    188             // special handling. We convert those to 'not resolved'
    189             pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
    190         }
    191         pd.mLookupKey = recipientEntry.getLookupKey();
    192         pd.mBlocked = false;
    193         pd.mSubscriptionColor = Color.TRANSPARENT;
    194         pd.mSubscriptionName = null;
    195         pd.maybeSetupUnknownSender();
    196         return pd;
    197     }
    198 
    199     // Shared code for getFromRawPhoneBySystemLocale and getFromRawPhoneBySimLocale
    200     private static ParticipantData getFromRawPhone(final String phoneNumber) {
    201         Assert.isTrue(phoneNumber != null);
    202         final ParticipantData pd = new ParticipantData();
    203         pd.mParticipantId = null;
    204         pd.mSubId = OTHER_THAN_SELF_SUB_ID;
    205         pd.mSlotId = INVALID_SLOT_ID;
    206         pd.mSendDestination = TextUtil.replaceUnicodeDigits(phoneNumber);
    207         pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
    208         pd.mFullName = null;
    209         pd.mFirstName = null;
    210         pd.mProfilePhotoUri = null;
    211         pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
    212         pd.mLookupKey = null;
    213         pd.mBlocked = false;
    214         pd.mSubscriptionColor = Color.TRANSPARENT;
    215         pd.mSubscriptionName = null;
    216         return pd;
    217     }
    218 
    219     /**
    220      * Get an instance from a raw phone number and using system locale to normalize it.
    221      *
    222      * Use this when creating a participant that is for displaying UI and not associated
    223      * with a specific SIM. For example, when creating a conversation using user entered
    224      * phone number.
    225      *
    226      * @param phoneNumber The raw phone number
    227      * @return instance
    228      */
    229     public static ParticipantData getFromRawPhoneBySystemLocale(final String phoneNumber) {
    230         final ParticipantData pd = getFromRawPhone(phoneNumber);
    231         pd.mNormalizedDestination = pd.mIsEmailAddress ?
    232                 pd.mSendDestination :
    233                 PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
    234         pd.mDisplayDestination = pd.mIsEmailAddress ?
    235                 pd.mNormalizedDestination :
    236                 PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
    237         pd.maybeSetupUnknownSender();
    238         return pd;
    239     }
    240 
    241     /**
    242      * Get an instance from a raw phone number and using SIM or system locale to normalize it.
    243      *
    244      * Use this when creating a participant that is associated with a specific SIM. For example,
    245      * the sender of a received message or the recipient of a sending message that is already
    246      * targeted at a specific SIM.
    247      *
    248      * @param phoneNumber The raw phone number
    249      * @return instance
    250      */
    251     public static ParticipantData getFromRawPhoneBySimLocale(
    252             final String phoneNumber, final int subId) {
    253         final ParticipantData pd = getFromRawPhone(phoneNumber);
    254         pd.mNormalizedDestination = pd.mIsEmailAddress ?
    255                 pd.mSendDestination :
    256                 PhoneUtils.get(subId).getCanonicalBySimLocale(pd.mSendDestination);
    257         pd.mDisplayDestination = pd.mIsEmailAddress ?
    258                 pd.mNormalizedDestination :
    259                 PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
    260         pd.maybeSetupUnknownSender();
    261         return pd;
    262     }
    263 
    264     public static ParticipantData getSelfParticipant(final int subId) {
    265         Assert.isTrue(subId != OTHER_THAN_SELF_SUB_ID);
    266         final ParticipantData pd = new ParticipantData();
    267         pd.mParticipantId = null;
    268         pd.mSubId = subId;
    269         pd.mSlotId = INVALID_SLOT_ID;
    270         pd.mIsEmailAddress = false;
    271         pd.mSendDestination = null;
    272         pd.mNormalizedDestination = null;
    273         pd.mDisplayDestination = null;
    274         pd.mFullName = null;
    275         pd.mFirstName = null;
    276         pd.mProfilePhotoUri = null;
    277         pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
    278         pd.mLookupKey = null;
    279         pd.mBlocked = false;
    280         pd.mSubscriptionColor = Color.TRANSPARENT;
    281         pd.mSubscriptionName = null;
    282         return pd;
    283     }
    284 
    285     private void maybeSetupUnknownSender() {
    286         if (isUnknownSender()) {
    287             // Because your locale may change, we setup the display string for the unknown sender
    288             // on the fly rather than relying on the version in the database.
    289             final Resources resources = Factory.get().getApplicationContext().getResources();
    290             mDisplayDestination = resources.getString(R.string.unknown_sender);
    291             mFullName = mDisplayDestination;
    292         }
    293     }
    294 
    295     public String getNormalizedDestination() {
    296         return mNormalizedDestination;
    297     }
    298 
    299     public String getSendDestination() {
    300         return mSendDestination;
    301     }
    302 
    303     public String getDisplayDestination() {
    304         return mDisplayDestination;
    305     }
    306 
    307     public String getContactDestination() {
    308         return mContactDestination;
    309     }
    310 
    311     public String getFullName() {
    312         return mFullName;
    313     }
    314 
    315     public String getFirstName() {
    316         return mFirstName;
    317     }
    318 
    319     public String getDisplayName(final boolean preferFullName) {
    320         if (preferFullName) {
    321             // Prefer full name over first name
    322             if (!TextUtils.isEmpty(mFullName)) {
    323                 return mFullName;
    324             }
    325             if (!TextUtils.isEmpty(mFirstName)) {
    326                 return mFirstName;
    327             }
    328         } else {
    329             // Prefer first name over full name
    330             if (!TextUtils.isEmpty(mFirstName)) {
    331                 return mFirstName;
    332             }
    333             if (!TextUtils.isEmpty(mFullName)) {
    334                 return mFullName;
    335             }
    336         }
    337 
    338         // Fallback to the display destination
    339         if (!TextUtils.isEmpty(mDisplayDestination)) {
    340             return mDisplayDestination;
    341         }
    342 
    343         return Factory.get().getApplicationContext().getResources().getString(
    344                 R.string.unknown_sender);
    345     }
    346 
    347     public String getProfilePhotoUri() {
    348         return mProfilePhotoUri;
    349     }
    350 
    351     public long getContactId() {
    352         return mContactId;
    353     }
    354 
    355     public String getLookupKey() {
    356         return mLookupKey;
    357     }
    358 
    359     public boolean updatePhoneNumberForSelfIfChanged() {
    360         final String phoneNumber =
    361                 PhoneUtils.get(mSubId).getCanonicalForSelf(true/*allowOverride*/);
    362         boolean changed = false;
    363         if (isSelf() && !TextUtils.equals(phoneNumber, mNormalizedDestination)) {
    364             mNormalizedDestination = phoneNumber;
    365             mSendDestination = phoneNumber;
    366             mDisplayDestination = mIsEmailAddress ?
    367                     phoneNumber :
    368                     PhoneUtils.getDefault().formatForDisplay(phoneNumber);
    369             changed = true;
    370         }
    371         return changed;
    372     }
    373 
    374     public boolean updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo) {
    375         boolean changed = false;
    376         if (isSelf()) {
    377             if (subscriptionInfo == null) {
    378                 // The subscription is inactive. Check if the participant is still active.
    379                 if (isActiveSubscription()) {
    380                     mSlotId = INVALID_SLOT_ID;
    381                     mSubscriptionColor = Color.TRANSPARENT;
    382                     mSubscriptionName = "";
    383                     changed = true;
    384                 }
    385             } else {
    386                 final int slotId = subscriptionInfo.getSimSlotIndex();
    387                 final int color = subscriptionInfo.getIconTint();
    388                 final CharSequence name = subscriptionInfo.getDisplayName();
    389                 if (mSlotId != slotId || mSubscriptionColor != color || mSubscriptionName != name) {
    390                     mSlotId = slotId;
    391                     mSubscriptionColor = color;
    392                     mSubscriptionName = name.toString();
    393                     changed = true;
    394                 }
    395             }
    396         }
    397         return changed;
    398     }
    399 
    400     public void setFullName(final String fullName) {
    401         mFullName = fullName;
    402     }
    403 
    404     public void setFirstName(final String firstName) {
    405         mFirstName = firstName;
    406     }
    407 
    408     public void setProfilePhotoUri(final String profilePhotoUri) {
    409         mProfilePhotoUri = profilePhotoUri;
    410     }
    411 
    412     public void setContactId(final long contactId) {
    413         mContactId = contactId;
    414     }
    415 
    416     public void setLookupKey(final String lookupKey) {
    417         mLookupKey = lookupKey;
    418     }
    419 
    420     public void setSendDestination(final String destination) {
    421         mSendDestination = destination;
    422     }
    423 
    424     public void setContactDestination(final String destination) {
    425         mContactDestination = destination;
    426     }
    427 
    428     public int getSubId() {
    429         return mSubId;
    430     }
    431 
    432     /**
    433      * @return whether this sub is active. Note that {@link ParticipantData#DEFAULT_SELF_SUB_ID} is
    434      *         is considered as active if there is any active SIM.
    435      */
    436     public boolean isActiveSubscription() {
    437         return mSlotId != INVALID_SLOT_ID;
    438     }
    439 
    440     public boolean isDefaultSelf() {
    441         return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID;
    442     }
    443 
    444     public int getSlotId() {
    445         return mSlotId;
    446     }
    447 
    448     /**
    449      * Slot IDs in the subscription manager is zero-based, but we want to show it
    450      * as 1-based in UI.
    451      */
    452     public int getDisplaySlotId() {
    453         return getSlotId() + 1;
    454     }
    455 
    456     public int getSubscriptionColor() {
    457         Assert.isTrue(isActiveSubscription());
    458         // Force the alpha channel to 0xff to ensure the returned color is solid.
    459         return mSubscriptionColor | 0xff000000;
    460     }
    461 
    462     public String getSubscriptionName() {
    463         Assert.isTrue(isActiveSubscription());
    464         return mSubscriptionName;
    465     }
    466 
    467     public String getId() {
    468         return mParticipantId;
    469     }
    470 
    471     public boolean isSelf() {
    472         return (mSubId != OTHER_THAN_SELF_SUB_ID);
    473     }
    474 
    475     public boolean isEmail() {
    476         return mIsEmailAddress;
    477     }
    478 
    479     public boolean isContactIdResolved() {
    480         return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED);
    481     }
    482 
    483     public boolean isBlocked() {
    484         return mBlocked;
    485     }
    486 
    487     public boolean isUnknownSender() {
    488         final String unknownSender = ParticipantData.getUnknownSenderDestination();
    489         return (TextUtils.equals(mSendDestination, unknownSender));
    490     }
    491 
    492     public ContentValues toContentValues() {
    493         final ContentValues values = new ContentValues();
    494         values.put(ParticipantColumns.SUB_ID, mSubId);
    495         values.put(ParticipantColumns.SIM_SLOT_ID, mSlotId);
    496         values.put(DatabaseHelper.ParticipantColumns.SEND_DESTINATION, mSendDestination);
    497 
    498         if (!isUnknownSender()) {
    499             values.put(DatabaseHelper.ParticipantColumns.DISPLAY_DESTINATION, mDisplayDestination);
    500             values.put(DatabaseHelper.ParticipantColumns.NORMALIZED_DESTINATION,
    501                     mNormalizedDestination);
    502             values.put(ParticipantColumns.FULL_NAME, mFullName);
    503             values.put(ParticipantColumns.FIRST_NAME, mFirstName);
    504         }
    505 
    506         values.put(ParticipantColumns.PROFILE_PHOTO_URI, mProfilePhotoUri);
    507         values.put(ParticipantColumns.CONTACT_ID, mContactId);
    508         values.put(ParticipantColumns.LOOKUP_KEY, mLookupKey);
    509         values.put(ParticipantColumns.BLOCKED, mBlocked);
    510         values.put(ParticipantColumns.SUBSCRIPTION_COLOR, mSubscriptionColor);
    511         values.put(ParticipantColumns.SUBSCRIPTION_NAME, mSubscriptionName);
    512         return values;
    513     }
    514 
    515     public ParticipantData(final Parcel in) {
    516         mParticipantId = in.readString();
    517         mSubId = in.readInt();
    518         mSlotId = in.readInt();
    519         mNormalizedDestination = in.readString();
    520         mSendDestination = in.readString();
    521         mDisplayDestination = in.readString();
    522         mFullName = in.readString();
    523         mFirstName = in.readString();
    524         mProfilePhotoUri = in.readString();
    525         mContactId = in.readLong();
    526         mLookupKey = in.readString();
    527         mIsEmailAddress = in.readInt() != 0;
    528         mBlocked = in.readInt() != 0;
    529         mSubscriptionColor = in.readInt();
    530         mSubscriptionName = in.readString();
    531     }
    532 
    533     @Override
    534     public int describeContents() {
    535         return 0;
    536     }
    537 
    538     @Override
    539     public void writeToParcel(final Parcel dest, final int flags) {
    540         dest.writeString(mParticipantId);
    541         dest.writeInt(mSubId);
    542         dest.writeInt(mSlotId);
    543         dest.writeString(mNormalizedDestination);
    544         dest.writeString(mSendDestination);
    545         dest.writeString(mDisplayDestination);
    546         dest.writeString(mFullName);
    547         dest.writeString(mFirstName);
    548         dest.writeString(mProfilePhotoUri);
    549         dest.writeLong(mContactId);
    550         dest.writeString(mLookupKey);
    551         dest.writeInt(mIsEmailAddress ? 1 : 0);
    552         dest.writeInt(mBlocked ? 1 : 0);
    553         dest.writeInt(mSubscriptionColor);
    554         dest.writeString(mSubscriptionName);
    555     }
    556 
    557     public static final Parcelable.Creator<ParticipantData> CREATOR
    558     = new Parcelable.Creator<ParticipantData>() {
    559         @Override
    560         public ParticipantData createFromParcel(final Parcel in) {
    561             return new ParticipantData(in);
    562         }
    563 
    564         @Override
    565         public ParticipantData[] newArray(final int size) {
    566             return new ParticipantData[size];
    567         }
    568     };
    569 }
    570