• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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       */
getUnknownSenderDestination()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
ParticipantData()120      private ParticipantData() {
121      }
122  
getFromCursor(final Cursor cursor)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  
getFromId(final DatabaseWrapper dbWrapper, final String participantId)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  
getFromRecipientEntry(final RecipientEntry recipientEntry)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
getFromRawPhone(final String phoneNumber)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       */
getFromRawPhoneBySystemLocale(final String phoneNumber)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       */
getFromRawPhoneBySimLocale( final String phoneNumber, final int subId)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  
getSelfParticipant(final int subId)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  
maybeSetupUnknownSender()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  
getNormalizedDestination()295      public String getNormalizedDestination() {
296          return mNormalizedDestination;
297      }
298  
getSendDestination()299      public String getSendDestination() {
300          return mSendDestination;
301      }
302  
getDisplayDestination()303      public String getDisplayDestination() {
304          return mDisplayDestination;
305      }
306  
getContactDestination()307      public String getContactDestination() {
308          return mContactDestination;
309      }
310  
getFullName()311      public String getFullName() {
312          return mFullName;
313      }
314  
getFirstName()315      public String getFirstName() {
316          return mFirstName;
317      }
318  
getDisplayName(final boolean preferFullName)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  
getProfilePhotoUri()347      public String getProfilePhotoUri() {
348          return mProfilePhotoUri;
349      }
350  
getContactId()351      public long getContactId() {
352          return mContactId;
353      }
354  
getLookupKey()355      public String getLookupKey() {
356          return mLookupKey;
357      }
358  
updatePhoneNumberForSelfIfChanged()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  
updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo)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  
setFullName(final String fullName)400      public void setFullName(final String fullName) {
401          mFullName = fullName;
402      }
403  
setFirstName(final String firstName)404      public void setFirstName(final String firstName) {
405          mFirstName = firstName;
406      }
407  
setProfilePhotoUri(final String profilePhotoUri)408      public void setProfilePhotoUri(final String profilePhotoUri) {
409          mProfilePhotoUri = profilePhotoUri;
410      }
411  
setContactId(final long contactId)412      public void setContactId(final long contactId) {
413          mContactId = contactId;
414      }
415  
setLookupKey(final String lookupKey)416      public void setLookupKey(final String lookupKey) {
417          mLookupKey = lookupKey;
418      }
419  
setSendDestination(final String destination)420      public void setSendDestination(final String destination) {
421          mSendDestination = destination;
422      }
423  
setContactDestination(final String destination)424      public void setContactDestination(final String destination) {
425          mContactDestination = destination;
426      }
427  
getSubId()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       */
isActiveSubscription()436      public boolean isActiveSubscription() {
437          return mSlotId != INVALID_SLOT_ID;
438      }
439  
isDefaultSelf()440      public boolean isDefaultSelf() {
441          return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID;
442      }
443  
getSlotId()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       */
getDisplaySlotId()452      public int getDisplaySlotId() {
453          return getSlotId() + 1;
454      }
455  
getSubscriptionColor()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  
getSubscriptionName()462      public String getSubscriptionName() {
463          Assert.isTrue(isActiveSubscription());
464          return mSubscriptionName;
465      }
466  
getId()467      public String getId() {
468          return mParticipantId;
469      }
470  
isSelf()471      public boolean isSelf() {
472          return (mSubId != OTHER_THAN_SELF_SUB_ID);
473      }
474  
isEmail()475      public boolean isEmail() {
476          return mIsEmailAddress;
477      }
478  
isContactIdResolved()479      public boolean isContactIdResolved() {
480          return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED);
481      }
482  
isBlocked()483      public boolean isBlocked() {
484          return mBlocked;
485      }
486  
isUnknownSender()487      public boolean isUnknownSender() {
488          final String unknownSender = ParticipantData.getUnknownSenderDestination();
489          return (TextUtils.equals(mSendDestination, unknownSender));
490      }
491  
toContentValues()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  
ParticipantData(final Parcel in)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
describeContents()534      public int describeContents() {
535          return 0;
536      }
537  
538      @Override
writeToParcel(final Parcel dest, final int flags)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