Revert "Initial checkin of AOSP Messaging app."
This reverts commit 461a34b466.
Change-Id: Iac4ca77eeaa94989e91dead49a7959c905bd3078
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.util.Assert;
|
||||
|
||||
/**
|
||||
* Services data needs for BlockedParticipantsFragment
|
||||
*/
|
||||
public class BlockedParticipantsData extends BindableData implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
public interface BlockedParticipantsDataListener {
|
||||
public void onBlockedParticipantsCursorUpdated(final Cursor cursor);
|
||||
}
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
private static final int BLOCKED_PARTICIPANTS_LOADER = 1;
|
||||
private final Context mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private BlockedParticipantsDataListener mListener;
|
||||
|
||||
public BlockedParticipantsData(final Context context,
|
||||
final BlockedParticipantsDataListener listener) {
|
||||
mContext = context;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
Assert.isTrue(id == BLOCKED_PARTICIPANTS_LOADER);
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
final Uri uri = MessagingContentProvider.PARTICIPANTS_URI;
|
||||
return new BoundCursorLoader(bindingId, mContext, uri,
|
||||
ParticipantData.ParticipantsQuery.PROJECTION,
|
||||
ParticipantColumns.BLOCKED + "=1", null, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) {
|
||||
Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER);
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
Assert.isTrue(isBound(cursorLoader.getBindingId()));
|
||||
mListener.onBlockedParticipantsCursorUpdated(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> loader) {
|
||||
Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER);
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
Assert.isTrue(isBound(cursorLoader.getBindingId()));
|
||||
mListener.onBlockedParticipantsCursorUpdated(null);
|
||||
}
|
||||
|
||||
public void init(final LoaderManager loaderManager,
|
||||
final BindingBase<BlockedParticipantsData> binding) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(BINDING_ID, binding.getBindingId());
|
||||
mLoaderManager = loaderManager;
|
||||
mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_LOADER, args, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ParticipantListItemData createParticipantListItemData(Cursor cursor) {
|
||||
return new ParticipantListItemData(ParticipantData.getFromCursor(cursor));
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract.DisplayNameSources;
|
||||
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ContactRecipientEntryUtils;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
|
||||
/**
|
||||
* Data model object used to power ContactListItemViews, which may be displayed either in
|
||||
* our contact list, or in the chips UI search drop down presented by ContactDropdownLayouter.
|
||||
*/
|
||||
public class ContactListItemData {
|
||||
// Keeps the contact data in the form of RecipientEntry that RecipientEditTextView can
|
||||
// directly use.
|
||||
private RecipientEntry mRecipientEntry;
|
||||
|
||||
private CharSequence mStyledName;
|
||||
private CharSequence mStyledDestination;
|
||||
|
||||
// If this contact is the first in the list for its first letter, then this will be the
|
||||
// first letter, otherwise this is null.
|
||||
private String mAlphabetHeader;
|
||||
|
||||
// Is the contact the only item in the list (happens when the user clicks on an
|
||||
// existing chip for which we show full contact detail for the selected contact).
|
||||
private boolean mSingleRecipient;
|
||||
|
||||
/**
|
||||
* Bind to a contact cursor in the contact list.
|
||||
*/
|
||||
public void bind(final Cursor cursor, final String alphabetHeader) {
|
||||
final long dataId = cursor.getLong(ContactUtil.INDEX_DATA_ID);
|
||||
final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
|
||||
final String lookupKey = cursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
|
||||
final String displayName = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
|
||||
final String photoThumbnailUri = cursor.getString(ContactUtil.INDEX_PHOTO_URI);
|
||||
final String destination = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL);
|
||||
final int destinationType = cursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
|
||||
final String destinationLabel = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
|
||||
mStyledName = null;
|
||||
mStyledDestination = null;
|
||||
mAlphabetHeader = alphabetHeader;
|
||||
mSingleRecipient = false;
|
||||
|
||||
// Check whether this contact is first level (i.e. whether it's the first entry of this
|
||||
// contact in the contact list).
|
||||
boolean isFirstLevel = true;
|
||||
if (!cursor.isFirst() && cursor.moveToPrevious()) {
|
||||
final long contactIdPrevious = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
|
||||
if (contactId == contactIdPrevious) {
|
||||
isFirstLevel = false;
|
||||
}
|
||||
cursor.moveToNext();
|
||||
}
|
||||
|
||||
mRecipientEntry = ContactUtil.createRecipientEntry(displayName,
|
||||
DisplayNameSources.STRUCTURED_NAME, destination, destinationType, destinationLabel,
|
||||
contactId, lookupKey, dataId, photoThumbnailUri, isFirstLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to a RecipientEntry produced by the chips text view in the search drop down, plus
|
||||
* optional styled name & destination for showing bold search match.
|
||||
*/
|
||||
public void bind(final RecipientEntry entry, final CharSequence styledName,
|
||||
final CharSequence styledDestination, final boolean singleRecipient) {
|
||||
Assert.isTrue(entry.isValid());
|
||||
mRecipientEntry = entry;
|
||||
mStyledName = styledName;
|
||||
mStyledDestination = styledDestination;
|
||||
mAlphabetHeader = null;
|
||||
mSingleRecipient = singleRecipient;
|
||||
}
|
||||
|
||||
public CharSequence getDisplayName() {
|
||||
final CharSequence displayName = mStyledName != null ? mStyledName :
|
||||
ContactRecipientEntryUtils.getDisplayNameForContactList(mRecipientEntry);
|
||||
return displayName == null ? "" : displayName;
|
||||
}
|
||||
|
||||
public Uri getPhotoThumbnailUri() {
|
||||
return mRecipientEntry.getPhotoThumbnailUri() == null ? null :
|
||||
mRecipientEntry.getPhotoThumbnailUri();
|
||||
}
|
||||
|
||||
public CharSequence getDestination() {
|
||||
final CharSequence destination = mStyledDestination != null ?
|
||||
mStyledDestination : ContactRecipientEntryUtils.formatDestination(mRecipientEntry);
|
||||
return destination == null ? "" : destination;
|
||||
}
|
||||
|
||||
public int getDestinationType() {
|
||||
return mRecipientEntry.getDestinationType();
|
||||
}
|
||||
|
||||
public String getDestinationLabel() {
|
||||
return mRecipientEntry.getDestinationLabel();
|
||||
}
|
||||
|
||||
public long getContactId() {
|
||||
return mRecipientEntry.getContactId();
|
||||
}
|
||||
|
||||
public String getLookupKey() {
|
||||
return mRecipientEntry.getLookupKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this item is "first-level," i.e. whether it's the first entry of the contact
|
||||
* that it represents in the list. For example, if John Smith has 3 different phone numbers,
|
||||
* then the first number is considered first-level, while the other two are considered
|
||||
* second-level.
|
||||
*/
|
||||
public boolean getIsFirstLevel() {
|
||||
// Treat the item as first level if it's a top-level recipient entry, or if it's the only
|
||||
// item in the list.
|
||||
return mRecipientEntry.isFirstLevel() || mSingleRecipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this item is simple, i.e. it has only avatar and a display name with phone number
|
||||
* embedded so we can hide everything else.
|
||||
*/
|
||||
public boolean getIsSimpleContactItem() {
|
||||
return ContactRecipientEntryUtils.isAvatarAndNumberOnlyContact(mRecipientEntry) ||
|
||||
ContactRecipientEntryUtils.isSendToDestinationContact(mRecipientEntry);
|
||||
}
|
||||
|
||||
public String getAlphabetHeader() {
|
||||
return mAlphabetHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a RecipientEntry instance readily usable by the RecipientEditTextView.
|
||||
*/
|
||||
public RecipientEntry getRecipientEntry() {
|
||||
return mRecipientEntry;
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.FrequentContactsCursorBuilder;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.sms.MmsConfig;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
/**
|
||||
* Class to access phone contacts.
|
||||
* The caller is responsible for ensuring that the app has READ_CONTACTS permission (see
|
||||
* {@link ContactUtil#hasReadContactsPermission()}) before instantiating this class.
|
||||
*/
|
||||
public class ContactPickerData extends BindableData implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
public interface ContactPickerDataListener {
|
||||
void onAllContactsCursorUpdated(Cursor data);
|
||||
void onFrequentContactsCursorUpdated(Cursor data);
|
||||
void onContactCustomColorLoaded(ContactPickerData data);
|
||||
}
|
||||
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
private final Context mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private ContactPickerDataListener mListener;
|
||||
private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder;
|
||||
|
||||
public ContactPickerData(final Context context, final ContactPickerDataListener listener) {
|
||||
mListener = listener;
|
||||
mContext = context;
|
||||
mFrequentContactsCursorBuilder = new FrequentContactsCursorBuilder();
|
||||
}
|
||||
|
||||
private static final int ALL_CONTACTS_LOADER = 1;
|
||||
private static final int FREQUENT_CONTACTS_LOADER = 2;
|
||||
private static final int PARTICIPANT_LOADER = 3;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
switch (id) {
|
||||
case ALL_CONTACTS_LOADER:
|
||||
return ContactUtil.getPhones(mContext)
|
||||
.createBoundCursorLoader(bindingId);
|
||||
case FREQUENT_CONTACTS_LOADER:
|
||||
return ContactUtil.getFrequentContacts(mContext)
|
||||
.createBoundCursorLoader(bindingId);
|
||||
case PARTICIPANT_LOADER:
|
||||
return new BoundCursorLoader(bindingId, mContext,
|
||||
MessagingContentProvider.PARTICIPANTS_URI,
|
||||
ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
|
||||
default:
|
||||
Assert.fail("Unknown loader id for contact picker!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the contacts list");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
if (isBound(cursorLoader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case ALL_CONTACTS_LOADER:
|
||||
mListener.onAllContactsCursorUpdated(data);
|
||||
mFrequentContactsCursorBuilder.setAllContacts(data);
|
||||
break;
|
||||
case FREQUENT_CONTACTS_LOADER:
|
||||
mFrequentContactsCursorBuilder.setFrequents(data);
|
||||
break;
|
||||
case PARTICIPANT_LOADER:
|
||||
mListener.onContactCustomColorLoaded(this);
|
||||
break;
|
||||
default:
|
||||
Assert.fail("Unknown loader id for contact picker!");
|
||||
break;
|
||||
}
|
||||
|
||||
if (loader.getId() != PARTICIPANT_LOADER) {
|
||||
// The frequent contacts cursor to be used in the UI depends on results from both
|
||||
// all contacts and frequent contacts loader, and we don't know which will finish
|
||||
// first. Therefore, try to build the cursor and notify the listener if it's
|
||||
// successfully built.
|
||||
final Cursor frequentContactsCursor = mFrequentContactsCursorBuilder.build();
|
||||
if (frequentContactsCursor != null) {
|
||||
mListener.onFrequentContactsCursorUpdated(frequentContactsCursor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the contacts list");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> loader) {
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
if (isBound(cursorLoader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case ALL_CONTACTS_LOADER:
|
||||
mListener.onAllContactsCursorUpdated(null);
|
||||
mFrequentContactsCursorBuilder.setAllContacts(null);
|
||||
break;
|
||||
case FREQUENT_CONTACTS_LOADER:
|
||||
mListener.onFrequentContactsCursorUpdated(null);
|
||||
mFrequentContactsCursorBuilder.setFrequents(null);
|
||||
break;
|
||||
case PARTICIPANT_LOADER:
|
||||
mListener.onContactCustomColorLoaded(this);
|
||||
break;
|
||||
default:
|
||||
Assert.fail("Unknown loader id for contact picker!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the contacts list");
|
||||
}
|
||||
}
|
||||
|
||||
public void init(final LoaderManager loaderManager,
|
||||
final BindingBase<ContactPickerData> binding) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(BINDING_ID, binding.getBindingId());
|
||||
mLoaderManager = loaderManager;
|
||||
mLoaderManager.initLoader(ALL_CONTACTS_LOADER, args, this);
|
||||
mLoaderManager.initLoader(FREQUENT_CONTACTS_LOADER, args, this);
|
||||
mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
|
||||
|
||||
// This could be null if we bind but the caller doesn't init the BindableData
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(ALL_CONTACTS_LOADER);
|
||||
mLoaderManager.destroyLoader(FREQUENT_CONTACTS_LOADER);
|
||||
mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
mFrequentContactsCursorBuilder.resetBuilder();
|
||||
}
|
||||
|
||||
public static boolean isTooManyParticipants(final int participantCount) {
|
||||
// When creating a conversation, the conversation will be created using the system's
|
||||
// default SIM, so use the default MmsConfig's recipient limit.
|
||||
return (participantCount > MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
|
||||
.getRecipientLimit());
|
||||
}
|
||||
|
||||
public static boolean getCanAddMoreParticipants(final int participantCount) {
|
||||
// When creating a conversation, the conversation will be created using the system's
|
||||
// default SIM, so use the default MmsConfig's recipient limit.
|
||||
return (participantCount < MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
|
||||
.getRecipientLimit());
|
||||
}
|
||||
}
|
||||
@@ -1,849 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.database.CursorWrapper;
|
||||
import android.database.sqlite.SQLiteFullException;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.common.contacts.DataUsageStatUpdater;
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.BugleNotifications;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.action.DeleteConversationAction;
|
||||
import com.android.messaging.datamodel.action.DeleteMessageAction;
|
||||
import com.android.messaging.datamodel.action.InsertNewMessageAction;
|
||||
import com.android.messaging.datamodel.action.RedownloadMmsAction;
|
||||
import com.android.messaging.datamodel.action.ResendMessageAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
|
||||
import com.android.messaging.sms.MmsSmsUtils;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.Assert.RunsOnMainThread;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.widget.WidgetConversationProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConversationData extends BindableData {
|
||||
|
||||
private static final String TAG = "bugle_datamodel";
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
private static final long LAST_MESSAGE_TIMESTAMP_NaN = -1;
|
||||
private static final int MESSAGE_COUNT_NaN = -1;
|
||||
|
||||
/**
|
||||
* Takes a conversation id and a list of message ids and computes the positions
|
||||
* for each message.
|
||||
*/
|
||||
public List<Integer> getPositions(final String conversationId, final List<Long> ids) {
|
||||
final ArrayList<Integer> result = new ArrayList<Integer>();
|
||||
|
||||
if (ids.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final Cursor c = new ConversationData.ReversedCursor(
|
||||
DataModel.get().getDatabase().rawQuery(
|
||||
ConversationMessageData.getConversationMessageIdsQuerySql(),
|
||||
new String [] { conversationId }));
|
||||
if (c != null) {
|
||||
try {
|
||||
final Set<Long> idsSet = new HashSet<Long>(ids);
|
||||
if (c.moveToLast()) {
|
||||
do {
|
||||
final long messageId = c.getLong(0);
|
||||
if (idsSet.contains(messageId)) {
|
||||
result.add(c.getPosition());
|
||||
}
|
||||
} while (c.moveToPrevious());
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public interface ConversationDataListener {
|
||||
public void onConversationMessagesCursorUpdated(ConversationData data, Cursor cursor,
|
||||
@Nullable ConversationMessageData newestMessage, boolean isSync);
|
||||
public void onConversationMetadataUpdated(ConversationData data);
|
||||
public void closeConversation(String conversationId);
|
||||
public void onConversationParticipantDataLoaded(ConversationData data);
|
||||
public void onSubscriptionListDataLoaded(ConversationData data);
|
||||
}
|
||||
|
||||
private static class ReversedCursor extends CursorWrapper {
|
||||
final int mCount;
|
||||
|
||||
public ReversedCursor(final Cursor cursor) {
|
||||
super(cursor);
|
||||
mCount = cursor.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToPosition(final int position) {
|
||||
return super.moveToPosition(mCount - position - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return mCount - super.getPosition() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAfterLast() {
|
||||
return super.isBeforeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeforeFirst() {
|
||||
return super.isAfterLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFirst() {
|
||||
return super.isLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return super.isFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean move(final int offset) {
|
||||
return super.move(-offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToFirst() {
|
||||
return super.moveToLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToLast() {
|
||||
return super.moveToFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToNext() {
|
||||
return super.moveToPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToPrevious() {
|
||||
return super.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
|
||||
*/
|
||||
private class MetadataLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
Assert.equals(CONVERSATION_META_DATA_LOADER, id);
|
||||
Loader<Cursor> loader = null;
|
||||
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
final Uri uri =
|
||||
MessagingContentProvider.buildConversationMetadataUri(mConversationId);
|
||||
loader = new BoundCursorLoader(bindingId, mContext, uri,
|
||||
ConversationListItemData.PROJECTION, null, null, null);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
if (data.moveToNext()) {
|
||||
Assert.isTrue(data.getCount() == 1);
|
||||
mConversationMetadata.bind(data);
|
||||
mListeners.onConversationMetadataUpdated(ConversationData.this);
|
||||
} else {
|
||||
// Close the conversation, no meta data means conversation was deleted
|
||||
LogUtil.w(TAG, "Meta data loader returned nothing for mConversationId = " +
|
||||
mConversationId);
|
||||
mListeners.closeConversation(mConversationId);
|
||||
// Notify the widget the conversation is deleted so it can go into its
|
||||
// configure state.
|
||||
WidgetConversationProvider.notifyConversationDeleted(
|
||||
Factory.get().getApplicationContext(),
|
||||
mConversationId);
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(TAG, "Meta data loader finished after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> generic) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
// Clear the conversation meta data
|
||||
mConversationMetadata = new ConversationListItemData();
|
||||
mListeners.onConversationMetadataUpdated(ConversationData.this);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Meta data loader reset after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
|
||||
*/
|
||||
private class MessagesLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
Assert.equals(CONVERSATION_MESSAGES_LOADER, id);
|
||||
Loader<Cursor> loader = null;
|
||||
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
final Uri uri =
|
||||
MessagingContentProvider.buildConversationMessagesUri(mConversationId);
|
||||
loader = new BoundCursorLoader(bindingId, mContext, uri,
|
||||
ConversationMessageData.getProjection(), null, null, null);
|
||||
mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
|
||||
mMessageCount = MESSAGE_COUNT_NaN;
|
||||
} else {
|
||||
LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> generic, final Cursor rawData) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
// Check if we have a new message, or if we had a message sync.
|
||||
ConversationMessageData newMessage = null;
|
||||
boolean isSync = false;
|
||||
Cursor data = null;
|
||||
if (rawData != null) {
|
||||
// Note that the cursor is sorted DESC so here we reverse it.
|
||||
// This is a performance issue (improvement) for large cursors.
|
||||
data = new ReversedCursor(rawData);
|
||||
|
||||
final int messageCountOld = mMessageCount;
|
||||
mMessageCount = data.getCount();
|
||||
final ConversationMessageData lastMessage = getLastMessage(data);
|
||||
if (lastMessage != null) {
|
||||
final long lastMessageTimestampOld = mLastMessageTimestamp;
|
||||
mLastMessageTimestamp = lastMessage.getReceivedTimeStamp();
|
||||
final String lastMessageIdOld = mLastMessageId;
|
||||
mLastMessageId = lastMessage.getMessageId();
|
||||
if (TextUtils.equals(lastMessageIdOld, mLastMessageId) &&
|
||||
messageCountOld < mMessageCount) {
|
||||
// Last message stays the same (no incoming message) but message
|
||||
// count increased, which means there has been a message sync.
|
||||
isSync = true;
|
||||
} else if (messageCountOld != MESSAGE_COUNT_NaN && // Ignore initial load
|
||||
mLastMessageTimestamp != LAST_MESSAGE_TIMESTAMP_NaN &&
|
||||
mLastMessageTimestamp > lastMessageTimestampOld) {
|
||||
newMessage = lastMessage;
|
||||
}
|
||||
} else {
|
||||
mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
|
||||
}
|
||||
} else {
|
||||
mMessageCount = MESSAGE_COUNT_NaN;
|
||||
}
|
||||
|
||||
mListeners.onConversationMessagesCursorUpdated(ConversationData.this, data,
|
||||
newMessage, isSync);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Messages loader finished after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> generic) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mListeners.onConversationMessagesCursorUpdated(ConversationData.this, null, null,
|
||||
false);
|
||||
mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
|
||||
mMessageCount = MESSAGE_COUNT_NaN;
|
||||
} else {
|
||||
LogUtil.w(TAG, "Messages loader reset after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
|
||||
private ConversationMessageData getLastMessage(final Cursor cursor) {
|
||||
if (cursor != null && cursor.getCount() > 0) {
|
||||
final int position = cursor.getPosition();
|
||||
if (cursor.moveToLast()) {
|
||||
final ConversationMessageData messageData = new ConversationMessageData();
|
||||
messageData.bind(cursor);
|
||||
cursor.move(position);
|
||||
return messageData;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
|
||||
*/
|
||||
private class ParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
Assert.equals(PARTICIPANT_LOADER, id);
|
||||
Loader<Cursor> loader = null;
|
||||
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
final Uri uri =
|
||||
MessagingContentProvider.buildConversationParticipantsUri(mConversationId);
|
||||
loader = new BoundCursorLoader(bindingId, mContext, uri,
|
||||
ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Creating participant loader after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mParticipantData.bind(data);
|
||||
mListeners.onConversationParticipantDataLoaded(ConversationData.this);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Participant loader finished after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> generic) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mParticipantData.bind(null);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Participant loader reset after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
|
||||
*/
|
||||
private class SelfParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
Assert.equals(SELF_PARTICIPANT_LOADER, id);
|
||||
Loader<Cursor> loader = null;
|
||||
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
loader = new BoundCursorLoader(bindingId, mContext,
|
||||
MessagingContentProvider.PARTICIPANTS_URI,
|
||||
ParticipantData.ParticipantsQuery.PROJECTION,
|
||||
ParticipantColumns.SUB_ID + " <> ?",
|
||||
new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
|
||||
null);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Creating self loader after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mSelfParticipantsData.bind(data);
|
||||
mSubscriptionListData.bind(mSelfParticipantsData.getSelfParticipants(true));
|
||||
mListeners.onSubscriptionListDataLoaded(ConversationData.this);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Self loader finished after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> generic) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mSelfParticipantsData.bind(null);
|
||||
} else {
|
||||
LogUtil.w(TAG, "Self loader reset after unbinding mConversationId = " +
|
||||
mConversationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ConversationDataEventDispatcher mListeners;
|
||||
private final MetadataLoaderCallbacks mMetadataLoaderCallbacks;
|
||||
private final MessagesLoaderCallbacks mMessagesLoaderCallbacks;
|
||||
private final ParticipantLoaderCallbacks mParticipantsLoaderCallbacks;
|
||||
private final SelfParticipantLoaderCallbacks mSelfParticipantLoaderCallbacks;
|
||||
private final Context mContext;
|
||||
private final String mConversationId;
|
||||
private final ConversationParticipantsData mParticipantData;
|
||||
private final SelfParticipantsData mSelfParticipantsData;
|
||||
private ConversationListItemData mConversationMetadata;
|
||||
private final SubscriptionListData mSubscriptionListData;
|
||||
private LoaderManager mLoaderManager;
|
||||
private long mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
|
||||
private int mMessageCount = MESSAGE_COUNT_NaN;
|
||||
private String mLastMessageId;
|
||||
|
||||
public ConversationData(final Context context, final ConversationDataListener listener,
|
||||
final String conversationId) {
|
||||
Assert.isTrue(conversationId != null);
|
||||
mContext = context;
|
||||
mConversationId = conversationId;
|
||||
mMetadataLoaderCallbacks = new MetadataLoaderCallbacks();
|
||||
mMessagesLoaderCallbacks = new MessagesLoaderCallbacks();
|
||||
mParticipantsLoaderCallbacks = new ParticipantLoaderCallbacks();
|
||||
mSelfParticipantLoaderCallbacks = new SelfParticipantLoaderCallbacks();
|
||||
mParticipantData = new ConversationParticipantsData();
|
||||
mConversationMetadata = new ConversationListItemData();
|
||||
mSelfParticipantsData = new SelfParticipantsData();
|
||||
mSubscriptionListData = new SubscriptionListData(context);
|
||||
|
||||
mListeners = new ConversationDataEventDispatcher();
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
@RunsOnMainThread
|
||||
public void addConversationDataListener(final ConversationDataListener listener) {
|
||||
Assert.isMainThread();
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public String getConversationName() {
|
||||
return mConversationMetadata.getName();
|
||||
}
|
||||
|
||||
public boolean getIsArchived() {
|
||||
return mConversationMetadata.getIsArchived();
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return mConversationMetadata.getIcon();
|
||||
}
|
||||
|
||||
public String getConversationId() {
|
||||
return mConversationId;
|
||||
}
|
||||
|
||||
public void setFocus() {
|
||||
DataModel.get().setFocusedConversation(mConversationId);
|
||||
// As we are loading the conversation assume the user has read the messages...
|
||||
// Do this late though so that it doesn't get in the way of other actions
|
||||
BugleNotifications.markMessagesAsRead(mConversationId);
|
||||
}
|
||||
|
||||
public void unsetFocus() {
|
||||
DataModel.get().setFocusedConversation(null);
|
||||
}
|
||||
|
||||
public boolean isFocused() {
|
||||
return isBound() && DataModel.get().isFocusedConversation(mConversationId);
|
||||
}
|
||||
|
||||
private static final int CONVERSATION_META_DATA_LOADER = 1;
|
||||
private static final int CONVERSATION_MESSAGES_LOADER = 2;
|
||||
private static final int PARTICIPANT_LOADER = 3;
|
||||
private static final int SELF_PARTICIPANT_LOADER = 4;
|
||||
|
||||
public void init(final LoaderManager loaderManager,
|
||||
final BindingBase<ConversationData> binding) {
|
||||
// Remember the binding id so that loader callbacks can check if data is still bound
|
||||
// to same ui component
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(BINDING_ID, binding.getBindingId());
|
||||
mLoaderManager = loaderManager;
|
||||
mLoaderManager.initLoader(CONVERSATION_META_DATA_LOADER, args, mMetadataLoaderCallbacks);
|
||||
mLoaderManager.initLoader(CONVERSATION_MESSAGES_LOADER, args, mMessagesLoaderCallbacks);
|
||||
mLoaderManager.initLoader(PARTICIPANT_LOADER, args, mParticipantsLoaderCallbacks);
|
||||
mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, mSelfParticipantLoaderCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListeners.clear();
|
||||
// Make sure focus has moved away from this conversation
|
||||
// TODO: May false trigger if destroy happens after "new" conversation is focused.
|
||||
// Assert.isTrue(!DataModel.get().isFocusedConversation(mConversationId));
|
||||
|
||||
// This could be null if we bind but the caller doesn't init the BindableData
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(CONVERSATION_META_DATA_LOADER);
|
||||
mLoaderManager.destroyLoader(CONVERSATION_MESSAGES_LOADER);
|
||||
mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
|
||||
mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default self participant in the participant table (NOT the conversation's self).
|
||||
* This is available as soon as self participant data is loaded.
|
||||
*/
|
||||
public ParticipantData getDefaultSelfParticipant() {
|
||||
return mSelfParticipantsData.getDefaultSelfParticipant();
|
||||
}
|
||||
|
||||
public List<ParticipantData> getSelfParticipants(final boolean activeOnly) {
|
||||
return mSelfParticipantsData.getSelfParticipants(activeOnly);
|
||||
}
|
||||
|
||||
public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) {
|
||||
return mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(activeOnly);
|
||||
}
|
||||
|
||||
public ParticipantData getSelfParticipantById(final String selfId) {
|
||||
return mSelfParticipantsData.getSelfParticipantById(selfId);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a 1:1 conversation return the other (not self) participant (else null)
|
||||
*/
|
||||
public ParticipantData getOtherParticipant() {
|
||||
return mParticipantData.getOtherParticipant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true once the participants are loaded
|
||||
*/
|
||||
public boolean getParticipantsLoaded() {
|
||||
return mParticipantData.isLoaded();
|
||||
}
|
||||
|
||||
public void sendMessage(final BindingBase<ConversationData> binding,
|
||||
final MessageData message) {
|
||||
Assert.isTrue(TextUtils.equals(mConversationId, message.getConversationId()));
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
|
||||
if (!OsUtil.isAtLeastL_MR1() || message.getSelfId() == null) {
|
||||
InsertNewMessageAction.insertNewMessage(message);
|
||||
} else {
|
||||
final int systemDefaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId();
|
||||
if (systemDefaultSubId != ParticipantData.DEFAULT_SELF_SUB_ID &&
|
||||
mSelfParticipantsData.isDefaultSelf(message.getSelfId())) {
|
||||
// Lock the sub selection to the system default SIM as soon as the user clicks on
|
||||
// the send button to avoid races between this and when InsertNewMessageAction is
|
||||
// actually executed on the data model thread, during which the user can potentially
|
||||
// change the system default SIM in Settings.
|
||||
InsertNewMessageAction.insertNewMessage(message, systemDefaultSubId);
|
||||
} else {
|
||||
InsertNewMessageAction.insertNewMessage(message);
|
||||
}
|
||||
}
|
||||
// Update contacts so Frequents will reflect messaging activity.
|
||||
if (!getParticipantsLoaded()) {
|
||||
return; // oh well, not critical
|
||||
}
|
||||
final ArrayList<String> phones = new ArrayList<>();
|
||||
final ArrayList<String> emails = new ArrayList<>();
|
||||
for (final ParticipantData participant : mParticipantData) {
|
||||
if (!participant.isSelf()) {
|
||||
if (participant.isEmail()) {
|
||||
emails.add(participant.getSendDestination());
|
||||
} else {
|
||||
phones.add(participant.getSendDestination());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ContactUtil.hasReadContactsPermission()) {
|
||||
SafeAsyncTask.executeOnThreadPool(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final DataUsageStatUpdater updater = new DataUsageStatUpdater(
|
||||
Factory.get().getApplicationContext());
|
||||
try {
|
||||
if (!phones.isEmpty()) {
|
||||
updater.updateWithPhoneNumber(phones);
|
||||
}
|
||||
if (!emails.isEmpty()) {
|
||||
updater.updateWithAddress(emails);
|
||||
}
|
||||
} catch (final SQLiteFullException ex) {
|
||||
LogUtil.w(TAG, "Unable to update contact", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadMessage(final BindingBase<ConversationData> binding,
|
||||
final String messageId) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
Assert.notNull(messageId);
|
||||
RedownloadMmsAction.redownloadMessage(messageId);
|
||||
}
|
||||
|
||||
public void resendMessage(final BindingBase<ConversationData> binding, final String messageId) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
Assert.notNull(messageId);
|
||||
ResendMessageAction.resendMessage(messageId);
|
||||
}
|
||||
|
||||
public void deleteMessage(final BindingBase<ConversationData> binding, final String messageId) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
Assert.notNull(messageId);
|
||||
DeleteMessageAction.deleteMessage(messageId);
|
||||
}
|
||||
|
||||
public void deleteConversation(final Binding<ConversationData> binding) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
// If possible use timestamp of last message shown to delete only messages user is aware of
|
||||
if (mConversationMetadata == null) {
|
||||
DeleteConversationAction.deleteConversation(mConversationId,
|
||||
System.currentTimeMillis());
|
||||
} else {
|
||||
mConversationMetadata.deleteConversation();
|
||||
}
|
||||
}
|
||||
|
||||
public void archiveConversation(final BindingBase<ConversationData> binding) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
UpdateConversationArchiveStatusAction.archiveConversation(mConversationId);
|
||||
}
|
||||
|
||||
public void unarchiveConversation(final BindingBase<ConversationData> binding) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(mConversationId);
|
||||
}
|
||||
|
||||
public ConversationParticipantsData getParticipants() {
|
||||
return mParticipantData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a dialable phone number for the participant if we are in a 1-1 conversation.
|
||||
* @return the participant phone number, or null if the phone number is not valid or if there
|
||||
* are more than one participant.
|
||||
*/
|
||||
public String getParticipantPhoneNumber() {
|
||||
final ParticipantData participant = this.getOtherParticipant();
|
||||
if (participant != null) {
|
||||
final String phoneNumber = participant.getSendDestination();
|
||||
if (!TextUtils.isEmpty(phoneNumber) && MmsSmsUtils.isPhoneNumber(phoneNumber)) {
|
||||
return phoneNumber;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message to be forwarded from an existing message.
|
||||
*/
|
||||
public MessageData createForwardedMessage(final ConversationMessageData message) {
|
||||
final MessageData forwardedMessage = new MessageData();
|
||||
|
||||
final String originalSubject =
|
||||
MmsUtils.cleanseMmsSubject(mContext.getResources(), message.getMmsSubject());
|
||||
if (!TextUtils.isEmpty(originalSubject)) {
|
||||
forwardedMessage.setMmsSubject(
|
||||
mContext.getResources().getString(R.string.message_fwd, originalSubject));
|
||||
}
|
||||
|
||||
for (final MessagePartData part : message.getParts()) {
|
||||
MessagePartData forwardedPart;
|
||||
|
||||
// Depending on the part type, if it is text, we can directly create a text part;
|
||||
// if it is attachment, then we need to create a pending attachment data out of it, so
|
||||
// that we may persist the attachment locally in the scratch folder when the user picks
|
||||
// a conversation to forward to.
|
||||
if (part.isText()) {
|
||||
forwardedPart = MessagePartData.createTextMessagePart(part.getText());
|
||||
} else {
|
||||
final PendingAttachmentData pendingAttachmentData = PendingAttachmentData
|
||||
.createPendingAttachmentData(part.getContentType(), part.getContentUri());
|
||||
forwardedPart = pendingAttachmentData;
|
||||
}
|
||||
forwardedMessage.addPart(forwardedPart);
|
||||
}
|
||||
return forwardedMessage;
|
||||
}
|
||||
|
||||
public int getNumberOfParticipantsExcludingSelf() {
|
||||
return mParticipantData.getNumberOfParticipantsExcludingSelf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link com.android.messaging.datamodel.data.SubscriptionListData
|
||||
* .SubscriptionListEntry} for a given self participant so UI can display SIM-related info
|
||||
* (icon, name etc.) for multi-SIM.
|
||||
*/
|
||||
public SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
|
||||
final String selfParticipantId, final boolean excludeDefault) {
|
||||
return getSubscriptionEntryForSelfParticipant(selfParticipantId, excludeDefault,
|
||||
mSubscriptionListData, mSelfParticipantsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link com.android.messaging.datamodel.data.SubscriptionListData
|
||||
* .SubscriptionListEntry} for a given self participant so UI can display SIM-related info
|
||||
* (icon, name etc.) for multi-SIM.
|
||||
*/
|
||||
public static SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
|
||||
final String selfParticipantId, final boolean excludeDefault,
|
||||
final SubscriptionListData subscriptionListData,
|
||||
final SelfParticipantsData selfParticipantsData) {
|
||||
// SIM indicators are shown in the UI only if:
|
||||
// 1. Framework has MSIM support AND
|
||||
// 2. The device has had multiple *active* subscriptions. AND
|
||||
// 3. The message's subscription is active.
|
||||
if (OsUtil.isAtLeastL_MR1() &&
|
||||
selfParticipantsData.getSelfParticipantsCountExcludingDefault(true) > 1) {
|
||||
return subscriptionListData.getActiveSubscriptionEntryBySelfId(selfParticipantId,
|
||||
excludeDefault);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SubscriptionListData getSubscriptionListData() {
|
||||
return mSubscriptionListData;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy implementation of {@link ConversationDataListener} so that subclasses may opt to
|
||||
* implement some, but not all, of the interface methods.
|
||||
*/
|
||||
public static class SimpleConversationDataListener implements ConversationDataListener {
|
||||
|
||||
@Override
|
||||
public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor,
|
||||
@Nullable
|
||||
final
|
||||
ConversationMessageData newestMessage, final boolean isSync) {}
|
||||
|
||||
@Override
|
||||
public void onConversationMetadataUpdated(final ConversationData data) {}
|
||||
|
||||
@Override
|
||||
public void closeConversation(final String conversationId) {}
|
||||
|
||||
@Override
|
||||
public void onConversationParticipantDataLoaded(final ConversationData data) {}
|
||||
|
||||
@Override
|
||||
public void onSubscriptionListDataLoaded(final ConversationData data) {}
|
||||
|
||||
}
|
||||
|
||||
private class ConversationDataEventDispatcher
|
||||
extends ArrayList<ConversationDataListener>
|
||||
implements ConversationDataListener {
|
||||
|
||||
@Override
|
||||
public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor,
|
||||
@Nullable
|
||||
final ConversationMessageData newestMessage, final boolean isSync) {
|
||||
for (final ConversationDataListener listener : this) {
|
||||
listener.onConversationMessagesCursorUpdated(data, cursor, newestMessage, isSync);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationMetadataUpdated(final ConversationData data) {
|
||||
for (final ConversationDataListener listener : this) {
|
||||
listener.onConversationMetadataUpdated(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConversation(final String conversationId) {
|
||||
for (final ConversationDataListener listener : this) {
|
||||
listener.closeConversation(conversationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationParticipantDataLoaded(final ConversationData data) {
|
||||
for (final ConversationDataListener listener : this) {
|
||||
listener.onConversationParticipantDataLoaded(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscriptionListDataLoaded(final ConversationData data) {
|
||||
for (final ConversationDataListener listener : this) {
|
||||
listener.onSubscriptionListDataLoaded(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.BugleNotifications;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.SyncManager;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
|
||||
import com.android.messaging.receiver.SmsReceiver;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class ConversationListData extends BindableData
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
public static final String SORT_ORDER =
|
||||
ConversationListViewColumns.SORT_TIMESTAMP + " DESC";
|
||||
|
||||
private static final String WHERE_ARCHIVED =
|
||||
"(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 1)";
|
||||
public static final String WHERE_NOT_ARCHIVED =
|
||||
"(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 0)";
|
||||
|
||||
public interface ConversationListDataListener {
|
||||
public void onConversationListCursorUpdated(ConversationListData data, Cursor cursor);
|
||||
public void setBlockedParticipantsAvailable(boolean blockedAvailable);
|
||||
}
|
||||
|
||||
private ConversationListDataListener mListener;
|
||||
private final Context mContext;
|
||||
private final boolean mArchivedMode;
|
||||
private LoaderManager mLoaderManager;
|
||||
|
||||
public ConversationListData(final Context context, final ConversationListDataListener listener,
|
||||
final boolean archivedMode) {
|
||||
mListener = listener;
|
||||
mContext = context;
|
||||
mArchivedMode = archivedMode;
|
||||
}
|
||||
|
||||
private static final int CONVERSATION_LIST_LOADER = 1;
|
||||
private static final int BLOCKED_PARTICIPANTS_AVAILABLE_LOADER = 2;
|
||||
|
||||
private static final String[] BLOCKED_PARTICIPANTS_PROJECTION = new String[] {
|
||||
ParticipantColumns._ID,
|
||||
ParticipantColumns.NORMALIZED_DESTINATION,
|
||||
};
|
||||
private static final int INDEX_BLOCKED_PARTICIPANTS_ID = 0;
|
||||
private static final int INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION = 1;
|
||||
|
||||
// all blocked participants
|
||||
private final HashSet<String> mBlockedParticipants = new HashSet<String>();
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
Loader<Cursor> loader = null;
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
switch (id) {
|
||||
case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
|
||||
loader = new BoundCursorLoader(bindingId, mContext,
|
||||
MessagingContentProvider.PARTICIPANTS_URI,
|
||||
BLOCKED_PARTICIPANTS_PROJECTION,
|
||||
ParticipantColumns.BLOCKED + "=1", null, null);
|
||||
break;
|
||||
case CONVERSATION_LIST_LOADER:
|
||||
loader = new BoundCursorLoader(bindingId, mContext,
|
||||
MessagingContentProvider.CONVERSATIONS_URI,
|
||||
ConversationListItemData.PROJECTION,
|
||||
mArchivedMode ? WHERE_ARCHIVED : WHERE_NOT_ARCHIVED,
|
||||
null, // selection args
|
||||
SORT_ORDER);
|
||||
break;
|
||||
default:
|
||||
Assert.fail("Unknown loader id");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(TAG, "Creating loader after unbinding list");
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
if (isBound(loader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
|
||||
mBlockedParticipants.clear();
|
||||
for (int i = 0; i < data.getCount(); i++) {
|
||||
data.moveToPosition(i);
|
||||
mBlockedParticipants.add(data.getString(
|
||||
INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION));
|
||||
}
|
||||
mListener.setBlockedParticipantsAvailable(data != null && data.getCount() > 0);
|
||||
break;
|
||||
case CONVERSATION_LIST_LOADER:
|
||||
mListener.onConversationListCursorUpdated(this, data);
|
||||
break;
|
||||
default:
|
||||
Assert.fail("Unknown loader id");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(TAG, "Loader finished after unbinding list");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> generic) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
if (isBound(loader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
|
||||
mListener.setBlockedParticipantsAvailable(false);
|
||||
break;
|
||||
case CONVERSATION_LIST_LOADER:
|
||||
mListener.onConversationListCursorUpdated(this, null);
|
||||
break;
|
||||
default:
|
||||
Assert.fail("Unknown loader id");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(TAG, "Loader reset after unbinding list");
|
||||
}
|
||||
}
|
||||
|
||||
private Bundle mArgs;
|
||||
|
||||
public void init(final LoaderManager loaderManager,
|
||||
final BindingBase<ConversationListData> binding) {
|
||||
mArgs = new Bundle();
|
||||
mArgs.putString(BINDING_ID, binding.getBindingId());
|
||||
mLoaderManager = loaderManager;
|
||||
mLoaderManager.initLoader(CONVERSATION_LIST_LOADER, mArgs, this);
|
||||
mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER, mArgs, this);
|
||||
}
|
||||
|
||||
public void handleMessagesSeen() {
|
||||
BugleNotifications.markAllMessagesAsSeen();
|
||||
|
||||
SmsReceiver.cancelSecondaryUserNotification();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
|
||||
// This could be null if we bind but the caller doesn't init the BindableData
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(CONVERSATION_LIST_LOADER);
|
||||
mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getHasFirstSyncCompleted() {
|
||||
final SyncManager syncManager = DataModel.get().getSyncManager();
|
||||
return syncManager.getHasFirstSyncCompleted();
|
||||
}
|
||||
|
||||
public void setScrolledToNewestConversation(boolean scrolledToNewestConversation) {
|
||||
DataModel.get().setConversationListScrolledToNewestConversation(
|
||||
scrolledToNewestConversation);
|
||||
if (scrolledToNewestConversation) {
|
||||
handleMessagesSeen();
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<String> getBlockedParticipants() {
|
||||
return mBlockedParticipants;
|
||||
}
|
||||
}
|
||||
@@ -1,510 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.datamodel.action.DeleteConversationAction;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.Dates;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class wrapping the conversation list view used to display each item in conversation list
|
||||
*/
|
||||
public class ConversationListItemData {
|
||||
private String mConversationId;
|
||||
private String mName;
|
||||
private String mIcon;
|
||||
private boolean mIsRead;
|
||||
private long mTimestamp;
|
||||
private String mSnippetText;
|
||||
private Uri mPreviewUri;
|
||||
private String mPreviewContentType;
|
||||
private long mParticipantContactId;
|
||||
private String mParticipantLookupKey;
|
||||
private String mOtherParticipantNormalizedDestination;
|
||||
private String mSelfId;
|
||||
private int mParticipantCount;
|
||||
private boolean mNotificationEnabled;
|
||||
private String mNotificationSoundUri;
|
||||
private boolean mNotificationVibrate;
|
||||
private boolean mIncludeEmailAddress;
|
||||
private int mMessageStatus;
|
||||
private int mMessageRawTelephonyStatus;
|
||||
private boolean mShowDraft;
|
||||
private Uri mDraftPreviewUri;
|
||||
private String mDraftPreviewContentType;
|
||||
private String mDraftSnippetText;
|
||||
private boolean mIsArchived;
|
||||
private String mSubject;
|
||||
private String mDraftSubject;
|
||||
private String mSnippetSenderFirstName;
|
||||
private String mSnippetSenderDisplayDestination;
|
||||
|
||||
public ConversationListItemData() {
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor) {
|
||||
bind(cursor, false);
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor, final boolean ignoreDraft) {
|
||||
mConversationId = cursor.getString(INDEX_ID);
|
||||
mName = cursor.getString(INDEX_CONVERSATION_NAME);
|
||||
mIcon = cursor.getString(INDEX_CONVERSATION_ICON);
|
||||
mSnippetText = cursor.getString(INDEX_SNIPPET_TEXT);
|
||||
mTimestamp = cursor.getLong(INDEX_SORT_TIMESTAMP);
|
||||
mIsRead = cursor.getInt(INDEX_READ) == 1;
|
||||
final String previewUriString = cursor.getString(INDEX_PREVIEW_URI);
|
||||
mPreviewUri = TextUtils.isEmpty(previewUriString) ? null : Uri.parse(previewUriString);
|
||||
mPreviewContentType = cursor.getString(INDEX_PREVIEW_CONTENT_TYPE);
|
||||
mParticipantContactId = cursor.getLong(INDEX_PARTICIPANT_CONTACT_ID);
|
||||
mParticipantLookupKey = cursor.getString(INDEX_PARTICIPANT_LOOKUP_KEY);
|
||||
mOtherParticipantNormalizedDestination = cursor.getString(
|
||||
INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION);
|
||||
mSelfId = cursor.getString(INDEX_SELF_ID);
|
||||
mParticipantCount = cursor.getInt(INDEX_PARTICIPANT_COUNT);
|
||||
mNotificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
|
||||
mNotificationSoundUri = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
|
||||
mNotificationVibrate = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
|
||||
mIncludeEmailAddress = cursor.getInt(INDEX_INCLUDE_EMAIL_ADDRESS) == 1;
|
||||
mMessageStatus = cursor.getInt(INDEX_MESSAGE_STATUS);
|
||||
mMessageRawTelephonyStatus = cursor.getInt(INDEX_MESSAGE_RAW_TELEPHONY_STATUS);
|
||||
if (!ignoreDraft) {
|
||||
mShowDraft = cursor.getInt(INDEX_SHOW_DRAFT) == 1;
|
||||
final String draftPreviewUriString = cursor.getString(INDEX_DRAFT_PREVIEW_URI);
|
||||
mDraftPreviewUri = TextUtils.isEmpty(draftPreviewUriString) ?
|
||||
null : Uri.parse(draftPreviewUriString);
|
||||
mDraftPreviewContentType = cursor.getString(INDEX_DRAFT_PREVIEW_CONTENT_TYPE);
|
||||
mDraftSnippetText = cursor.getString(INDEX_DRAFT_SNIPPET_TEXT);
|
||||
mDraftSubject = cursor.getString(INDEX_DRAFT_SUBJECT_TEXT);
|
||||
} else {
|
||||
mShowDraft = false;
|
||||
mDraftPreviewUri = null;
|
||||
mDraftPreviewContentType = null;
|
||||
mDraftSnippetText = null;
|
||||
mDraftSubject = null;
|
||||
}
|
||||
|
||||
mIsArchived = cursor.getInt(INDEX_ARCHIVE_STATUS) == 1;
|
||||
mSubject = cursor.getString(INDEX_SUBJECT_TEXT);
|
||||
mSnippetSenderFirstName = cursor.getString(INDEX_SNIPPET_SENDER_FIRST_NAME);
|
||||
mSnippetSenderDisplayDestination =
|
||||
cursor.getString(INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION);
|
||||
}
|
||||
|
||||
public String getConversationId() {
|
||||
return mConversationId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
public boolean getIsRead() {
|
||||
return mIsRead;
|
||||
}
|
||||
|
||||
public String getFormattedTimestamp() {
|
||||
return Dates.getConversationTimeString(mTimestamp).toString();
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
public String getSnippetText() {
|
||||
return mSnippetText;
|
||||
}
|
||||
|
||||
public Uri getPreviewUri() {
|
||||
return mPreviewUri;
|
||||
}
|
||||
|
||||
public String getPreviewContentType() {
|
||||
return mPreviewContentType;
|
||||
}
|
||||
|
||||
public long getParticipantContactId() {
|
||||
return mParticipantContactId;
|
||||
}
|
||||
|
||||
public String getParticipantLookupKey() {
|
||||
return mParticipantLookupKey;
|
||||
}
|
||||
|
||||
public String getOtherParticipantNormalizedDestination() {
|
||||
return mOtherParticipantNormalizedDestination;
|
||||
}
|
||||
|
||||
public String getSelfId() {
|
||||
return mSelfId;
|
||||
}
|
||||
|
||||
public int getParticipantCount() {
|
||||
return mParticipantCount;
|
||||
}
|
||||
|
||||
public boolean getIsGroup() {
|
||||
// Participant count excludes self
|
||||
return (mParticipantCount > 1);
|
||||
}
|
||||
|
||||
public boolean getIncludeEmailAddress() {
|
||||
return mIncludeEmailAddress;
|
||||
}
|
||||
|
||||
public boolean getNotificationEnabled() {
|
||||
return mNotificationEnabled;
|
||||
}
|
||||
|
||||
public String getNotificationSoundUri() {
|
||||
return mNotificationSoundUri;
|
||||
}
|
||||
|
||||
public boolean getNotifiationVibrate() {
|
||||
return mNotificationVibrate;
|
||||
}
|
||||
|
||||
public final boolean getIsFailedStatus() {
|
||||
return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED ||
|
||||
mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER ||
|
||||
mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
|
||||
mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
public final boolean getIsSendRequested() {
|
||||
return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
|
||||
mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY ||
|
||||
mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_SENDING ||
|
||||
mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_RESENDING);
|
||||
}
|
||||
|
||||
public boolean getIsMessageTypeOutgoing() {
|
||||
return !MessageData.getIsIncoming(mMessageStatus);
|
||||
}
|
||||
|
||||
public int getMessageRawTelephonyStatus() {
|
||||
return mMessageRawTelephonyStatus;
|
||||
}
|
||||
|
||||
public int getMessageStatus() {
|
||||
return mMessageStatus;
|
||||
}
|
||||
|
||||
public boolean getShowDraft() {
|
||||
return mShowDraft;
|
||||
}
|
||||
|
||||
public String getDraftSnippetText() {
|
||||
return mDraftSnippetText;
|
||||
}
|
||||
|
||||
public Uri getDraftPreviewUri() {
|
||||
return mDraftPreviewUri;
|
||||
}
|
||||
|
||||
public String getDraftPreviewContentType() {
|
||||
return mDraftPreviewContentType;
|
||||
}
|
||||
|
||||
public boolean getIsArchived() {
|
||||
return mIsArchived;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return mSubject;
|
||||
}
|
||||
|
||||
public String getDraftSubject() {
|
||||
return mDraftSubject;
|
||||
}
|
||||
|
||||
public String getSnippetSenderName() {
|
||||
if (!TextUtils.isEmpty(mSnippetSenderFirstName)) {
|
||||
return mSnippetSenderFirstName;
|
||||
}
|
||||
return mSnippetSenderDisplayDestination;
|
||||
}
|
||||
|
||||
public void deleteConversation() {
|
||||
DeleteConversationAction.deleteConversation(mConversationId, mTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the view for this data item
|
||||
*/
|
||||
public static final String getConversationListView() {
|
||||
return CONVERSATION_LIST_VIEW;
|
||||
}
|
||||
|
||||
public static final String getConversationListViewSql() {
|
||||
return CONVERSATION_LIST_VIEW_SQL;
|
||||
}
|
||||
|
||||
private static final String CONVERSATION_LIST_VIEW = "conversation_list_view";
|
||||
|
||||
private static final String CONVERSATION_LIST_VIEW_PROJECTION =
|
||||
DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns._ID
|
||||
+ " as " + ConversationListViewColumns._ID + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NAME
|
||||
+ " as " + ConversationListViewColumns.NAME + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.CURRENT_SELF_ID
|
||||
+ " as " + ConversationListViewColumns.CURRENT_SELF_ID + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ARCHIVE_STATUS
|
||||
+ " as " + ConversationListViewColumns.ARCHIVE_STATUS + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
|
||||
+ " as " + ConversationListViewColumns.READ + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ICON
|
||||
+ " as " + ConversationListViewColumns.ICON + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_CONTACT_ID
|
||||
+ " as " + ConversationListViewColumns.PARTICIPANT_CONTACT_ID + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_LOOKUP_KEY
|
||||
+ " as " + ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.'
|
||||
+ ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION
|
||||
+ " as " + ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SORT_TIMESTAMP
|
||||
+ " as " + ConversationListViewColumns.SORT_TIMESTAMP + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SHOW_DRAFT
|
||||
+ " as " + ConversationListViewColumns.SHOW_DRAFT + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SNIPPET_TEXT
|
||||
+ " as " + ConversationListViewColumns.DRAFT_SNIPPET_TEXT + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_PREVIEW_URI
|
||||
+ " as " + ConversationListViewColumns.DRAFT_PREVIEW_URI + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SUBJECT_TEXT
|
||||
+ " as " + ConversationListViewColumns.DRAFT_SUBJECT_TEXT + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.'
|
||||
+ ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE
|
||||
+ " as " + ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_URI
|
||||
+ " as " + ConversationListViewColumns.PREVIEW_URI + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_CONTENT_TYPE
|
||||
+ " as " + ConversationListViewColumns.PREVIEW_CONTENT_TYPE + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_COUNT
|
||||
+ " as " + ConversationListViewColumns.PARTICIPANT_COUNT + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_ENABLED
|
||||
+ " as " + ConversationListViewColumns.NOTIFICATION_ENABLED + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_SOUND_URI
|
||||
+ " as " + ConversationListViewColumns.NOTIFICATION_SOUND_URI + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_VIBRATION
|
||||
+ " as " + ConversationListViewColumns.NOTIFICATION_VIBRATION + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' +
|
||||
ConversationColumns.INCLUDE_EMAIL_ADDRESS
|
||||
+ " as " + ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
|
||||
+ " as " + ConversationListViewColumns.MESSAGE_STATUS + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
|
||||
+ " as " + ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
|
||||
+ " as " + ConversationListViewColumns.MESSAGE_ID + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
|
||||
+ " as " + ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
|
||||
+ " as " + ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION;
|
||||
|
||||
private static final String JOIN_PARTICIPANTS =
|
||||
" LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON ("
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
|
||||
+ '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + DatabaseHelper.ParticipantColumns._ID
|
||||
+ ") ";
|
||||
|
||||
// View that makes latest message read flag available with rest of conversation data.
|
||||
private static final String CONVERSATION_LIST_VIEW_SQL = "CREATE VIEW " +
|
||||
CONVERSATION_LIST_VIEW + " AS SELECT "
|
||||
+ CONVERSATION_LIST_VIEW_PROJECTION + ", "
|
||||
// Snippet not part of the base projection shared with search view
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SNIPPET_TEXT
|
||||
+ " as " + ConversationListViewColumns.SNIPPET_TEXT + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SUBJECT_TEXT
|
||||
+ " as " + ConversationListViewColumns.SUBJECT_TEXT + " "
|
||||
+ " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
|
||||
+ " LEFT JOIN " + DatabaseHelper.MESSAGES_TABLE + " ON ("
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.LATEST_MESSAGE_ID
|
||||
+ '=' + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID + ") "
|
||||
+ JOIN_PARTICIPANTS
|
||||
+ "ORDER BY " + DatabaseHelper.CONVERSATIONS_TABLE + '.'
|
||||
+ ConversationColumns.SORT_TIMESTAMP + " DESC";
|
||||
|
||||
public static class ConversationListViewColumns implements BaseColumns {
|
||||
public static final String _ID = ConversationColumns._ID;
|
||||
static final String NAME = ConversationColumns.NAME;
|
||||
static final String ARCHIVE_STATUS = ConversationColumns.ARCHIVE_STATUS;
|
||||
static final String READ = MessageColumns.READ;
|
||||
static final String SORT_TIMESTAMP = ConversationColumns.SORT_TIMESTAMP;
|
||||
static final String PREVIEW_URI = ConversationColumns.PREVIEW_URI;
|
||||
static final String PREVIEW_CONTENT_TYPE = ConversationColumns.PREVIEW_CONTENT_TYPE;
|
||||
static final String SNIPPET_TEXT = ConversationColumns.SNIPPET_TEXT;
|
||||
static final String SUBJECT_TEXT = ConversationColumns.SUBJECT_TEXT;
|
||||
static final String ICON = ConversationColumns.ICON;
|
||||
static final String SHOW_DRAFT = ConversationColumns.SHOW_DRAFT;
|
||||
static final String DRAFT_SUBJECT_TEXT = ConversationColumns.DRAFT_SUBJECT_TEXT;
|
||||
static final String DRAFT_PREVIEW_URI = ConversationColumns.DRAFT_PREVIEW_URI;
|
||||
static final String DRAFT_PREVIEW_CONTENT_TYPE =
|
||||
ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE;
|
||||
static final String DRAFT_SNIPPET_TEXT = ConversationColumns.DRAFT_SNIPPET_TEXT;
|
||||
static final String PARTICIPANT_CONTACT_ID = ConversationColumns.PARTICIPANT_CONTACT_ID;
|
||||
static final String PARTICIPANT_LOOKUP_KEY = ConversationColumns.PARTICIPANT_LOOKUP_KEY;
|
||||
static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION =
|
||||
ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION;
|
||||
static final String CURRENT_SELF_ID = ConversationColumns.CURRENT_SELF_ID;
|
||||
static final String PARTICIPANT_COUNT = ConversationColumns.PARTICIPANT_COUNT;
|
||||
static final String NOTIFICATION_ENABLED = ConversationColumns.NOTIFICATION_ENABLED;
|
||||
static final String NOTIFICATION_SOUND_URI = ConversationColumns.NOTIFICATION_SOUND_URI;
|
||||
static final String NOTIFICATION_VIBRATION = ConversationColumns.NOTIFICATION_VIBRATION;
|
||||
static final String INCLUDE_EMAIL_ADDRESS =
|
||||
ConversationColumns.INCLUDE_EMAIL_ADDRESS;
|
||||
static final String MESSAGE_STATUS = MessageColumns.STATUS;
|
||||
static final String MESSAGE_RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
|
||||
static final String MESSAGE_ID = "message_id";
|
||||
static final String SNIPPET_SENDER_FIRST_NAME = "snippet_sender_first_name";
|
||||
static final String SNIPPET_SENDER_DISPLAY_DESTINATION =
|
||||
"snippet_sender_display_destination";
|
||||
}
|
||||
|
||||
public static final String[] PROJECTION = {
|
||||
ConversationListViewColumns._ID,
|
||||
ConversationListViewColumns.NAME,
|
||||
ConversationListViewColumns.ICON,
|
||||
ConversationListViewColumns.SNIPPET_TEXT,
|
||||
ConversationListViewColumns.SORT_TIMESTAMP,
|
||||
ConversationListViewColumns.READ,
|
||||
ConversationListViewColumns.PREVIEW_URI,
|
||||
ConversationListViewColumns.PREVIEW_CONTENT_TYPE,
|
||||
ConversationListViewColumns.PARTICIPANT_CONTACT_ID,
|
||||
ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY,
|
||||
ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION,
|
||||
ConversationListViewColumns.PARTICIPANT_COUNT,
|
||||
ConversationListViewColumns.CURRENT_SELF_ID,
|
||||
ConversationListViewColumns.NOTIFICATION_ENABLED,
|
||||
ConversationListViewColumns.NOTIFICATION_SOUND_URI,
|
||||
ConversationListViewColumns.NOTIFICATION_VIBRATION,
|
||||
ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS,
|
||||
ConversationListViewColumns.MESSAGE_STATUS,
|
||||
ConversationListViewColumns.SHOW_DRAFT,
|
||||
ConversationListViewColumns.DRAFT_PREVIEW_URI,
|
||||
ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE,
|
||||
ConversationListViewColumns.DRAFT_SNIPPET_TEXT,
|
||||
ConversationListViewColumns.ARCHIVE_STATUS,
|
||||
ConversationListViewColumns.MESSAGE_ID,
|
||||
ConversationListViewColumns.SUBJECT_TEXT,
|
||||
ConversationListViewColumns.DRAFT_SUBJECT_TEXT,
|
||||
ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS,
|
||||
ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME,
|
||||
ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION,
|
||||
};
|
||||
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_CONVERSATION_NAME = 1;
|
||||
private static final int INDEX_CONVERSATION_ICON = 2;
|
||||
private static final int INDEX_SNIPPET_TEXT = 3;
|
||||
private static final int INDEX_SORT_TIMESTAMP = 4;
|
||||
private static final int INDEX_READ = 5;
|
||||
private static final int INDEX_PREVIEW_URI = 6;
|
||||
private static final int INDEX_PREVIEW_CONTENT_TYPE = 7;
|
||||
private static final int INDEX_PARTICIPANT_CONTACT_ID = 8;
|
||||
private static final int INDEX_PARTICIPANT_LOOKUP_KEY = 9;
|
||||
private static final int INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION = 10;
|
||||
private static final int INDEX_PARTICIPANT_COUNT = 11;
|
||||
private static final int INDEX_SELF_ID = 12;
|
||||
private static final int INDEX_NOTIFICATION_ENABLED = 13;
|
||||
private static final int INDEX_NOTIFICATION_SOUND_URI = 14;
|
||||
private static final int INDEX_NOTIFICATION_VIBRATION = 15;
|
||||
private static final int INDEX_INCLUDE_EMAIL_ADDRESS = 16;
|
||||
private static final int INDEX_MESSAGE_STATUS = 17;
|
||||
private static final int INDEX_SHOW_DRAFT = 18;
|
||||
private static final int INDEX_DRAFT_PREVIEW_URI = 19;
|
||||
private static final int INDEX_DRAFT_PREVIEW_CONTENT_TYPE = 20;
|
||||
private static final int INDEX_DRAFT_SNIPPET_TEXT = 21;
|
||||
private static final int INDEX_ARCHIVE_STATUS = 22;
|
||||
private static final int INDEX_MESSAGE_ID = 23;
|
||||
private static final int INDEX_SUBJECT_TEXT = 24;
|
||||
private static final int INDEX_DRAFT_SUBJECT_TEXT = 25;
|
||||
private static final int INDEX_MESSAGE_RAW_TELEPHONY_STATUS = 26;
|
||||
private static final int INDEX_SNIPPET_SENDER_FIRST_NAME = 27;
|
||||
private static final int INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION = 28;
|
||||
|
||||
private static final String DIVIDER_TEXT = ", ";
|
||||
|
||||
/**
|
||||
* Get a conversation from the local DB based on the conversation id.
|
||||
*
|
||||
* @param dbWrapper The database
|
||||
* @param conversationId The conversation Id to read
|
||||
* @return The existing conversation or null
|
||||
*/
|
||||
public static ConversationListItemData getExistingConversation(final DatabaseWrapper dbWrapper,
|
||||
final String conversationId) {
|
||||
ConversationListItemData conversation = null;
|
||||
|
||||
// Look for an existing conversation in the db with this conversation id
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
// TODO: Should we be able to read a row from just the conversation table?
|
||||
cursor = dbWrapper.query(getConversationListView(),
|
||||
PROJECTION,
|
||||
ConversationColumns._ID + "=?",
|
||||
new String[] { conversationId },
|
||||
null, null, null);
|
||||
Assert.inRange(cursor.getCount(), 0, 1);
|
||||
if (cursor.moveToFirst()) {
|
||||
conversation = new ConversationListItemData();
|
||||
conversation.bind(cursor);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return conversation;
|
||||
}
|
||||
|
||||
public static String generateConversationName(final List<ParticipantData>
|
||||
participants) {
|
||||
if (participants.size() == 1) {
|
||||
// Prefer full name over first name for 1:1 conversation
|
||||
return participants.get(0).getDisplayName(true);
|
||||
}
|
||||
|
||||
final ArrayList<String> participantNames = new ArrayList<String>();
|
||||
for (final ParticipantData participant : participants) {
|
||||
// Prefer first name over full name for group conversation
|
||||
participantNames.add(participant.getDisplayName(false));
|
||||
}
|
||||
|
||||
final Joiner joiner = Joiner.on(DIVIDER_TEXT).skipNulls();
|
||||
return joiner.join(participantNames);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Holds data for conversation message bubble which keeps track of whether it's been bound to
|
||||
* a new message.
|
||||
*/
|
||||
public class ConversationMessageBubbleData {
|
||||
private String mMessageId;
|
||||
|
||||
/**
|
||||
* Binds to ConversationMessageData instance.
|
||||
* @return true if we are binding to a different message, false if we are binding to the
|
||||
* same message (e.g. in order to update the status text)
|
||||
*/
|
||||
public boolean bind(final ConversationMessageData data) {
|
||||
final boolean changed = !TextUtils.equals(mMessageId, data.getMessageId());
|
||||
mMessageId = data.getMessageId();
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
@@ -1,917 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.BugleGservices;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.Dates;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class representing a message within a conversation sequence. The message parts
|
||||
* are available via the getParts() method.
|
||||
*
|
||||
* TODO: See if we can delegate to MessageData for the logic that this class duplicates
|
||||
* (e.g. getIsMms).
|
||||
*/
|
||||
public class ConversationMessageData {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
|
||||
private String mMessageId;
|
||||
private String mConversationId;
|
||||
private String mParticipantId;
|
||||
private int mPartsCount;
|
||||
private List<MessagePartData> mParts;
|
||||
private long mSentTimestamp;
|
||||
private long mReceivedTimestamp;
|
||||
private boolean mSeen;
|
||||
private boolean mRead;
|
||||
private int mProtocol;
|
||||
private int mStatus;
|
||||
private String mSmsMessageUri;
|
||||
private int mSmsPriority;
|
||||
private int mSmsMessageSize;
|
||||
private String mMmsSubject;
|
||||
private long mMmsExpiry;
|
||||
private int mRawTelephonyStatus;
|
||||
private String mSenderFullName;
|
||||
private String mSenderFirstName;
|
||||
private String mSenderDisplayDestination;
|
||||
private String mSenderNormalizedDestination;
|
||||
private String mSenderProfilePhotoUri;
|
||||
private long mSenderContactId;
|
||||
private String mSenderContactLookupKey;
|
||||
private String mSelfParticipantId;
|
||||
|
||||
/** Are we similar enough to the previous/next messages that we can cluster them? */
|
||||
private boolean mCanClusterWithPreviousMessage;
|
||||
private boolean mCanClusterWithNextMessage;
|
||||
|
||||
public ConversationMessageData() {
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor) {
|
||||
mMessageId = cursor.getString(INDEX_MESSAGE_ID);
|
||||
mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
|
||||
mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
|
||||
mPartsCount = cursor.getInt(INDEX_PARTS_COUNT);
|
||||
|
||||
mParts = makeParts(
|
||||
cursor.getString(INDEX_PARTS_IDS),
|
||||
cursor.getString(INDEX_PARTS_CONTENT_TYPES),
|
||||
cursor.getString(INDEX_PARTS_CONTENT_URIS),
|
||||
cursor.getString(INDEX_PARTS_WIDTHS),
|
||||
cursor.getString(INDEX_PARTS_HEIGHTS),
|
||||
cursor.getString(INDEX_PARTS_TEXTS),
|
||||
mPartsCount,
|
||||
mMessageId);
|
||||
|
||||
mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
|
||||
mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
|
||||
mSeen = (cursor.getInt(INDEX_SEEN) != 0);
|
||||
mRead = (cursor.getInt(INDEX_READ) != 0);
|
||||
mProtocol = cursor.getInt(INDEX_PROTOCOL);
|
||||
mStatus = cursor.getInt(INDEX_STATUS);
|
||||
mSmsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
|
||||
mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
|
||||
mSmsMessageSize = cursor.getInt(INDEX_SMS_MESSAGE_SIZE);
|
||||
mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
|
||||
mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
|
||||
mRawTelephonyStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
|
||||
mSenderFullName = cursor.getString(INDEX_SENDER_FULL_NAME);
|
||||
mSenderFirstName = cursor.getString(INDEX_SENDER_FIRST_NAME);
|
||||
mSenderDisplayDestination = cursor.getString(INDEX_SENDER_DISPLAY_DESTINATION);
|
||||
mSenderNormalizedDestination = cursor.getString(INDEX_SENDER_NORMALIZED_DESTINATION);
|
||||
mSenderProfilePhotoUri = cursor.getString(INDEX_SENDER_PROFILE_PHOTO_URI);
|
||||
mSenderContactId = cursor.getLong(INDEX_SENDER_CONTACT_ID);
|
||||
mSenderContactLookupKey = cursor.getString(INDEX_SENDER_CONTACT_LOOKUP_KEY);
|
||||
mSelfParticipantId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
|
||||
|
||||
if (!cursor.isFirst() && cursor.moveToPrevious()) {
|
||||
mCanClusterWithPreviousMessage = canClusterWithMessage(cursor);
|
||||
cursor.moveToNext();
|
||||
} else {
|
||||
mCanClusterWithPreviousMessage = false;
|
||||
}
|
||||
if (!cursor.isLast() && cursor.moveToNext()) {
|
||||
mCanClusterWithNextMessage = canClusterWithMessage(cursor);
|
||||
cursor.moveToPrevious();
|
||||
} else {
|
||||
mCanClusterWithNextMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canClusterWithMessage(final Cursor cursor) {
|
||||
final String otherParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
|
||||
if (!TextUtils.equals(getParticipantId(), otherParticipantId)) {
|
||||
return false;
|
||||
}
|
||||
final int otherStatus = cursor.getInt(INDEX_STATUS);
|
||||
final boolean otherIsIncoming = (otherStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
|
||||
if (getIsIncoming() != otherIsIncoming) {
|
||||
return false;
|
||||
}
|
||||
final long otherReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
|
||||
final long timestampDeltaMillis = Math.abs(mReceivedTimestamp - otherReceivedTimestamp);
|
||||
if (timestampDeltaMillis > DateUtils.MINUTE_IN_MILLIS) {
|
||||
return false;
|
||||
}
|
||||
final String otherSelfId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
|
||||
if (!TextUtils.equals(getSelfParticipantId(), otherSelfId)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final Character QUOTE_CHAR = '\'';
|
||||
private static final char DIVIDER = '|';
|
||||
|
||||
// statics to avoid unnecessary object allocation
|
||||
private static final StringBuilder sUnquoteStringBuilder = new StringBuilder();
|
||||
private static final ArrayList<String> sUnquoteResults = new ArrayList<String>();
|
||||
|
||||
// this lock is used to guard access to the above statics
|
||||
private static final Object sUnquoteLock = new Object();
|
||||
|
||||
private static void addResult(final ArrayList<String> results, final StringBuilder value) {
|
||||
if (value.length() > 0) {
|
||||
results.add(value.toString());
|
||||
} else {
|
||||
results.add(EMPTY_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static String[] splitUnquotedString(final String inputString) {
|
||||
if (TextUtils.isEmpty(inputString)) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
return inputString.split("\\" + DIVIDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a group-concated and quoted string and decomposes it into its constituent
|
||||
* parts. A quoted string starts and ends with a single quote. Actual single quotes
|
||||
* within the string are escaped using a second single quote. So, for example, an
|
||||
* input string with 3 constituent parts might look like this:
|
||||
*
|
||||
* 'now is the time'|'I can''t do it'|'foo'
|
||||
*
|
||||
* This would be returned as an array of 3 strings as follows:
|
||||
* now is the time
|
||||
* I can't do it
|
||||
* foo
|
||||
*
|
||||
* This is achieved by walking through the inputString, character by character,
|
||||
* ignoring the outer quotes and the divider and replacing any pair of consecutive
|
||||
* single quotes with a single single quote.
|
||||
*
|
||||
* @param inputString
|
||||
* @return array of constituent strings
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String[] splitQuotedString(final String inputString) {
|
||||
if (TextUtils.isEmpty(inputString)) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// this method can be called from multiple threads but it uses a static
|
||||
// string builder
|
||||
synchronized (sUnquoteLock) {
|
||||
final int length = inputString.length();
|
||||
final ArrayList<String> results = sUnquoteResults;
|
||||
results.clear();
|
||||
|
||||
int characterPos = -1;
|
||||
while (++characterPos < length) {
|
||||
final char mustBeQuote = inputString.charAt(characterPos);
|
||||
Assert.isTrue(QUOTE_CHAR == mustBeQuote);
|
||||
while (++characterPos < length) {
|
||||
final char currentChar = inputString.charAt(characterPos);
|
||||
if (currentChar == QUOTE_CHAR) {
|
||||
final char peekAhead = characterPos < length - 1
|
||||
? inputString.charAt(characterPos + 1) : 0;
|
||||
|
||||
if (peekAhead == QUOTE_CHAR) {
|
||||
characterPos += 1; // skip the second quote
|
||||
} else {
|
||||
addResult(results, sUnquoteStringBuilder);
|
||||
sUnquoteStringBuilder.setLength(0);
|
||||
|
||||
Assert.isTrue((peekAhead == DIVIDER) || (peekAhead == (char) 0));
|
||||
characterPos += 1; // skip the divider
|
||||
break;
|
||||
}
|
||||
}
|
||||
sUnquoteStringBuilder.append(currentChar);
|
||||
}
|
||||
}
|
||||
return results.toArray(new String[results.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
static MessagePartData makePartData(
|
||||
final String partId,
|
||||
final String contentType,
|
||||
final String contentUriString,
|
||||
final String contentWidth,
|
||||
final String contentHeight,
|
||||
final String text,
|
||||
final String messageId) {
|
||||
if (ContentType.isTextType(contentType)) {
|
||||
final MessagePartData textPart = MessagePartData.createTextMessagePart(text);
|
||||
textPart.updatePartId(partId);
|
||||
textPart.updateMessageId(messageId);
|
||||
return textPart;
|
||||
} else {
|
||||
final Uri contentUri = Uri.parse(contentUriString);
|
||||
final int width = Integer.parseInt(contentWidth);
|
||||
final int height = Integer.parseInt(contentHeight);
|
||||
final MessagePartData attachmentPart = MessagePartData.createMediaMessagePart(
|
||||
contentType, contentUri, width, height);
|
||||
attachmentPart.updatePartId(partId);
|
||||
attachmentPart.updateMessageId(messageId);
|
||||
return attachmentPart;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<MessagePartData> makeParts(
|
||||
final String rawIds,
|
||||
final String rawContentTypes,
|
||||
final String rawContentUris,
|
||||
final String rawWidths,
|
||||
final String rawHeights,
|
||||
final String rawTexts,
|
||||
final int partsCount,
|
||||
final String messageId) {
|
||||
final List<MessagePartData> parts = new LinkedList<MessagePartData>();
|
||||
if (partsCount == 1) {
|
||||
parts.add(makePartData(
|
||||
rawIds,
|
||||
rawContentTypes,
|
||||
rawContentUris,
|
||||
rawWidths,
|
||||
rawHeights,
|
||||
rawTexts,
|
||||
messageId));
|
||||
} else {
|
||||
unpackMessageParts(
|
||||
parts,
|
||||
splitUnquotedString(rawIds),
|
||||
splitQuotedString(rawContentTypes),
|
||||
splitQuotedString(rawContentUris),
|
||||
splitUnquotedString(rawWidths),
|
||||
splitUnquotedString(rawHeights),
|
||||
splitQuotedString(rawTexts),
|
||||
partsCount,
|
||||
messageId);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void unpackMessageParts(
|
||||
final List<MessagePartData> parts,
|
||||
final String[] ids,
|
||||
final String[] contentTypes,
|
||||
final String[] contentUris,
|
||||
final String[] contentWidths,
|
||||
final String[] contentHeights,
|
||||
final String[] texts,
|
||||
final int partsCount,
|
||||
final String messageId) {
|
||||
|
||||
Assert.equals(partsCount, ids.length);
|
||||
Assert.equals(partsCount, contentTypes.length);
|
||||
Assert.equals(partsCount, contentUris.length);
|
||||
Assert.equals(partsCount, contentWidths.length);
|
||||
Assert.equals(partsCount, contentHeights.length);
|
||||
Assert.equals(partsCount, texts.length);
|
||||
|
||||
for (int i = 0; i < partsCount; i++) {
|
||||
parts.add(makePartData(
|
||||
ids[i],
|
||||
contentTypes[i],
|
||||
contentUris[i],
|
||||
contentWidths[i],
|
||||
contentHeights[i],
|
||||
texts[i],
|
||||
messageId));
|
||||
}
|
||||
|
||||
if (parts.size() != partsCount) {
|
||||
LogUtil.wtf(TAG, "Only unpacked " + parts.size() + " parts from message (id="
|
||||
+ messageId + "), expected " + partsCount + " parts");
|
||||
}
|
||||
}
|
||||
|
||||
public final String getMessageId() {
|
||||
return mMessageId;
|
||||
}
|
||||
|
||||
public final String getConversationId() {
|
||||
return mConversationId;
|
||||
}
|
||||
|
||||
public final String getParticipantId() {
|
||||
return mParticipantId;
|
||||
}
|
||||
|
||||
public List<MessagePartData> getParts() {
|
||||
return mParts;
|
||||
}
|
||||
|
||||
public boolean hasText() {
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (part.isText()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a concatenation of all text parts
|
||||
*
|
||||
* @return the text that is a concatenation of all text parts
|
||||
*/
|
||||
public String getText() {
|
||||
// This is optimized for single text part case, which is the majority
|
||||
|
||||
// For single text part, we just return the part without creating the StringBuilder
|
||||
String firstTextPart = null;
|
||||
boolean foundText = false;
|
||||
// For multiple text parts, we need the StringBuilder and the separator for concatenation
|
||||
StringBuilder sb = null;
|
||||
String separator = null;
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (part.isText()) {
|
||||
if (!foundText) {
|
||||
// First text part
|
||||
firstTextPart = part.getText();
|
||||
foundText = true;
|
||||
} else {
|
||||
// Second and beyond
|
||||
if (sb == null) {
|
||||
// Need the StringBuilder and the separator starting from 2nd text part
|
||||
sb = new StringBuilder();
|
||||
if (!TextUtils.isEmpty(firstTextPart)) {
|
||||
sb.append(firstTextPart);
|
||||
}
|
||||
separator = BugleGservices.get().getString(
|
||||
BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR,
|
||||
BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR_DEFAULT);
|
||||
}
|
||||
final String partText = part.getText();
|
||||
if (!TextUtils.isEmpty(partText)) {
|
||||
if (!TextUtils.isEmpty(separator) && sb.length() > 0) {
|
||||
sb.append(separator);
|
||||
}
|
||||
sb.append(partText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb == null) {
|
||||
// Only one text part
|
||||
return firstTextPart;
|
||||
} else {
|
||||
// More than one
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAttachments() {
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (part.isAttachment()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<MessagePartData> getAttachments() {
|
||||
return getAttachments(null);
|
||||
}
|
||||
|
||||
public List<MessagePartData> getAttachments(final Predicate<MessagePartData> filter) {
|
||||
if (mParts.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<MessagePartData> attachmentParts = new LinkedList<>();
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (part.isAttachment()) {
|
||||
if (filter == null || filter.apply(part)) {
|
||||
attachmentParts.add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attachmentParts;
|
||||
}
|
||||
|
||||
public final long getSentTimeStamp() {
|
||||
return mSentTimestamp;
|
||||
}
|
||||
|
||||
public final long getReceivedTimeStamp() {
|
||||
return mReceivedTimestamp;
|
||||
}
|
||||
|
||||
public final String getFormattedReceivedTimeStamp() {
|
||||
return Dates.getMessageTimeString(mReceivedTimestamp).toString();
|
||||
}
|
||||
|
||||
public final boolean getIsSeen() {
|
||||
return mSeen;
|
||||
}
|
||||
|
||||
public final boolean getIsRead() {
|
||||
return mRead;
|
||||
}
|
||||
|
||||
public final boolean getIsMms() {
|
||||
return (mProtocol == MessageData.PROTOCOL_MMS ||
|
||||
mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
|
||||
}
|
||||
|
||||
public final boolean getIsMmsNotification() {
|
||||
return (mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
|
||||
}
|
||||
|
||||
public final boolean getIsSms() {
|
||||
return mProtocol == (MessageData.PROTOCOL_SMS);
|
||||
}
|
||||
|
||||
final int getProtocol() {
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
public final int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
public final String getSmsMessageUri() {
|
||||
return mSmsMessageUri;
|
||||
}
|
||||
|
||||
public final int getSmsPriority() {
|
||||
return mSmsPriority;
|
||||
}
|
||||
|
||||
public final int getSmsMessageSize() {
|
||||
return mSmsMessageSize;
|
||||
}
|
||||
|
||||
public final String getMmsSubject() {
|
||||
return mMmsSubject;
|
||||
}
|
||||
|
||||
public final long getMmsExpiry() {
|
||||
return mMmsExpiry;
|
||||
}
|
||||
|
||||
public final int getRawTelephonyStatus() {
|
||||
return mRawTelephonyStatus;
|
||||
}
|
||||
|
||||
public final String getSelfParticipantId() {
|
||||
return mSelfParticipantId;
|
||||
}
|
||||
|
||||
public boolean getIsIncoming() {
|
||||
return (mStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
|
||||
}
|
||||
|
||||
public boolean hasIncomingErrorStatus() {
|
||||
return (mStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE ||
|
||||
mStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED);
|
||||
}
|
||||
|
||||
public boolean getIsSendComplete() {
|
||||
return mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
|
||||
}
|
||||
|
||||
public String getSenderFullName() {
|
||||
return mSenderFullName;
|
||||
}
|
||||
|
||||
public String getSenderFirstName() {
|
||||
return mSenderFirstName;
|
||||
}
|
||||
|
||||
public String getSenderDisplayDestination() {
|
||||
return mSenderDisplayDestination;
|
||||
}
|
||||
|
||||
public String getSenderNormalizedDestination() {
|
||||
return mSenderNormalizedDestination;
|
||||
}
|
||||
|
||||
public Uri getSenderProfilePhotoUri() {
|
||||
return mSenderProfilePhotoUri == null ? null : Uri.parse(mSenderProfilePhotoUri);
|
||||
}
|
||||
|
||||
public long getSenderContactId() {
|
||||
return mSenderContactId;
|
||||
}
|
||||
|
||||
public String getSenderDisplayName() {
|
||||
if (!TextUtils.isEmpty(mSenderFullName)) {
|
||||
return mSenderFullName;
|
||||
}
|
||||
if (!TextUtils.isEmpty(mSenderFirstName)) {
|
||||
return mSenderFirstName;
|
||||
}
|
||||
return mSenderDisplayDestination;
|
||||
}
|
||||
|
||||
public String getSenderContactLookupKey() {
|
||||
return mSenderContactLookupKey;
|
||||
}
|
||||
|
||||
public boolean getShowDownloadMessage() {
|
||||
return MessageData.getShowDownloadMessage(mStatus);
|
||||
}
|
||||
|
||||
public boolean getShowResendMessage() {
|
||||
return MessageData.getShowResendMessage(mStatus);
|
||||
}
|
||||
|
||||
public boolean getCanForwardMessage() {
|
||||
// Even for outgoing messages, we only allow forwarding if the message has finished sending
|
||||
// as media often has issues when send isn't complete
|
||||
return (mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE ||
|
||||
mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
|
||||
}
|
||||
|
||||
public boolean getCanCopyMessageToClipboard() {
|
||||
return (hasText() &&
|
||||
(!getIsIncoming() || mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE));
|
||||
}
|
||||
|
||||
public boolean getOneClickResendMessage() {
|
||||
return MessageData.getOneClickResendMessage(mStatus, mRawTelephonyStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender's lookup uri.
|
||||
* This method doesn't support corp contacts.
|
||||
*
|
||||
* @return Lookup uri of sender's contact
|
||||
*/
|
||||
public Uri getSenderContactLookupUri() {
|
||||
if (mSenderContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
|
||||
&& !TextUtils.isEmpty(mSenderContactLookupKey)) {
|
||||
return ContactsContract.Contacts.getLookupUri(mSenderContactId,
|
||||
mSenderContactLookupKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean getCanClusterWithPreviousMessage() {
|
||||
return mCanClusterWithPreviousMessage;
|
||||
}
|
||||
|
||||
public boolean getCanClusterWithNextMessage() {
|
||||
return mCanClusterWithNextMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MessageData.toString(mMessageId, mParts);
|
||||
}
|
||||
|
||||
// Data definitions
|
||||
|
||||
public static final String getConversationMessagesQuerySql() {
|
||||
return CONVERSATION_MESSAGES_QUERY_SQL
|
||||
+ " AND "
|
||||
// Inject the conversation id
|
||||
+ DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
|
||||
+ CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
|
||||
}
|
||||
|
||||
static final String getConversationMessageIdsQuerySql() {
|
||||
return CONVERSATION_MESSAGES_IDS_QUERY_SQL
|
||||
+ " AND "
|
||||
// Inject the conversation id
|
||||
+ DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
|
||||
+ CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
|
||||
}
|
||||
|
||||
public static final String getNotificationQuerySql() {
|
||||
return CONVERSATION_MESSAGES_QUERY_SQL
|
||||
+ " AND "
|
||||
+ "(" + DatabaseHelper.MessageColumns.STATUS + " in ("
|
||||
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
|
||||
+ MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
|
||||
+ " AND "
|
||||
+ DatabaseHelper.MessageColumns.SEEN + " = 0)"
|
||||
+ ")"
|
||||
+ NOTIFICATION_QUERY_SQL_GROUP_BY;
|
||||
}
|
||||
|
||||
public static final String getWearableQuerySql() {
|
||||
return CONVERSATION_MESSAGES_QUERY_SQL
|
||||
+ " AND "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?"
|
||||
+ " AND "
|
||||
+ DatabaseHelper.MessageColumns.STATUS + " IN ("
|
||||
+ MessageData.BUGLE_STATUS_OUTGOING_DELIVERED + ", "
|
||||
+ MessageData.BUGLE_STATUS_OUTGOING_COMPLETE + ", "
|
||||
+ MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ", "
|
||||
+ MessageData.BUGLE_STATUS_OUTGOING_SENDING + ", "
|
||||
+ MessageData.BUGLE_STATUS_OUTGOING_RESENDING + ", "
|
||||
+ MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ", "
|
||||
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
|
||||
+ MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
|
||||
+ ")"
|
||||
+ NOTIFICATION_QUERY_SQL_GROUP_BY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a sqlite snippet to call the quote function on the columnName argument.
|
||||
* The columnName doesn't strictly have to be a column name (e.g. it could be an
|
||||
* expression).
|
||||
*/
|
||||
private static String quote(final String columnName) {
|
||||
return "quote(" + columnName + ")";
|
||||
}
|
||||
|
||||
private static String makeGroupConcatString(final String column) {
|
||||
return "group_concat(" + column + ", '" + DIVIDER + "')";
|
||||
}
|
||||
|
||||
private static String makeIfNullString(final String column) {
|
||||
return "ifnull(" + column + "," + "''" + ")";
|
||||
}
|
||||
|
||||
private static String makePartsTableColumnString(final String column) {
|
||||
return DatabaseHelper.PARTS_TABLE + '.' + column;
|
||||
}
|
||||
|
||||
private static String makeCaseWhenString(final String column,
|
||||
final boolean quote,
|
||||
final String asColumn) {
|
||||
final String fullColumn = makeIfNullString(makePartsTableColumnString(column));
|
||||
final String groupConcatTerm = quote
|
||||
? makeGroupConcatString(quote(fullColumn))
|
||||
: makeGroupConcatString(fullColumn);
|
||||
return "CASE WHEN (" + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT + ">1) THEN " + groupConcatTerm
|
||||
+ " ELSE " + makePartsTableColumnString(column) + " END AS " + asColumn;
|
||||
}
|
||||
|
||||
private static final String CONVERSATION_MESSAGE_VIEW_PARTS_COUNT =
|
||||
"count(" + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + ")";
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
private static final String CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL =
|
||||
DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
|
||||
+ " as " + ConversationMessageViewColumns._ID + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
|
||||
+ " as " + ConversationMessageViewColumns.CONVERSATION_ID + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
|
||||
+ " as " + ConversationMessageViewColumns.PARTICIPANT_ID + ", "
|
||||
|
||||
+ makeCaseWhenString(PartColumns._ID, false,
|
||||
ConversationMessageViewColumns.PARTS_IDS) + ", "
|
||||
+ makeCaseWhenString(PartColumns.CONTENT_TYPE, true,
|
||||
ConversationMessageViewColumns.PARTS_CONTENT_TYPES) + ", "
|
||||
+ makeCaseWhenString(PartColumns.CONTENT_URI, true,
|
||||
ConversationMessageViewColumns.PARTS_CONTENT_URIS) + ", "
|
||||
+ makeCaseWhenString(PartColumns.WIDTH, false,
|
||||
ConversationMessageViewColumns.PARTS_WIDTHS) + ", "
|
||||
+ makeCaseWhenString(PartColumns.HEIGHT, false,
|
||||
ConversationMessageViewColumns.PARTS_HEIGHTS) + ", "
|
||||
+ makeCaseWhenString(PartColumns.TEXT, true,
|
||||
ConversationMessageViewColumns.PARTS_TEXTS) + ", "
|
||||
|
||||
+ CONVERSATION_MESSAGE_VIEW_PARTS_COUNT
|
||||
+ " as " + ConversationMessageViewColumns.PARTS_COUNT + ", "
|
||||
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENT_TIMESTAMP
|
||||
+ " as " + ConversationMessageViewColumns.SENT_TIMESTAMP + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP
|
||||
+ " as " + ConversationMessageViewColumns.RECEIVED_TIMESTAMP + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SEEN
|
||||
+ " as " + ConversationMessageViewColumns.SEEN + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
|
||||
+ " as " + ConversationMessageViewColumns.READ + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.PROTOCOL
|
||||
+ " as " + ConversationMessageViewColumns.PROTOCOL + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
|
||||
+ " as " + ConversationMessageViewColumns.STATUS + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_URI
|
||||
+ " as " + ConversationMessageViewColumns.SMS_MESSAGE_URI + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_PRIORITY
|
||||
+ " as " + ConversationMessageViewColumns.SMS_PRIORITY + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_SIZE
|
||||
+ " as " + ConversationMessageViewColumns.SMS_MESSAGE_SIZE + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_SUBJECT
|
||||
+ " as " + ConversationMessageViewColumns.MMS_SUBJECT + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_EXPIRY
|
||||
+ " as " + ConversationMessageViewColumns.MMS_EXPIRY + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
|
||||
+ " as " + ConversationMessageViewColumns.RAW_TELEPHONY_STATUS + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SELF_PARTICIPANT_ID
|
||||
+ " as " + ConversationMessageViewColumns.SELF_PARTICIPANT_ID + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_FULL_NAME + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_FIRST_NAME + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.NORMALIZED_DESTINATION
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.PROFILE_PHOTO_URI
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.CONTACT_ID
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_CONTACT_ID + ", "
|
||||
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.LOOKUP_KEY
|
||||
+ " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + " ";
|
||||
|
||||
private static final String CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL =
|
||||
" FROM " + DatabaseHelper.MESSAGES_TABLE
|
||||
+ " LEFT JOIN " + DatabaseHelper.PARTS_TABLE
|
||||
+ " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID
|
||||
+ "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") "
|
||||
+ " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE
|
||||
+ " ON (" + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
|
||||
+ '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")"
|
||||
// Exclude draft messages from main view
|
||||
+ " WHERE (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.STATUS
|
||||
+ " <> " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT;
|
||||
|
||||
// This query is mostly static, except for the injection of conversation id. This is for
|
||||
// performance reasons, to ensure that the query uses indices and does not trigger full scans
|
||||
// of the messages table. See b/17160946 for more details.
|
||||
private static final String CONVERSATION_MESSAGES_QUERY_SQL = "SELECT "
|
||||
+ CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL
|
||||
+ CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
|
||||
|
||||
private static final String CONVERSATION_MESSAGE_IDS_PROJECTION_SQL =
|
||||
DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
|
||||
+ " as " + ConversationMessageViewColumns._ID + " ";
|
||||
|
||||
private static final String CONVERSATION_MESSAGES_IDS_QUERY_SQL = "SELECT "
|
||||
+ CONVERSATION_MESSAGE_IDS_PROJECTION_SQL
|
||||
+ CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
|
||||
|
||||
// Note that we sort DESC and ConversationData reverses the cursor. This is a performance
|
||||
// issue (improvement) for large cursors.
|
||||
private static final String CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY =
|
||||
" GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
|
||||
+ " ORDER BY "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
|
||||
|
||||
private static final String NOTIFICATION_QUERY_SQL_GROUP_BY =
|
||||
" GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
|
||||
+ " ORDER BY "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
|
||||
|
||||
interface ConversationMessageViewColumns extends BaseColumns {
|
||||
static final String _ID = MessageColumns._ID;
|
||||
static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID;
|
||||
static final String PARTICIPANT_ID = MessageColumns.SENDER_PARTICIPANT_ID;
|
||||
static final String PARTS_COUNT = "parts_count";
|
||||
static final String SENT_TIMESTAMP = MessageColumns.SENT_TIMESTAMP;
|
||||
static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP;
|
||||
static final String SEEN = MessageColumns.SEEN;
|
||||
static final String READ = MessageColumns.READ;
|
||||
static final String PROTOCOL = MessageColumns.PROTOCOL;
|
||||
static final String STATUS = MessageColumns.STATUS;
|
||||
static final String SMS_MESSAGE_URI = MessageColumns.SMS_MESSAGE_URI;
|
||||
static final String SMS_PRIORITY = MessageColumns.SMS_PRIORITY;
|
||||
static final String SMS_MESSAGE_SIZE = MessageColumns.SMS_MESSAGE_SIZE;
|
||||
static final String MMS_SUBJECT = MessageColumns.MMS_SUBJECT;
|
||||
static final String MMS_EXPIRY = MessageColumns.MMS_EXPIRY;
|
||||
static final String RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
|
||||
static final String SELF_PARTICIPANT_ID = MessageColumns.SELF_PARTICIPANT_ID;
|
||||
static final String SENDER_FULL_NAME = ParticipantColumns.FULL_NAME;
|
||||
static final String SENDER_FIRST_NAME = ParticipantColumns.FIRST_NAME;
|
||||
static final String SENDER_DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION;
|
||||
static final String SENDER_NORMALIZED_DESTINATION =
|
||||
ParticipantColumns.NORMALIZED_DESTINATION;
|
||||
static final String SENDER_PROFILE_PHOTO_URI = ParticipantColumns.PROFILE_PHOTO_URI;
|
||||
static final String SENDER_CONTACT_ID = ParticipantColumns.CONTACT_ID;
|
||||
static final String SENDER_CONTACT_LOOKUP_KEY = ParticipantColumns.LOOKUP_KEY;
|
||||
static final String PARTS_IDS = "parts_ids";
|
||||
static final String PARTS_CONTENT_TYPES = "parts_content_types";
|
||||
static final String PARTS_CONTENT_URIS = "parts_content_uris";
|
||||
static final String PARTS_WIDTHS = "parts_widths";
|
||||
static final String PARTS_HEIGHTS = "parts_heights";
|
||||
static final String PARTS_TEXTS = "parts_texts";
|
||||
}
|
||||
|
||||
private static int sIndexIncrementer = 0;
|
||||
|
||||
private static final int INDEX_MESSAGE_ID = sIndexIncrementer++;
|
||||
private static final int INDEX_CONVERSATION_ID = sIndexIncrementer++;
|
||||
private static final int INDEX_PARTICIPANT_ID = sIndexIncrementer++;
|
||||
|
||||
private static final int INDEX_PARTS_IDS = sIndexIncrementer++;
|
||||
private static final int INDEX_PARTS_CONTENT_TYPES = sIndexIncrementer++;
|
||||
private static final int INDEX_PARTS_CONTENT_URIS = sIndexIncrementer++;
|
||||
private static final int INDEX_PARTS_WIDTHS = sIndexIncrementer++;
|
||||
private static final int INDEX_PARTS_HEIGHTS = sIndexIncrementer++;
|
||||
private static final int INDEX_PARTS_TEXTS = sIndexIncrementer++;
|
||||
|
||||
private static final int INDEX_PARTS_COUNT = sIndexIncrementer++;
|
||||
|
||||
private static final int INDEX_SENT_TIMESTAMP = sIndexIncrementer++;
|
||||
private static final int INDEX_RECEIVED_TIMESTAMP = sIndexIncrementer++;
|
||||
private static final int INDEX_SEEN = sIndexIncrementer++;
|
||||
private static final int INDEX_READ = sIndexIncrementer++;
|
||||
private static final int INDEX_PROTOCOL = sIndexIncrementer++;
|
||||
private static final int INDEX_STATUS = sIndexIncrementer++;
|
||||
private static final int INDEX_SMS_MESSAGE_URI = sIndexIncrementer++;
|
||||
private static final int INDEX_SMS_PRIORITY = sIndexIncrementer++;
|
||||
private static final int INDEX_SMS_MESSAGE_SIZE = sIndexIncrementer++;
|
||||
private static final int INDEX_MMS_SUBJECT = sIndexIncrementer++;
|
||||
private static final int INDEX_MMS_EXPIRY = sIndexIncrementer++;
|
||||
private static final int INDEX_RAW_TELEPHONY_STATUS = sIndexIncrementer++;
|
||||
private static final int INDEX_SELF_PARTICIPIANT_ID = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_FULL_NAME = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_FIRST_NAME = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_DISPLAY_DESTINATION = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_NORMALIZED_DESTINATION = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_PROFILE_PHOTO_URI = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_CONTACT_ID = sIndexIncrementer++;
|
||||
private static final int INDEX_SENDER_CONTACT_LOOKUP_KEY = sIndexIncrementer++;
|
||||
|
||||
|
||||
private static String[] sProjection = {
|
||||
ConversationMessageViewColumns._ID,
|
||||
ConversationMessageViewColumns.CONVERSATION_ID,
|
||||
ConversationMessageViewColumns.PARTICIPANT_ID,
|
||||
|
||||
ConversationMessageViewColumns.PARTS_IDS,
|
||||
ConversationMessageViewColumns.PARTS_CONTENT_TYPES,
|
||||
ConversationMessageViewColumns.PARTS_CONTENT_URIS,
|
||||
ConversationMessageViewColumns.PARTS_WIDTHS,
|
||||
ConversationMessageViewColumns.PARTS_HEIGHTS,
|
||||
ConversationMessageViewColumns.PARTS_TEXTS,
|
||||
|
||||
ConversationMessageViewColumns.PARTS_COUNT,
|
||||
ConversationMessageViewColumns.SENT_TIMESTAMP,
|
||||
ConversationMessageViewColumns.RECEIVED_TIMESTAMP,
|
||||
ConversationMessageViewColumns.SEEN,
|
||||
ConversationMessageViewColumns.READ,
|
||||
ConversationMessageViewColumns.PROTOCOL,
|
||||
ConversationMessageViewColumns.STATUS,
|
||||
ConversationMessageViewColumns.SMS_MESSAGE_URI,
|
||||
ConversationMessageViewColumns.SMS_PRIORITY,
|
||||
ConversationMessageViewColumns.SMS_MESSAGE_SIZE,
|
||||
ConversationMessageViewColumns.MMS_SUBJECT,
|
||||
ConversationMessageViewColumns.MMS_EXPIRY,
|
||||
ConversationMessageViewColumns.RAW_TELEPHONY_STATUS,
|
||||
ConversationMessageViewColumns.SELF_PARTICIPANT_ID,
|
||||
ConversationMessageViewColumns.SENDER_FULL_NAME,
|
||||
ConversationMessageViewColumns.SENDER_FIRST_NAME,
|
||||
ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION,
|
||||
ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION,
|
||||
ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
|
||||
ConversationMessageViewColumns.SENDER_CONTACT_ID,
|
||||
ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY,
|
||||
};
|
||||
|
||||
public static String[] getProjection() {
|
||||
return sProjection;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.util.SimpleArrayMap;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* A class that contains the list of all participants potentially involved in a conversation.
|
||||
* Includes both the participant records for each participant referenced in conversation
|
||||
* participants table (i.e. "other" phone numbers) plus all participants representing self
|
||||
* (i.e. one per sim recorded in the subscription manager db).
|
||||
*/
|
||||
public class ConversationParticipantsData implements Iterable<ParticipantData> {
|
||||
// A map from a participant id to a participant
|
||||
private final SimpleArrayMap<String, ParticipantData> mConversationParticipantsMap;
|
||||
private int mParticipantCountExcludingSelf = 0;
|
||||
|
||||
public ConversationParticipantsData() {
|
||||
mConversationParticipantsMap = new SimpleArrayMap<String, ParticipantData>();
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor) {
|
||||
mConversationParticipantsMap.clear();
|
||||
mParticipantCountExcludingSelf = 0;
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor);
|
||||
if (!newParticipant.isSelf()) {
|
||||
mParticipantCountExcludingSelf++;
|
||||
}
|
||||
mConversationParticipantsMap.put(newParticipant.getId(), newParticipant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ParticipantData getParticipantById(final String participantId) {
|
||||
return mConversationParticipantsMap.get(participantId);
|
||||
}
|
||||
|
||||
ArrayList<ParticipantData> getParticipantListExcludingSelf() {
|
||||
final ArrayList<ParticipantData> retList =
|
||||
new ArrayList<ParticipantData>(mConversationParticipantsMap.size());
|
||||
for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
|
||||
final ParticipantData participant = mConversationParticipantsMap.valueAt(i);
|
||||
if (!participant.isSelf()) {
|
||||
retList.add(participant);
|
||||
}
|
||||
}
|
||||
return retList;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a 1:1 conversation return the other (not self) participant
|
||||
*/
|
||||
public ParticipantData getOtherParticipant() {
|
||||
if (mParticipantCountExcludingSelf == 1) {
|
||||
for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
|
||||
final ParticipantData participant = mConversationParticipantsMap.valueAt(i);
|
||||
if (!participant.isSelf()) {
|
||||
return participant;
|
||||
}
|
||||
}
|
||||
Assert.fail();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getNumberOfParticipantsExcludingSelf() {
|
||||
return mParticipantCountExcludingSelf;
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return !mConversationParticipantsMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ParticipantData> iterator() {
|
||||
return new Iterator<ParticipantData>() {
|
||||
private int mCurrentIndex = -1;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mCurrentIndex < mConversationParticipantsMap.size() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParticipantData next() {
|
||||
mCurrentIndex++;
|
||||
if (mCurrentIndex >= mConversationParticipantsMap.size()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return mConversationParticipantsMap.valueAt(mCurrentIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,855 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.datamodel.MessageTextStats;
|
||||
import com.android.messaging.datamodel.action.ReadDraftDataAction;
|
||||
import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener;
|
||||
import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionMonitor;
|
||||
import com.android.messaging.datamodel.action.WriteDraftMessageAction;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.sms.MmsConfig;
|
||||
import com.android.messaging.sms.MmsSmsUtils;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
|
||||
import com.android.messaging.util.Assert.RunsOnMainThread;
|
||||
import com.android.messaging.util.BugleGservices;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DraftMessageData extends BindableData implements ReadDraftDataActionListener {
|
||||
|
||||
/**
|
||||
* Interface for DraftMessageData listeners
|
||||
*/
|
||||
public interface DraftMessageDataListener {
|
||||
@RunsOnMainThread
|
||||
void onDraftChanged(DraftMessageData data, int changeFlags);
|
||||
|
||||
@RunsOnMainThread
|
||||
void onDraftAttachmentLimitReached(DraftMessageData data);
|
||||
|
||||
@RunsOnMainThread
|
||||
void onDraftAttachmentLoadFailed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for providing subscription-related data to DraftMessageData
|
||||
*/
|
||||
public interface DraftMessageSubscriptionDataProvider {
|
||||
int getConversationSelfSubId();
|
||||
}
|
||||
|
||||
// Flags sent to onDraftChanged to help the receiver limit the amount of work done
|
||||
public static int ATTACHMENTS_CHANGED = 0x0001;
|
||||
public static int MESSAGE_TEXT_CHANGED = 0x0002;
|
||||
public static int MESSAGE_SUBJECT_CHANGED = 0x0004;
|
||||
// Whether the self participant data has been loaded
|
||||
public static int SELF_CHANGED = 0x0008;
|
||||
public static int ALL_CHANGED = 0x00FF;
|
||||
// ALL_CHANGED intentionally doesn't include WIDGET_CHANGED. ConversationFragment needs to
|
||||
// be notified if the draft it is looking at is changed externally (by a desktop widget) so it
|
||||
// can reload the draft.
|
||||
public static int WIDGET_CHANGED = 0x0100;
|
||||
|
||||
private final String mConversationId;
|
||||
private ReadDraftDataActionMonitor mMonitor;
|
||||
private final DraftMessageDataEventDispatcher mListeners;
|
||||
private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
|
||||
|
||||
private boolean mIncludeEmailAddress;
|
||||
private boolean mIsGroupConversation;
|
||||
private String mMessageText;
|
||||
private String mMessageSubject;
|
||||
private String mSelfId;
|
||||
private MessageTextStats mMessageTextStats;
|
||||
private boolean mSending;
|
||||
|
||||
/** Keeps track of completed attachments in the message draft. This data is persisted to db */
|
||||
private final List<MessagePartData> mAttachments;
|
||||
|
||||
/** A read-only wrapper on mAttachments surfaced to the UI layer for rendering */
|
||||
private final List<MessagePartData> mReadOnlyAttachments;
|
||||
|
||||
/** Keeps track of pending attachments that are being loaded. The pending attachments are
|
||||
* transient, because they are not persisted to the database and are dropped once we go
|
||||
* to the background (after the UI calls saveToStorage) */
|
||||
private final List<PendingAttachmentData> mPendingAttachments;
|
||||
|
||||
/** A read-only wrapper on mPendingAttachments surfaced to the UI layer for rendering */
|
||||
private final List<PendingAttachmentData> mReadOnlyPendingAttachments;
|
||||
|
||||
/** Is the current draft a cached copy of what's been saved to the database. If so, we
|
||||
* may skip loading from database if we are still bound */
|
||||
private boolean mIsDraftCachedCopy;
|
||||
|
||||
/** Whether we are currently asynchronously validating the draft before sending. */
|
||||
private CheckDraftForSendTask mCheckDraftForSendTask;
|
||||
|
||||
public DraftMessageData(final String conversationId) {
|
||||
mConversationId = conversationId;
|
||||
mAttachments = new ArrayList<MessagePartData>();
|
||||
mReadOnlyAttachments = Collections.unmodifiableList(mAttachments);
|
||||
mPendingAttachments = new ArrayList<PendingAttachmentData>();
|
||||
mReadOnlyPendingAttachments = Collections.unmodifiableList(mPendingAttachments);
|
||||
mListeners = new DraftMessageDataEventDispatcher();
|
||||
mMessageTextStats = new MessageTextStats();
|
||||
}
|
||||
|
||||
public void addListener(final DraftMessageDataListener listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
|
||||
mSubscriptionDataProvider = provider;
|
||||
}
|
||||
|
||||
public void updateFromMessageData(final MessageData message, final String bindingId) {
|
||||
// New attachments have arrived - only update if the user hasn't already edited
|
||||
Assert.notNull(bindingId);
|
||||
// The draft is now synced with actual MessageData and no longer a cached copy.
|
||||
mIsDraftCachedCopy = false;
|
||||
// Do not use the loaded draft if the user began composing a message before the draft loaded
|
||||
// During config changes (orientation), the text fields preserve their data, so allow them
|
||||
// to be the same and still consider the draft unchanged by the user
|
||||
if (isDraftEmpty() || (TextUtils.equals(mMessageText, message.getMessageText()) &&
|
||||
TextUtils.equals(mMessageSubject, message.getMmsSubject()) &&
|
||||
mAttachments.isEmpty())) {
|
||||
// No need to clear as just checked it was empty or a subset
|
||||
setMessageText(message.getMessageText(), false /* notify */);
|
||||
setMessageSubject(message.getMmsSubject(), false /* notify */);
|
||||
for (final MessagePartData part : message.getParts()) {
|
||||
if (part.isAttachment() && getAttachmentCount() >= getAttachmentLimit()) {
|
||||
dispatchAttachmentLimitReached();
|
||||
break;
|
||||
}
|
||||
|
||||
if (part instanceof PendingAttachmentData) {
|
||||
// This is a pending attachment data from share intent (e.g. an shared image
|
||||
// that we need to persist locally).
|
||||
final PendingAttachmentData data = (PendingAttachmentData) part;
|
||||
Assert.equals(PendingAttachmentData.STATE_PENDING, data.getCurrentState());
|
||||
addOnePendingAttachmentNoNotify(data, bindingId);
|
||||
} else if (part.isAttachment()) {
|
||||
addOneAttachmentNoNotify(part);
|
||||
}
|
||||
}
|
||||
dispatchChanged(ALL_CHANGED);
|
||||
} else {
|
||||
// The user has started a new message so we throw out the draft message data if there
|
||||
// is one but we also loaded the self metadata and need to let our listeners know.
|
||||
dispatchChanged(SELF_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MessageData object containing a copy of all the parts in this DraftMessageData.
|
||||
*
|
||||
* @param clearLocalCopy whether we should clear out the in-memory copy of the parts. If we
|
||||
* are simply pausing/resuming and not sending the message, then we can keep
|
||||
* @return the MessageData for the draft, null if self id is not set
|
||||
*/
|
||||
public MessageData createMessageWithCurrentAttachments(final boolean clearLocalCopy) {
|
||||
MessageData message = null;
|
||||
if (getIsMms()) {
|
||||
message = MessageData.createDraftMmsMessage(mConversationId, mSelfId,
|
||||
mMessageText, mMessageSubject);
|
||||
for (final MessagePartData attachment : mAttachments) {
|
||||
message.addPart(attachment);
|
||||
}
|
||||
} else {
|
||||
message = MessageData.createDraftSmsMessage(mConversationId, mSelfId,
|
||||
mMessageText);
|
||||
}
|
||||
|
||||
if (clearLocalCopy) {
|
||||
// The message now owns all the attachments and the text...
|
||||
clearLocalDraftCopy();
|
||||
dispatchChanged(ALL_CHANGED);
|
||||
} else {
|
||||
// The draft message becomes a cached copy for UI.
|
||||
mIsDraftCachedCopy = true;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
private void clearLocalDraftCopy() {
|
||||
mIsDraftCachedCopy = false;
|
||||
mAttachments.clear();
|
||||
setMessageText("");
|
||||
setMessageSubject("");
|
||||
}
|
||||
|
||||
public String getConversationId() {
|
||||
return mConversationId;
|
||||
}
|
||||
|
||||
public String getMessageText() {
|
||||
return mMessageText;
|
||||
}
|
||||
|
||||
public String getMessageSubject() {
|
||||
return mMessageSubject;
|
||||
}
|
||||
|
||||
public boolean getIsMms() {
|
||||
final int selfSubId = getSelfSubId();
|
||||
return MmsSmsUtils.getRequireMmsForEmailAddress(mIncludeEmailAddress, selfSubId) ||
|
||||
(mIsGroupConversation && MmsUtils.groupMmsEnabled(selfSubId)) ||
|
||||
mMessageTextStats.getMessageLengthRequiresMms() || !mAttachments.isEmpty() ||
|
||||
!TextUtils.isEmpty(mMessageSubject);
|
||||
}
|
||||
|
||||
public boolean getIsGroupMmsConversation() {
|
||||
return getIsMms() && mIsGroupConversation;
|
||||
}
|
||||
|
||||
public String getSelfId() {
|
||||
return mSelfId;
|
||||
}
|
||||
|
||||
public int getNumMessagesToBeSent() {
|
||||
return mMessageTextStats.getNumMessagesToBeSent();
|
||||
}
|
||||
|
||||
public int getCodePointsRemainingInCurrentMessage() {
|
||||
return mMessageTextStats.getCodePointsRemainingInCurrentMessage();
|
||||
}
|
||||
|
||||
public int getSelfSubId() {
|
||||
return mSubscriptionDataProvider == null ? ParticipantData.DEFAULT_SELF_SUB_ID :
|
||||
mSubscriptionDataProvider.getConversationSelfSubId();
|
||||
}
|
||||
|
||||
private void setMessageText(final String messageText, final boolean notify) {
|
||||
mMessageText = messageText;
|
||||
mMessageTextStats.updateMessageTextStats(getSelfSubId(), mMessageText);
|
||||
if (notify) {
|
||||
dispatchChanged(MESSAGE_TEXT_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMessageSubject(final String subject, final boolean notify) {
|
||||
mMessageSubject = subject;
|
||||
if (notify) {
|
||||
dispatchChanged(MESSAGE_SUBJECT_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessageText(final String messageText) {
|
||||
setMessageText(messageText, false);
|
||||
}
|
||||
|
||||
public void setMessageSubject(final String subject) {
|
||||
setMessageSubject(subject, false);
|
||||
}
|
||||
|
||||
public void addAttachments(final Collection<? extends MessagePartData> attachments) {
|
||||
// If the incoming attachments contains a single-only attachment, we need to clear
|
||||
// the existing attachments.
|
||||
for (final MessagePartData data : attachments) {
|
||||
if (data.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the attachment we're adding can only
|
||||
// exist by itself.
|
||||
destroyAttachments();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the existing attachments contain a single-only attachment, we need to clear the
|
||||
// existing attachments to make room for the incoming attachment.
|
||||
for (final MessagePartData data : mAttachments) {
|
||||
if (data.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the single attachment can only exist
|
||||
// by itself
|
||||
destroyAttachments();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If any of the pending attachments contain a single-only attachment, we need to clear the
|
||||
// existing attachments to make room for the incoming attachment.
|
||||
for (final MessagePartData data : mPendingAttachments) {
|
||||
if (data.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the single attachment can only exist
|
||||
// by itself
|
||||
destroyAttachments();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean reachedLimit = false;
|
||||
for (final MessagePartData data : attachments) {
|
||||
// Don't break out of loop even if limit has been reached so we can destroy all
|
||||
// of the over-limit attachments.
|
||||
reachedLimit |= addOneAttachmentNoNotify(data);
|
||||
}
|
||||
if (reachedLimit) {
|
||||
dispatchAttachmentLimitReached();
|
||||
}
|
||||
dispatchChanged(ATTACHMENTS_CHANGED);
|
||||
}
|
||||
|
||||
public boolean containsAttachment(final Uri contentUri) {
|
||||
for (final MessagePartData existingAttachment : mAttachments) {
|
||||
if (existingAttachment.getContentUri().equals(contentUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (final PendingAttachmentData pendingAttachment : mPendingAttachments) {
|
||||
if (pendingAttachment.getContentUri().equals(contentUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to add one attachment to the attachment list, while guarding against duplicates and
|
||||
* going over the limit.
|
||||
* @return true if the attachment limit was reached, false otherwise
|
||||
*/
|
||||
private boolean addOneAttachmentNoNotify(final MessagePartData attachment) {
|
||||
Assert.isTrue(attachment.isAttachment());
|
||||
final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit();
|
||||
if (reachedLimit || containsAttachment(attachment.getContentUri())) {
|
||||
// Never go over the limit. Never add duplicated attachments.
|
||||
attachment.destroyAsync();
|
||||
return reachedLimit;
|
||||
} else {
|
||||
addAttachment(attachment, null /*pendingAttachment*/);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttachment(final MessagePartData attachment,
|
||||
final PendingAttachmentData pendingAttachment) {
|
||||
if (attachment != null && attachment.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the attachment we're adding can only
|
||||
// exist by itself.
|
||||
destroyAttachments();
|
||||
}
|
||||
if (pendingAttachment != null && pendingAttachment.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the attachment we're adding can only
|
||||
// exist by itself.
|
||||
destroyAttachments();
|
||||
}
|
||||
// If the existing attachments contain a single-only attachment, we need to clear the
|
||||
// existing attachments to make room for the incoming attachment.
|
||||
for (final MessagePartData data : mAttachments) {
|
||||
if (data.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the single attachment can only exist
|
||||
// by itself
|
||||
destroyAttachments();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If any of the pending attachments contain a single-only attachment, we need to clear the
|
||||
// existing attachments to make room for the incoming attachment.
|
||||
for (final MessagePartData data : mPendingAttachments) {
|
||||
if (data.isSinglePartOnly()) {
|
||||
// clear any existing attachments because the single attachment can only exist
|
||||
// by itself
|
||||
destroyAttachments();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attachment != null) {
|
||||
mAttachments.add(attachment);
|
||||
} else if (pendingAttachment != null) {
|
||||
mPendingAttachments.add(pendingAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPendingAttachment(final PendingAttachmentData pendingAttachment,
|
||||
final BindingBase<DraftMessageData> binding) {
|
||||
final boolean reachedLimit = addOnePendingAttachmentNoNotify(pendingAttachment,
|
||||
binding.getBindingId());
|
||||
if (reachedLimit) {
|
||||
dispatchAttachmentLimitReached();
|
||||
}
|
||||
dispatchChanged(ATTACHMENTS_CHANGED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to add one pending attachment, while guarding against duplicates and
|
||||
* going over the limit.
|
||||
* @return true if the attachment limit was reached, false otherwise
|
||||
*/
|
||||
private boolean addOnePendingAttachmentNoNotify(final PendingAttachmentData pendingAttachment,
|
||||
final String bindingId) {
|
||||
final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit();
|
||||
if (reachedLimit || containsAttachment(pendingAttachment.getContentUri())) {
|
||||
// Never go over the limit. Never add duplicated attachments.
|
||||
pendingAttachment.destroyAsync();
|
||||
return reachedLimit;
|
||||
} else {
|
||||
Assert.isTrue(!mPendingAttachments.contains(pendingAttachment));
|
||||
Assert.equals(PendingAttachmentData.STATE_PENDING, pendingAttachment.getCurrentState());
|
||||
addAttachment(null /*attachment*/, pendingAttachment);
|
||||
|
||||
pendingAttachment.loadAttachmentForDraft(this, bindingId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelfId(final String selfId, final boolean notify) {
|
||||
LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: set selfId=" + selfId
|
||||
+ " for conversationId=" + mConversationId);
|
||||
mSelfId = selfId;
|
||||
if (notify) {
|
||||
dispatchChanged(SELF_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAttachments() {
|
||||
return !mAttachments.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasPendingAttachments() {
|
||||
return !mPendingAttachments.isEmpty();
|
||||
}
|
||||
|
||||
private int getAttachmentCount() {
|
||||
return mAttachments.size() + mPendingAttachments.size();
|
||||
}
|
||||
|
||||
private int getVideoAttachmentCount() {
|
||||
int count = 0;
|
||||
for (MessagePartData part : mAttachments) {
|
||||
if (part.isVideo()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
for (MessagePartData part : mPendingAttachments) {
|
||||
if (part.isVideo()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int getAttachmentLimit() {
|
||||
return BugleGservices.get().getInt(
|
||||
BugleGservicesKeys.MMS_ATTACHMENT_LIMIT,
|
||||
BugleGservicesKeys.MMS_ATTACHMENT_LIMIT_DEFAULT);
|
||||
}
|
||||
|
||||
public void removeAttachment(final MessagePartData attachment) {
|
||||
for (final MessagePartData existingAttachment : mAttachments) {
|
||||
if (existingAttachment.getContentUri().equals(attachment.getContentUri())) {
|
||||
mAttachments.remove(existingAttachment);
|
||||
existingAttachment.destroyAsync();
|
||||
dispatchChanged(ATTACHMENTS_CHANGED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeExistingAttachments(final Set<MessagePartData> attachmentsToRemove) {
|
||||
boolean removed = false;
|
||||
final Iterator<MessagePartData> iterator = mAttachments.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final MessagePartData existingAttachment = iterator.next();
|
||||
if (attachmentsToRemove.contains(existingAttachment)) {
|
||||
iterator.remove();
|
||||
existingAttachment.destroyAsync();
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
dispatchChanged(ATTACHMENTS_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePendingAttachment(final PendingAttachmentData pendingAttachment) {
|
||||
for (final PendingAttachmentData existingAttachment : mPendingAttachments) {
|
||||
if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) {
|
||||
mPendingAttachments.remove(pendingAttachment);
|
||||
pendingAttachment.destroyAsync();
|
||||
dispatchChanged(ATTACHMENTS_CHANGED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePendingAttachment(final MessagePartData updatedAttachment,
|
||||
final PendingAttachmentData pendingAttachment) {
|
||||
for (final PendingAttachmentData existingAttachment : mPendingAttachments) {
|
||||
if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) {
|
||||
mPendingAttachments.remove(pendingAttachment);
|
||||
if (pendingAttachment.isSinglePartOnly()) {
|
||||
updatedAttachment.setSinglePartOnly(true);
|
||||
}
|
||||
mAttachments.add(updatedAttachment);
|
||||
dispatchChanged(ATTACHMENTS_CHANGED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are here, this means the pending attachment has been dropped before the task
|
||||
// to load it was completed. In this case destroy the temporarily staged file since it
|
||||
// is no longer needed.
|
||||
updatedAttachment.destroyAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the attachments from the draft and notify any listeners.
|
||||
* @param flags typically this will be ATTACHMENTS_CHANGED. When attachments are cleared in a
|
||||
* widget, flags will also contain WIDGET_CHANGED.
|
||||
*/
|
||||
public void clearAttachments(final int flags) {
|
||||
destroyAttachments();
|
||||
dispatchChanged(flags);
|
||||
}
|
||||
|
||||
public List<MessagePartData> getReadOnlyAttachments() {
|
||||
return mReadOnlyAttachments;
|
||||
}
|
||||
|
||||
public List<PendingAttachmentData> getReadOnlyPendingAttachments() {
|
||||
return mReadOnlyPendingAttachments;
|
||||
}
|
||||
|
||||
public boolean loadFromStorage(final BindingBase<DraftMessageData> binding,
|
||||
final MessageData optionalIncomingDraft, boolean clearLocalDraft) {
|
||||
LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: "
|
||||
+ (optionalIncomingDraft == null ? "loading" : "setting")
|
||||
+ " for conversationId=" + mConversationId);
|
||||
if (clearLocalDraft) {
|
||||
clearLocalDraftCopy();
|
||||
}
|
||||
final boolean isDraftCachedCopy = mIsDraftCachedCopy;
|
||||
mIsDraftCachedCopy = false;
|
||||
// Before reading message from db ensure the caller is bound to us (and knows the id)
|
||||
if (mMonitor == null && !isDraftCachedCopy && isBound(binding.getBindingId())) {
|
||||
mMonitor = ReadDraftDataAction.readDraftData(mConversationId,
|
||||
optionalIncomingDraft, binding.getBindingId(), this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current draft to db. This will save the draft and drop any pending attachments
|
||||
* we have. The UI typically goes into the background when this is called, and instead of
|
||||
* trying to persist the state of the pending attachments (the app may be killed, the activity
|
||||
* may be destroyed), we simply drop the pending attachments for consistency.
|
||||
*/
|
||||
public void saveToStorage(final BindingBase<DraftMessageData> binding) {
|
||||
saveToStorageInternal(binding);
|
||||
dropPendingAttachments();
|
||||
}
|
||||
|
||||
private void saveToStorageInternal(final BindingBase<DraftMessageData> binding) {
|
||||
// Create MessageData to store to db, but don't clear the in-memory copy so UI will
|
||||
// continue to display it.
|
||||
// If self id is null then we'll not attempt to change the conversation's self id.
|
||||
final MessageData message = createMessageWithCurrentAttachments(false /* clearLocalCopy */);
|
||||
// Before writing message to db ensure the caller is bound to us (and knows the id)
|
||||
if (isBound(binding.getBindingId())){
|
||||
WriteDraftMessageAction.writeDraftMessage(mConversationId, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we are ready to send the message. This will assemble/return the MessageData for
|
||||
* sending and clear the local draft data, both from memory and from DB. This will also bind
|
||||
* the message data with a self Id through which the message will be sent.
|
||||
*
|
||||
* @param binding the binding object from our consumer. We need to make sure we are still bound
|
||||
* to that binding before saving to storage.
|
||||
*/
|
||||
public MessageData prepareMessageForSending(final BindingBase<DraftMessageData> binding) {
|
||||
// We can't send the message while there's still stuff pending.
|
||||
Assert.isTrue(!hasPendingAttachments());
|
||||
mSending = true;
|
||||
// Assembles the message to send and empty working draft data.
|
||||
// If self id is null then message is sent with conversation's self id.
|
||||
final MessageData messageToSend =
|
||||
createMessageWithCurrentAttachments(true /* clearLocalCopy */);
|
||||
// Note sending message will empty the draft data in DB.
|
||||
mSending = false;
|
||||
return messageToSend;
|
||||
}
|
||||
|
||||
public boolean isSending() {
|
||||
return mSending;
|
||||
}
|
||||
|
||||
@Override // ReadDraftMessageActionListener.onReadDraftMessageSucceeded
|
||||
public void onReadDraftDataSucceeded(final ReadDraftDataAction action, final Object data,
|
||||
final MessageData message, final ConversationListItemData conversation) {
|
||||
final String bindingId = (String) data;
|
||||
|
||||
// Before passing draft message on to ui ensure the data is bound to the same bindingid
|
||||
if (isBound(bindingId)) {
|
||||
mSelfId = message.getSelfId();
|
||||
mIsGroupConversation = conversation.getIsGroup();
|
||||
mIncludeEmailAddress = conversation.getIncludeEmailAddress();
|
||||
updateFromMessageData(message, bindingId);
|
||||
LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded. "
|
||||
+ "conversationId=" + mConversationId + " selfId=" + mSelfId);
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded but not bound. "
|
||||
+ "conversationId=" + mConversationId);
|
||||
}
|
||||
mMonitor = null;
|
||||
}
|
||||
|
||||
@Override // ReadDraftMessageActionListener.onReadDraftDataFailed
|
||||
public void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft not loaded. "
|
||||
+ "conversationId=" + mConversationId);
|
||||
// The draft is now synced with actual MessageData and no longer a cached copy.
|
||||
mIsDraftCachedCopy = false;
|
||||
// Just clear the monitor - no update to draft data
|
||||
mMonitor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Bugle is default sms app
|
||||
* @return
|
||||
*/
|
||||
public boolean getIsDefaultSmsApp() {
|
||||
return PhoneUtils.getDefault().isDefaultSmsApp();
|
||||
}
|
||||
|
||||
@Override //BindableData.unregisterListeners
|
||||
protected void unregisterListeners() {
|
||||
if (mMonitor != null) {
|
||||
mMonitor.unregister();
|
||||
}
|
||||
mMonitor = null;
|
||||
mListeners.clear();
|
||||
}
|
||||
|
||||
private void destroyAttachments() {
|
||||
for (final MessagePartData attachment : mAttachments) {
|
||||
attachment.destroyAsync();
|
||||
}
|
||||
mAttachments.clear();
|
||||
mPendingAttachments.clear();
|
||||
}
|
||||
|
||||
private void dispatchChanged(final int changeFlags) {
|
||||
// No change is expected to be made to the draft if it is in cached copy state.
|
||||
if (mIsDraftCachedCopy) {
|
||||
return;
|
||||
}
|
||||
// Any change in the draft will cancel any pending draft checking task, since the
|
||||
// size/status of the draft may have changed.
|
||||
if (mCheckDraftForSendTask != null) {
|
||||
mCheckDraftForSendTask.cancel(true /* mayInterruptIfRunning */);
|
||||
mCheckDraftForSendTask = null;
|
||||
}
|
||||
mListeners.onDraftChanged(this, changeFlags);
|
||||
}
|
||||
|
||||
private void dispatchAttachmentLimitReached() {
|
||||
mListeners.onDraftAttachmentLimitReached(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop any pending attachments that haven't finished. This is called after the UI goes to
|
||||
* the background and we persist the draft data to the database.
|
||||
*/
|
||||
private void dropPendingAttachments() {
|
||||
mPendingAttachments.clear();
|
||||
}
|
||||
|
||||
private boolean isDraftEmpty() {
|
||||
return TextUtils.isEmpty(mMessageText) && mAttachments.isEmpty() &&
|
||||
TextUtils.isEmpty(mMessageSubject);
|
||||
}
|
||||
|
||||
public boolean isCheckingDraft() {
|
||||
return mCheckDraftForSendTask != null && !mCheckDraftForSendTask.isCancelled();
|
||||
}
|
||||
|
||||
public void checkDraftForAction(final boolean checkMessageSize, final int selfSubId,
|
||||
final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
|
||||
new CheckDraftForSendTask(checkMessageSize, selfSubId, callback, binding)
|
||||
.executeOnThreadPool((Void) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows us to have multiple data listeners for DraftMessageData
|
||||
*/
|
||||
private class DraftMessageDataEventDispatcher
|
||||
extends ArrayList<DraftMessageDataListener>
|
||||
implements DraftMessageDataListener {
|
||||
|
||||
@Override
|
||||
@RunsOnMainThread
|
||||
public void onDraftChanged(DraftMessageData data, int changeFlags) {
|
||||
Assert.isMainThread();
|
||||
for (final DraftMessageDataListener listener : this) {
|
||||
listener.onDraftChanged(data, changeFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RunsOnMainThread
|
||||
public void onDraftAttachmentLimitReached(DraftMessageData data) {
|
||||
Assert.isMainThread();
|
||||
for (final DraftMessageDataListener listener : this) {
|
||||
listener.onDraftAttachmentLimitReached(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RunsOnMainThread
|
||||
public void onDraftAttachmentLoadFailed() {
|
||||
Assert.isMainThread();
|
||||
for (final DraftMessageDataListener listener : this) {
|
||||
listener.onDraftAttachmentLoadFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface CheckDraftTaskCallback {
|
||||
void onDraftChecked(DraftMessageData data, int result);
|
||||
}
|
||||
|
||||
public class CheckDraftForSendTask extends SafeAsyncTask<Void, Void, Integer> {
|
||||
public static final int RESULT_PASSED = 0;
|
||||
public static final int RESULT_HAS_PENDING_ATTACHMENTS = 1;
|
||||
public static final int RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS = 2;
|
||||
public static final int RESULT_MESSAGE_OVER_LIMIT = 3;
|
||||
public static final int RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED = 4;
|
||||
public static final int RESULT_SIM_NOT_READY = 5;
|
||||
private final boolean mCheckMessageSize;
|
||||
private final int mSelfSubId;
|
||||
private final CheckDraftTaskCallback mCallback;
|
||||
private final String mBindingId;
|
||||
private final List<MessagePartData> mAttachmentsCopy;
|
||||
private int mPreExecuteResult = RESULT_PASSED;
|
||||
|
||||
public CheckDraftForSendTask(final boolean checkMessageSize, final int selfSubId,
|
||||
final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
|
||||
mCheckMessageSize = checkMessageSize;
|
||||
mSelfSubId = selfSubId;
|
||||
mCallback = callback;
|
||||
mBindingId = binding.getBindingId();
|
||||
// Obtain an immutable copy of the attachment list so we can operate on it in the
|
||||
// background thread.
|
||||
mAttachmentsCopy = new ArrayList<MessagePartData>(mAttachments);
|
||||
|
||||
mCheckDraftForSendTask = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
// Perform checking work that can happen on the main thread.
|
||||
if (hasPendingAttachments()) {
|
||||
mPreExecuteResult = RESULT_HAS_PENDING_ATTACHMENTS;
|
||||
return;
|
||||
}
|
||||
if (getIsGroupMmsConversation()) {
|
||||
try {
|
||||
if (TextUtils.isEmpty(PhoneUtils.get(mSelfSubId).getSelfRawNumber(true))) {
|
||||
mPreExecuteResult = RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS;
|
||||
return;
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
// This happens when there is no active subscription, e.g. on Nova
|
||||
// when the phone switches carrier.
|
||||
mPreExecuteResult = RESULT_SIM_NOT_READY;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (getVideoAttachmentCount() > MmsUtils.MAX_VIDEO_ATTACHMENT_COUNT) {
|
||||
mPreExecuteResult = RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackgroundTimed(Void... params) {
|
||||
if (mPreExecuteResult != RESULT_PASSED) {
|
||||
return mPreExecuteResult;
|
||||
}
|
||||
|
||||
if (mCheckMessageSize && getIsMessageOverLimit()) {
|
||||
return RESULT_MESSAGE_OVER_LIMIT;
|
||||
}
|
||||
return RESULT_PASSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer result) {
|
||||
mCheckDraftForSendTask = null;
|
||||
// Only call back if we are bound to the original binding.
|
||||
if (isBound(mBindingId) && !isCancelled()) {
|
||||
mCallback.onDraftChecked(DraftMessageData.this, result);
|
||||
} else {
|
||||
if (!isBound(mBindingId)) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft not bound");
|
||||
}
|
||||
if (isCancelled()) {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft is cancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
mCheckDraftForSendTask = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Check if the draft message contains too many attachments to send
|
||||
* 2. Computes the minimum size that this message could be compressed/downsampled/encoded
|
||||
* before sending and check if it meets the carrier max size for sending.
|
||||
* @see MessagePartData#getMinimumSizeInBytesForSending()
|
||||
*/
|
||||
@DoesNotRunOnMainThread
|
||||
private boolean getIsMessageOverLimit() {
|
||||
Assert.isNotMainThread();
|
||||
if (mAttachmentsCopy.size() > getAttachmentLimit()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Aggregate the size from all the attachments.
|
||||
long totalSize = 0;
|
||||
for (final MessagePartData attachment : mAttachmentsCopy) {
|
||||
totalSize += attachment.getMinimumSizeInBytesForSending();
|
||||
}
|
||||
return totalSize > MmsConfig.get(mSelfSubId).getMaxMessageSize();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPendingAttachmentLoadFailed(PendingAttachmentData data) {
|
||||
mListeners.onDraftAttachmentLoadFailed();
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore.Images.Media;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
|
||||
import com.android.messaging.datamodel.media.ImageRequest;
|
||||
import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
|
||||
import com.android.messaging.util.Assert;
|
||||
|
||||
/**
|
||||
* Provides data for GalleryGridItemView
|
||||
*/
|
||||
public class GalleryGridItemData {
|
||||
public static final String[] IMAGE_PROJECTION = new String[] {
|
||||
Media._ID,
|
||||
Media.DATA,
|
||||
Media.WIDTH,
|
||||
Media.HEIGHT,
|
||||
Media.MIME_TYPE,
|
||||
Media.DATE_MODIFIED};
|
||||
|
||||
public static final String[] SPECIAL_ITEM_COLUMNS = new String[] {
|
||||
BaseColumns._ID
|
||||
};
|
||||
|
||||
private static final int INDEX_ID = 0;
|
||||
|
||||
// For local image gallery.
|
||||
private static final int INDEX_DATA_PATH = 1;
|
||||
private static final int INDEX_WIDTH = 2;
|
||||
private static final int INDEX_HEIGHT = 3;
|
||||
private static final int INDEX_MIME_TYPE = 4;
|
||||
private static final int INDEX_DATE_MODIFIED = 5;
|
||||
|
||||
/** A special item's id for picking images from document picker */
|
||||
public static final String ID_DOCUMENT_PICKER_ITEM = "-1";
|
||||
|
||||
private UriImageRequestDescriptor mImageData;
|
||||
private String mContentType;
|
||||
private boolean mIsDocumentPickerItem;
|
||||
private long mDateSeconds;
|
||||
|
||||
public GalleryGridItemData() {
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor, final int desiredWidth, final int desiredHeight) {
|
||||
mIsDocumentPickerItem = TextUtils.equals(cursor.getString(INDEX_ID),
|
||||
ID_DOCUMENT_PICKER_ITEM);
|
||||
if (mIsDocumentPickerItem) {
|
||||
mImageData = null;
|
||||
mContentType = null;
|
||||
} else {
|
||||
int sourceWidth = cursor.getInt(INDEX_WIDTH);
|
||||
int sourceHeight = cursor.getInt(INDEX_HEIGHT);
|
||||
|
||||
// Guard against bad data
|
||||
if (sourceWidth <= 0) {
|
||||
sourceWidth = ImageRequest.UNSPECIFIED_SIZE;
|
||||
}
|
||||
if (sourceHeight <= 0) {
|
||||
sourceHeight = ImageRequest.UNSPECIFIED_SIZE;
|
||||
}
|
||||
|
||||
mContentType = cursor.getString(INDEX_MIME_TYPE);
|
||||
final String dateModified = cursor.getString(INDEX_DATE_MODIFIED);
|
||||
mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1;
|
||||
mImageData = new FileImageRequestDescriptor(
|
||||
cursor.getString(INDEX_DATA_PATH),
|
||||
desiredWidth,
|
||||
desiredHeight,
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
true /* canUseThumbnail */,
|
||||
true /* allowCompression */,
|
||||
true /* isStatic */);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDocumentPickerItem() {
|
||||
return mIsDocumentPickerItem;
|
||||
}
|
||||
|
||||
public Uri getImageUri() {
|
||||
return mImageData.uri;
|
||||
}
|
||||
|
||||
public UriImageRequestDescriptor getImageRequestDescriptor() {
|
||||
return mImageData;
|
||||
}
|
||||
|
||||
public MessagePartData constructMessagePartData(final Rect startRect) {
|
||||
Assert.isTrue(!mIsDocumentPickerItem);
|
||||
return new MediaPickerMessagePartData(startRect, mContentType,
|
||||
mImageData.uri, mImageData.sourceWidth, mImageData.sourceHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The date in seconds. This can be negative if we could not retreive date info
|
||||
*/
|
||||
public long getDateSeconds() {
|
||||
return mDateSeconds;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return mContentType;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import com.android.messaging.datamodel.action.ActionMonitor;
|
||||
import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
|
||||
import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
|
||||
import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.Assert.RunsOnMainThread;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
public class LaunchConversationData extends BindableData implements
|
||||
GetOrCreateConversationActionListener {
|
||||
public interface LaunchConversationDataListener {
|
||||
void onGetOrCreateNewConversation(String conversationId);
|
||||
void onGetOrCreateNewConversationFailed();
|
||||
}
|
||||
|
||||
private LaunchConversationDataListener mListener;
|
||||
private GetOrCreateConversationActionMonitor mMonitor;
|
||||
|
||||
public LaunchConversationData(final LaunchConversationDataListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
if (mMonitor != null) {
|
||||
mMonitor.unregister();
|
||||
}
|
||||
mMonitor = null;
|
||||
}
|
||||
|
||||
public void getOrCreateConversation(final BindingBase<LaunchConversationData> binding,
|
||||
final String[] recipients) {
|
||||
final String bindingId = binding.getBindingId();
|
||||
|
||||
// Start a new conversation from the list of contacts.
|
||||
if (isBound(bindingId) && mMonitor == null) {
|
||||
mMonitor = GetOrCreateConversationAction.getOrCreateConversation(recipients,
|
||||
bindingId, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RunsOnMainThread
|
||||
public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
|
||||
final Object data, final String conversationId) {
|
||||
Assert.isTrue(monitor == mMonitor);
|
||||
Assert.isTrue(conversationId != null);
|
||||
|
||||
final String bindingId = (String) data;
|
||||
if (isBound(bindingId) && mListener != null) {
|
||||
mListener.onGetOrCreateNewConversation(conversationId);
|
||||
}
|
||||
|
||||
mMonitor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RunsOnMainThread
|
||||
public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
|
||||
final Object data) {
|
||||
Assert.isTrue(monitor == mMonitor);
|
||||
final String bindingId = (String) data;
|
||||
if (isBound(bindingId) && mListener != null) {
|
||||
mListener.onGetOrCreateNewConversationFailed();
|
||||
}
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
|
||||
mMonitor = null;
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.GalleryBoundCursorLoader;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.BuglePrefs;
|
||||
import com.android.messaging.util.BuglePrefsKeys;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
/**
|
||||
* Services data needs for MediaPicker.
|
||||
*/
|
||||
public class MediaPickerData extends BindableData {
|
||||
public interface MediaPickerDataListener {
|
||||
void onMediaPickerDataUpdated(MediaPickerData mediaPickerData, Object data, int loaderId);
|
||||
}
|
||||
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
private final Context mContext;
|
||||
private LoaderManager mLoaderManager;
|
||||
private final GalleryLoaderCallbacks mGalleryLoaderCallbacks;
|
||||
private MediaPickerDataListener mListener;
|
||||
|
||||
public MediaPickerData(final Context context) {
|
||||
mContext = context;
|
||||
mGalleryLoaderCallbacks = new GalleryLoaderCallbacks();
|
||||
}
|
||||
|
||||
public static final int GALLERY_IMAGE_LOADER = 1;
|
||||
|
||||
/**
|
||||
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
|
||||
*/
|
||||
private class GalleryLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
switch (id) {
|
||||
case GALLERY_IMAGE_LOADER:
|
||||
return new GalleryBoundCursorLoader(bindingId, mContext);
|
||||
|
||||
default:
|
||||
Assert.fail("Unknown loader id for gallery picker!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the media picker");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
if (isBound(cursorLoader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case GALLERY_IMAGE_LOADER:
|
||||
mListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
|
||||
GALLERY_IMAGE_LOADER);
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.fail("Unknown loader id for gallery picker!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the media picker");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> loader) {
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
if (isBound(cursorLoader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case GALLERY_IMAGE_LOADER:
|
||||
mListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
|
||||
GALLERY_IMAGE_LOADER);
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.fail("Unknown loader id for media picker!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the media picker");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void startLoader(final int loaderId, final BindingBase<MediaPickerData> binding,
|
||||
@Nullable Bundle args, final MediaPickerDataListener listener) {
|
||||
if (args == null) {
|
||||
args = new Bundle();
|
||||
}
|
||||
args.putString(BINDING_ID, binding.getBindingId());
|
||||
if (loaderId == GALLERY_IMAGE_LOADER) {
|
||||
mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
|
||||
} else {
|
||||
Assert.fail("Unsupported loader id for media picker!");
|
||||
}
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void destroyLoader(final int loaderId) {
|
||||
mLoaderManager.destroyLoader(loaderId);
|
||||
}
|
||||
|
||||
public void init(final LoaderManager loaderManager) {
|
||||
mLoaderManager = loaderManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
// This could be null if we bind but the caller doesn't init the BindableData
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(GALLERY_IMAGE_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last selected chooser index, or -1 if no selection has been saved.
|
||||
*/
|
||||
public int getSelectedChooserIndex() {
|
||||
return BuglePrefs.getApplicationPrefs().getInt(
|
||||
BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX,
|
||||
BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the selected media chooser index.
|
||||
* @param selectedIndex the selected media chooser index.
|
||||
*/
|
||||
public void saveSelectedChooserIndex(final int selectedIndex) {
|
||||
BuglePrefs.getApplicationPrefs().putInt(BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX,
|
||||
selectedIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
|
||||
public class MediaPickerMessagePartData extends MessagePartData {
|
||||
private final Rect mStartRect;
|
||||
|
||||
public MediaPickerMessagePartData(final Rect startRect, final String contentType,
|
||||
final Uri contentUri, final int width, final int height) {
|
||||
this(startRect, null /* messageText */, contentType, contentUri, width, height);
|
||||
}
|
||||
|
||||
public MediaPickerMessagePartData(final Rect startRect, final String messageText,
|
||||
final String contentType, final Uri contentUri, final int width, final int height) {
|
||||
this(startRect, messageText, contentType, contentUri, width, height,
|
||||
false /*onlySingleAttachment*/);
|
||||
}
|
||||
|
||||
public MediaPickerMessagePartData(final Rect startRect, final String contentType,
|
||||
final Uri contentUri, final int width, final int height,
|
||||
final boolean onlySingleAttachment) {
|
||||
this(startRect, null /* messageText */, contentType, contentUri, width, height,
|
||||
onlySingleAttachment);
|
||||
}
|
||||
|
||||
public MediaPickerMessagePartData(final Rect startRect, final String messageText,
|
||||
final String contentType, final Uri contentUri, final int width, final int height,
|
||||
final boolean onlySingleAttachment) {
|
||||
super(messageText, contentType, contentUri, width, height, onlySingleAttachment);
|
||||
mStartRect = startRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The starting rect to animate the attachment preview from in order to perform a smooth
|
||||
* transition
|
||||
*/
|
||||
public Rect getStartRect() {
|
||||
return mStartRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the start rect of the attachment.
|
||||
*/
|
||||
public void setStartRect(final Rect startRect) {
|
||||
mStartRect.set(startRect);
|
||||
}
|
||||
}
|
||||
@@ -1,922 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.BugleGservices;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
import com.android.messaging.util.Dates;
|
||||
import com.android.messaging.util.DebugUtils;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MessageData implements Parcelable {
|
||||
private static final String[] sProjection = {
|
||||
MessageColumns._ID,
|
||||
MessageColumns.CONVERSATION_ID,
|
||||
MessageColumns.SENDER_PARTICIPANT_ID,
|
||||
MessageColumns.SELF_PARTICIPANT_ID,
|
||||
MessageColumns.SENT_TIMESTAMP,
|
||||
MessageColumns.RECEIVED_TIMESTAMP,
|
||||
MessageColumns.SEEN,
|
||||
MessageColumns.READ,
|
||||
MessageColumns.PROTOCOL,
|
||||
MessageColumns.STATUS,
|
||||
MessageColumns.SMS_MESSAGE_URI,
|
||||
MessageColumns.SMS_PRIORITY,
|
||||
MessageColumns.SMS_MESSAGE_SIZE,
|
||||
MessageColumns.MMS_SUBJECT,
|
||||
MessageColumns.MMS_TRANSACTION_ID,
|
||||
MessageColumns.MMS_CONTENT_LOCATION,
|
||||
MessageColumns.MMS_EXPIRY,
|
||||
MessageColumns.RAW_TELEPHONY_STATUS,
|
||||
MessageColumns.RETRY_START_TIMESTAMP,
|
||||
};
|
||||
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_CONVERSATION_ID = 1;
|
||||
private static final int INDEX_PARTICIPANT_ID = 2;
|
||||
private static final int INDEX_SELF_ID = 3;
|
||||
private static final int INDEX_SENT_TIMESTAMP = 4;
|
||||
private static final int INDEX_RECEIVED_TIMESTAMP = 5;
|
||||
private static final int INDEX_SEEN = 6;
|
||||
private static final int INDEX_READ = 7;
|
||||
private static final int INDEX_PROTOCOL = 8;
|
||||
private static final int INDEX_BUGLE_STATUS = 9;
|
||||
private static final int INDEX_SMS_MESSAGE_URI = 10;
|
||||
private static final int INDEX_SMS_PRIORITY = 11;
|
||||
private static final int INDEX_SMS_MESSAGE_SIZE = 12;
|
||||
private static final int INDEX_MMS_SUBJECT = 13;
|
||||
private static final int INDEX_MMS_TRANSACTION_ID = 14;
|
||||
private static final int INDEX_MMS_CONTENT_LOCATION = 15;
|
||||
private static final int INDEX_MMS_EXPIRY = 16;
|
||||
private static final int INDEX_RAW_TELEPHONY_STATUS = 17;
|
||||
private static final int INDEX_RETRY_START_TIMESTAMP = 18;
|
||||
|
||||
// SQL statement to insert a "complete" message row (columns based on the projection above).
|
||||
private static final String INSERT_MESSAGE_SQL =
|
||||
"INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( "
|
||||
+ TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1,
|
||||
INDEX_RETRY_START_TIMESTAMP + 1))
|
||||
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
private String mMessageId;
|
||||
private String mConversationId;
|
||||
private String mParticipantId;
|
||||
private String mSelfId;
|
||||
private long mSentTimestamp;
|
||||
private long mReceivedTimestamp;
|
||||
private boolean mSeen;
|
||||
private boolean mRead;
|
||||
private int mProtocol;
|
||||
private Uri mSmsMessageUri;
|
||||
private int mSmsPriority;
|
||||
private long mSmsMessageSize;
|
||||
private String mMmsSubject;
|
||||
private String mMmsTransactionId;
|
||||
private String mMmsContentLocation;
|
||||
private long mMmsExpiry;
|
||||
private int mRawStatus;
|
||||
private int mStatus;
|
||||
private final ArrayList<MessagePartData> mParts;
|
||||
private long mRetryStartTimestamp;
|
||||
|
||||
// PROTOCOL Values
|
||||
public static final int PROTOCOL_UNKNOWN = -1; // Unknown type
|
||||
public static final int PROTOCOL_SMS = 0; // SMS message
|
||||
public static final int PROTOCOL_MMS = 1; // MMS message
|
||||
public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification
|
||||
|
||||
// Bugle STATUS Values
|
||||
public static final int BUGLE_STATUS_UNKNOWN = 0;
|
||||
|
||||
// Outgoing
|
||||
public static final int BUGLE_STATUS_OUTGOING_COMPLETE = 1;
|
||||
public static final int BUGLE_STATUS_OUTGOING_DELIVERED = 2;
|
||||
// Transitions to either YET_TO_SEND or SEND_AFTER_PROCESSING depending attachments.
|
||||
public static final int BUGLE_STATUS_OUTGOING_DRAFT = 3;
|
||||
public static final int BUGLE_STATUS_OUTGOING_YET_TO_SEND = 4;
|
||||
public static final int BUGLE_STATUS_OUTGOING_SENDING = 5;
|
||||
public static final int BUGLE_STATUS_OUTGOING_RESENDING = 6;
|
||||
public static final int BUGLE_STATUS_OUTGOING_AWAITING_RETRY = 7;
|
||||
public static final int BUGLE_STATUS_OUTGOING_FAILED = 8;
|
||||
public static final int BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER = 9;
|
||||
|
||||
// Incoming
|
||||
public static final int BUGLE_STATUS_INCOMING_COMPLETE = 100;
|
||||
public static final int BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD = 101;
|
||||
public static final int BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD = 102;
|
||||
public static final int BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING = 103;
|
||||
public static final int BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD = 104;
|
||||
public static final int BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING = 105;
|
||||
public static final int BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED = 106;
|
||||
public static final int BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE = 107;
|
||||
|
||||
public static final String getStatusDescription(int status) {
|
||||
switch (status) {
|
||||
case BUGLE_STATUS_UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case BUGLE_STATUS_OUTGOING_COMPLETE:
|
||||
return "OUTGOING_COMPLETE";
|
||||
case BUGLE_STATUS_OUTGOING_DELIVERED:
|
||||
return "OUTGOING_DELIVERED";
|
||||
case BUGLE_STATUS_OUTGOING_DRAFT:
|
||||
return "OUTGOING_DRAFT";
|
||||
case BUGLE_STATUS_OUTGOING_YET_TO_SEND:
|
||||
return "OUTGOING_YET_TO_SEND";
|
||||
case BUGLE_STATUS_OUTGOING_SENDING:
|
||||
return "OUTGOING_SENDING";
|
||||
case BUGLE_STATUS_OUTGOING_RESENDING:
|
||||
return "OUTGOING_RESENDING";
|
||||
case BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
|
||||
return "OUTGOING_AWAITING_RETRY";
|
||||
case BUGLE_STATUS_OUTGOING_FAILED:
|
||||
return "OUTGOING_FAILED";
|
||||
case BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
|
||||
return "OUTGOING_FAILED_EMERGENCY_NUMBER";
|
||||
case BUGLE_STATUS_INCOMING_COMPLETE:
|
||||
return "INCOMING_COMPLETE";
|
||||
case BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
|
||||
return "INCOMING_YET_TO_MANUAL_DOWNLOAD";
|
||||
case BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
|
||||
return "INCOMING_RETRYING_MANUAL_DOWNLOAD";
|
||||
case BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
|
||||
return "INCOMING_MANUAL_DOWNLOADING";
|
||||
case BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
|
||||
return "INCOMING_RETRYING_AUTO_DOWNLOAD";
|
||||
case BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
|
||||
return "INCOMING_AUTO_DOWNLOADING";
|
||||
case BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
|
||||
return "INCOMING_DOWNLOAD_FAILED";
|
||||
case BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
|
||||
return "INCOMING_EXPIRED_OR_NOT_AVAILABLE";
|
||||
default:
|
||||
return String.valueOf(status) + " (check MessageData)";
|
||||
}
|
||||
}
|
||||
|
||||
// All incoming messages expect to have status >= BUGLE_STATUS_FIRST_INCOMING
|
||||
public static final int BUGLE_STATUS_FIRST_INCOMING = BUGLE_STATUS_INCOMING_COMPLETE;
|
||||
|
||||
// Detailed MMS failures. Most of the values are defined in PduHeaders. However, a few are
|
||||
// defined here instead. These are never returned in the MMS HTTP response, but are used
|
||||
// internally. The values here must not conflict with any of the existing PduHeader values.
|
||||
public static final int RAW_TELEPHONY_STATUS_UNDEFINED = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
|
||||
public static final int RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG = 10000;
|
||||
|
||||
// Unknown result code for MMS sending/downloading. This is used as the default value
|
||||
// for result code returned from platform MMS API.
|
||||
public static final int UNKNOWN_RESULT_CODE = 0;
|
||||
|
||||
/**
|
||||
* Create an "empty" message
|
||||
*/
|
||||
public MessageData() {
|
||||
mParts = new ArrayList<MessagePartData>();
|
||||
}
|
||||
|
||||
public static String[] getProjection() {
|
||||
return sProjection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a draft message for a particular conversation based on supplied content
|
||||
*/
|
||||
public static MessageData createDraftMessage(final String conversationId,
|
||||
final String selfId, final MessageData content) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
||||
message.mProtocol = PROTOCOL_UNKNOWN;
|
||||
message.mConversationId = conversationId;
|
||||
message.mParticipantId = selfId;
|
||||
message.mReceivedTimestamp = System.currentTimeMillis();
|
||||
if (content == null) {
|
||||
message.mParts.add(MessagePartData.createTextMessagePart(""));
|
||||
} else {
|
||||
if (!TextUtils.isEmpty(content.mParticipantId)) {
|
||||
message.mParticipantId = content.mParticipantId;
|
||||
}
|
||||
if (!TextUtils.isEmpty(content.mMmsSubject)) {
|
||||
message.mMmsSubject = content.mMmsSubject;
|
||||
}
|
||||
for (final MessagePartData part : content.getParts()) {
|
||||
message.mParts.add(part);
|
||||
}
|
||||
}
|
||||
message.mSelfId = selfId;
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a draft sms message for a particular conversation
|
||||
*/
|
||||
public static MessageData createDraftSmsMessage(final String conversationId,
|
||||
final String selfId, final String messageText) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
||||
message.mProtocol = PROTOCOL_SMS;
|
||||
message.mConversationId = conversationId;
|
||||
message.mParticipantId = selfId;
|
||||
message.mSelfId = selfId;
|
||||
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
||||
message.mReceivedTimestamp = System.currentTimeMillis();
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a draft mms message for a particular conversation
|
||||
*/
|
||||
public static MessageData createDraftMmsMessage(final String conversationId,
|
||||
final String selfId, final String messageText, final String subjectText) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
||||
message.mProtocol = PROTOCOL_MMS;
|
||||
message.mConversationId = conversationId;
|
||||
message.mParticipantId = selfId;
|
||||
message.mSelfId = selfId;
|
||||
message.mMmsSubject = subjectText;
|
||||
message.mReceivedTimestamp = System.currentTimeMillis();
|
||||
if (!TextUtils.isEmpty(messageText)) {
|
||||
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message received from a particular number in a particular conversation
|
||||
*/
|
||||
public static MessageData createReceivedSmsMessage(final Uri uri, final String conversationId,
|
||||
final String participantId, final String selfId, final String messageText,
|
||||
final String subject, final long sent, final long recieved,
|
||||
final boolean seen, final boolean read) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mSmsMessageUri = uri;
|
||||
message.mConversationId = conversationId;
|
||||
message.mParticipantId = participantId;
|
||||
message.mSelfId = selfId;
|
||||
message.mProtocol = PROTOCOL_SMS;
|
||||
message.mStatus = BUGLE_STATUS_INCOMING_COMPLETE;
|
||||
message.mMmsSubject = subject;
|
||||
message.mReceivedTimestamp = recieved;
|
||||
message.mSentTimestamp = sent;
|
||||
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
||||
message.mSeen = seen;
|
||||
message.mRead = read;
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message not yet associated with a particular conversation
|
||||
*/
|
||||
public static MessageData createSharedMessage(final String messageText) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
|
||||
if (!TextUtils.isEmpty(messageText)) {
|
||||
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message from Sms table fields
|
||||
*/
|
||||
public static MessageData createSmsMessage(final String messageUri, final String participantId,
|
||||
final String selfId, final String conversationId, final int bugleStatus,
|
||||
final boolean seen, final boolean read, final long sent,
|
||||
final long recieved, final String messageText) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mParticipantId = participantId;
|
||||
message.mSelfId = selfId;
|
||||
message.mConversationId = conversationId;
|
||||
message.mSentTimestamp = sent;
|
||||
message.mReceivedTimestamp = recieved;
|
||||
message.mSeen = seen;
|
||||
message.mRead = read;
|
||||
message.mProtocol = PROTOCOL_SMS;
|
||||
message.mStatus = bugleStatus;
|
||||
message.mSmsMessageUri = Uri.parse(messageUri);
|
||||
message.mParts.add(MessagePartData.createTextMessagePart(messageText));
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message from Mms table fields
|
||||
*/
|
||||
public static MessageData createMmsMessage(final String messageUri, final String participantId,
|
||||
final String selfId, final String conversationId, final boolean isNotification,
|
||||
final int bugleStatus, final String contentLocation, final String transactionId,
|
||||
final int smsPriority, final String subject, final boolean seen, final boolean read,
|
||||
final long size, final int rawStatus, final long expiry, final long sent,
|
||||
final long received) {
|
||||
final MessageData message = new MessageData();
|
||||
message.mParticipantId = participantId;
|
||||
message.mSelfId = selfId;
|
||||
message.mConversationId = conversationId;
|
||||
message.mSentTimestamp = sent;
|
||||
message.mReceivedTimestamp = received;
|
||||
message.mMmsContentLocation = contentLocation;
|
||||
message.mMmsTransactionId = transactionId;
|
||||
message.mSeen = seen;
|
||||
message.mRead = read;
|
||||
message.mStatus = bugleStatus;
|
||||
message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS);
|
||||
message.mSmsMessageUri = Uri.parse(messageUri);
|
||||
message.mSmsPriority = smsPriority;
|
||||
message.mSmsMessageSize = size;
|
||||
message.mMmsSubject = subject;
|
||||
message.mMmsExpiry = expiry;
|
||||
message.mRawStatus = rawStatus;
|
||||
if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD ||
|
||||
bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) {
|
||||
// Set the retry start timestamp if this message is already in process of retrying
|
||||
// Either as autodownload is starting or sending already in progress (MMS update)
|
||||
message.mRetryStartTimestamp = received;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public void addPart(final MessagePartData part) {
|
||||
if (part instanceof PendingAttachmentData) {
|
||||
// Pending attachments may only be added to shared message data that's not associated
|
||||
// with any particular conversation, in order to store shared images.
|
||||
Assert.isTrue(mConversationId == null);
|
||||
}
|
||||
mParts.add(part);
|
||||
}
|
||||
|
||||
public Iterable<MessagePartData> getParts() {
|
||||
return mParts;
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor) {
|
||||
mMessageId = cursor.getString(INDEX_ID);
|
||||
mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
|
||||
mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
|
||||
mSelfId = cursor.getString(INDEX_SELF_ID);
|
||||
mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
|
||||
mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
|
||||
mSeen = (cursor.getInt(INDEX_SEEN) != 0);
|
||||
mRead = (cursor.getInt(INDEX_READ) != 0);
|
||||
mProtocol = cursor.getInt(INDEX_PROTOCOL);
|
||||
mStatus = cursor.getInt(INDEX_BUGLE_STATUS);
|
||||
final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
|
||||
mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri);
|
||||
mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
|
||||
mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE);
|
||||
mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
|
||||
mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
|
||||
mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
|
||||
mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID);
|
||||
mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION);
|
||||
mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to the draft message data for a conversation. The conversation's self id is used as
|
||||
* the draft's self id.
|
||||
*/
|
||||
public void bindDraft(final Cursor cursor, final String conversationSelfId) {
|
||||
bind(cursor);
|
||||
mSelfId = conversationSelfId;
|
||||
}
|
||||
|
||||
protected static String getParticipantId(final Cursor cursor) {
|
||||
return cursor.getString(INDEX_PARTICIPANT_ID);
|
||||
}
|
||||
|
||||
public void populate(final ContentValues values) {
|
||||
values.put(MessageColumns.CONVERSATION_ID, mConversationId);
|
||||
values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId);
|
||||
values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId);
|
||||
values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp);
|
||||
values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp);
|
||||
values.put(MessageColumns.SEEN, mSeen ? 1 : 0);
|
||||
values.put(MessageColumns.READ, mRead ? 1 : 0);
|
||||
values.put(MessageColumns.PROTOCOL, mProtocol);
|
||||
values.put(MessageColumns.STATUS, mStatus);
|
||||
final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString());
|
||||
values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri);
|
||||
values.put(MessageColumns.SMS_PRIORITY, mSmsPriority);
|
||||
values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize);
|
||||
values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry);
|
||||
values.put(MessageColumns.MMS_SUBJECT, mMmsSubject);
|
||||
values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId);
|
||||
values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation);
|
||||
values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus);
|
||||
values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note this is not thread safe so callers need to make sure they own the wrapper + statements
|
||||
* while they call this and use the returned value.
|
||||
*/
|
||||
public SQLiteStatement getInsertStatement(final DatabaseWrapper db) {
|
||||
final SQLiteStatement insert = db.getStatementInTransaction(
|
||||
DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL);
|
||||
insert.clearBindings();
|
||||
insert.bindString(INDEX_CONVERSATION_ID, mConversationId);
|
||||
insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId);
|
||||
insert.bindString(INDEX_SELF_ID, mSelfId);
|
||||
insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp);
|
||||
insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp);
|
||||
insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0);
|
||||
insert.bindLong(INDEX_READ, mRead ? 1 : 0);
|
||||
insert.bindLong(INDEX_PROTOCOL, mProtocol);
|
||||
insert.bindLong(INDEX_BUGLE_STATUS, mStatus);
|
||||
if (mSmsMessageUri != null) {
|
||||
insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString());
|
||||
}
|
||||
insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority);
|
||||
insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize);
|
||||
insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry);
|
||||
if (mMmsSubject != null) {
|
||||
insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject);
|
||||
}
|
||||
if (mMmsTransactionId != null) {
|
||||
insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId);
|
||||
}
|
||||
if (mMmsContentLocation != null) {
|
||||
insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation);
|
||||
}
|
||||
insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus);
|
||||
insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp);
|
||||
return insert;
|
||||
}
|
||||
|
||||
public final String getMessageId() {
|
||||
return mMessageId;
|
||||
}
|
||||
|
||||
public final String getConversationId() {
|
||||
return mConversationId;
|
||||
}
|
||||
|
||||
public final String getParticipantId() {
|
||||
return mParticipantId;
|
||||
}
|
||||
|
||||
public final String getSelfId() {
|
||||
return mSelfId;
|
||||
}
|
||||
|
||||
public final long getSentTimeStamp() {
|
||||
return mSentTimestamp;
|
||||
}
|
||||
|
||||
public final long getReceivedTimeStamp() {
|
||||
return mReceivedTimestamp;
|
||||
}
|
||||
|
||||
public final String getFormattedReceivedTimeStamp() {
|
||||
return Dates.getMessageTimeString(mReceivedTimestamp).toString();
|
||||
}
|
||||
|
||||
public final int getProtocol() {
|
||||
return mProtocol;
|
||||
}
|
||||
|
||||
public final int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
public final Uri getSmsMessageUri() {
|
||||
return mSmsMessageUri;
|
||||
}
|
||||
|
||||
public final int getSmsPriority() {
|
||||
return mSmsPriority;
|
||||
}
|
||||
|
||||
public final long getSmsMessageSize() {
|
||||
return mSmsMessageSize;
|
||||
}
|
||||
|
||||
public final String getMmsSubject() {
|
||||
return mMmsSubject;
|
||||
}
|
||||
|
||||
public final void setMmsSubject(final String subject) {
|
||||
mMmsSubject = subject;
|
||||
}
|
||||
|
||||
public final String getMmsContentLocation() {
|
||||
return mMmsContentLocation;
|
||||
}
|
||||
|
||||
public final String getMmsTransactionId() {
|
||||
return mMmsTransactionId;
|
||||
}
|
||||
|
||||
public final boolean getMessageSeen() {
|
||||
return mSeen;
|
||||
}
|
||||
|
||||
/**
|
||||
* For incoming MMS messages this returns the retrieve-status value
|
||||
* For sent MMS messages this returns the response-status value
|
||||
* See PduHeaders.java for possible values
|
||||
* Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED
|
||||
*/
|
||||
public final int getRawTelephonyStatus() {
|
||||
return mRawStatus;
|
||||
}
|
||||
|
||||
public final void setMessageSeen(final boolean hasSeen) {
|
||||
mSeen = hasSeen;
|
||||
}
|
||||
|
||||
public final boolean getInResendWindow(final long now) {
|
||||
final long maxAgeToResend = BugleGservices.get().getLong(
|
||||
BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS,
|
||||
BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT);
|
||||
final long age = now - mRetryStartTimestamp;
|
||||
return age < maxAgeToResend;
|
||||
}
|
||||
|
||||
public final boolean getInDownloadWindow(final long now) {
|
||||
final long maxAgeToRedownload = BugleGservices.get().getLong(
|
||||
BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS,
|
||||
BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT);
|
||||
final long age = now - mRetryStartTimestamp;
|
||||
return age < maxAgeToRedownload;
|
||||
}
|
||||
|
||||
static boolean getShowDownloadMessage(final int status) {
|
||||
if (OsUtil.isSecondaryUser()) {
|
||||
// Secondary users can't download mms's. Mms's are downloaded by bugle running as the
|
||||
// primary user.
|
||||
return false;
|
||||
}
|
||||
// Should show option for manual download iff status is manual download or failed
|
||||
return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
|
||||
status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
|
||||
// If debug is enabled, allow to download an expired or unavailable message.
|
||||
(DebugUtils.isDebugEnabled()
|
||||
&& status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
|
||||
}
|
||||
|
||||
public boolean canDownloadMessage() {
|
||||
if (OsUtil.isSecondaryUser()) {
|
||||
// Secondary users can't download mms's. Mms's are downloaded by bugle running as the
|
||||
// primary user.
|
||||
return false;
|
||||
}
|
||||
// Can download iff status is retrying auto/manual downloading
|
||||
return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD ||
|
||||
mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD);
|
||||
}
|
||||
|
||||
public boolean canRedownloadMessage() {
|
||||
if (OsUtil.isSecondaryUser()) {
|
||||
// Secondary users can't download mms's. Mms's are downloaded by bugle running as the
|
||||
// primary user.
|
||||
return false;
|
||||
}
|
||||
// Can redownload iff status is manual download not started or download failed
|
||||
return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
|
||||
mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
|
||||
// If debug is enabled, allow to download an expired or unavailable message.
|
||||
(DebugUtils.isDebugEnabled()
|
||||
&& mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
|
||||
}
|
||||
|
||||
static boolean getShowResendMessage(final int status) {
|
||||
// Should show option to resend iff status is failed
|
||||
return (status == BUGLE_STATUS_OUTGOING_FAILED);
|
||||
}
|
||||
|
||||
static boolean getOneClickResendMessage(final int status, final int rawStatus) {
|
||||
// Should show option to resend iff status is failed
|
||||
return (status == BUGLE_STATUS_OUTGOING_FAILED
|
||||
&& rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED);
|
||||
}
|
||||
|
||||
public boolean canResendMessage() {
|
||||
// Manual retry allowed only from failed
|
||||
return (mStatus == BUGLE_STATUS_OUTGOING_FAILED);
|
||||
}
|
||||
|
||||
public boolean canSendMessage() {
|
||||
// Sending messages must be in yet_to_send or awaiting_retry state
|
||||
return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
|
||||
mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY);
|
||||
}
|
||||
|
||||
public final boolean getYetToSend() {
|
||||
return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND);
|
||||
}
|
||||
|
||||
public final boolean getIsMms() {
|
||||
return mProtocol == MessageData.PROTOCOL_MMS
|
||||
|| mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
|
||||
}
|
||||
|
||||
public static final boolean getIsMmsNotification(final int protocol) {
|
||||
return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
|
||||
}
|
||||
|
||||
public final boolean getIsMmsNotification() {
|
||||
return getIsMmsNotification(mProtocol);
|
||||
}
|
||||
|
||||
public static final boolean getIsSms(final int protocol) {
|
||||
return protocol == (MessageData.PROTOCOL_SMS);
|
||||
}
|
||||
|
||||
public final boolean getIsSms() {
|
||||
return getIsSms(mProtocol);
|
||||
}
|
||||
|
||||
public static boolean getIsIncoming(final int status) {
|
||||
return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
|
||||
}
|
||||
|
||||
public boolean getIsIncoming() {
|
||||
return getIsIncoming(mStatus);
|
||||
}
|
||||
|
||||
public long getRetryStartTimestamp() {
|
||||
return mRetryStartTimestamp;
|
||||
}
|
||||
|
||||
public final String getMessageText() {
|
||||
final String separator = System.getProperty("line.separator");
|
||||
final StringBuilder text = new StringBuilder();
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
|
||||
if (text.length() > 0) {
|
||||
text.append(separator);
|
||||
}
|
||||
text.append(part.getText());
|
||||
}
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all captions from attachments and adds them as a prefix to the first text part or
|
||||
* appends a text part
|
||||
*/
|
||||
public final void consolidateText() {
|
||||
final String separator = System.getProperty("line.separator");
|
||||
final StringBuilder captionText = new StringBuilder();
|
||||
MessagePartData firstTextPart = null;
|
||||
int firstTextPartIndex = -1;
|
||||
for (int i = 0; i < mParts.size(); i++) {
|
||||
final MessagePartData part = mParts.get(i);
|
||||
if (firstTextPart == null && !part.isAttachment()) {
|
||||
firstTextPart = part;
|
||||
firstTextPartIndex = i;
|
||||
}
|
||||
if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
|
||||
if (captionText.length() > 0) {
|
||||
captionText.append(separator);
|
||||
}
|
||||
captionText.append(part.getText());
|
||||
}
|
||||
}
|
||||
|
||||
if (captionText.length() == 0) {
|
||||
// Nothing to consolidate
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstTextPart == null) {
|
||||
addPart(MessagePartData.createTextMessagePart(captionText.toString()));
|
||||
} else {
|
||||
final String partText = firstTextPart.getText();
|
||||
if (partText.length() > 0) {
|
||||
captionText.append(separator);
|
||||
captionText.append(partText);
|
||||
}
|
||||
mParts.set(firstTextPartIndex,
|
||||
MessagePartData.createTextMessagePart(captionText.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public final MessagePartData getFirstAttachment() {
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (part.isAttachment()) {
|
||||
return part;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the messageId for this message.
|
||||
* Can be used to reset the messageId prior to persisting (which will assign a new messageId)
|
||||
* or can be called on a message that does not yet have a valid messageId to set it.
|
||||
*/
|
||||
public void updateMessageId(final String messageId) {
|
||||
Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
|
||||
mMessageId = messageId;
|
||||
|
||||
// TODO : This should probably also call updateMessageId on the message parts. We
|
||||
// may also want to make messages effectively immutable once they have a valid message id.
|
||||
}
|
||||
|
||||
public final void updateSendingMessage(final String conversationId, final Uri messageUri,
|
||||
final long timestamp) {
|
||||
mConversationId = conversationId;
|
||||
mSmsMessageUri = messageUri;
|
||||
mRead = true;
|
||||
mSeen = true;
|
||||
mReceivedTimestamp = timestamp;
|
||||
mSentTimestamp = timestamp;
|
||||
mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND;
|
||||
mRetryStartTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public final void markMessageManualResend(final long timestamp) {
|
||||
// Manual send updates timestamp and transitions back to initial sending status.
|
||||
mReceivedTimestamp = timestamp;
|
||||
mSentTimestamp = timestamp;
|
||||
mStatus = BUGLE_STATUS_OUTGOING_SENDING;
|
||||
}
|
||||
|
||||
public final void markMessageSending(final long timestamp) {
|
||||
// Initial send
|
||||
mStatus = BUGLE_STATUS_OUTGOING_SENDING;
|
||||
mSentTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public final void markMessageResending(final long timestamp) {
|
||||
// Auto resend of message
|
||||
mStatus = BUGLE_STATUS_OUTGOING_RESENDING;
|
||||
mSentTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public final void markMessageSent(final long timestamp) {
|
||||
mSentTimestamp = timestamp;
|
||||
mStatus = BUGLE_STATUS_OUTGOING_COMPLETE;
|
||||
}
|
||||
|
||||
public final void markMessageFailed(final long timestamp) {
|
||||
mSentTimestamp = timestamp;
|
||||
mStatus = BUGLE_STATUS_OUTGOING_FAILED;
|
||||
}
|
||||
|
||||
public final void markMessageFailedEmergencyNumber(final long timestamp) {
|
||||
mSentTimestamp = timestamp;
|
||||
mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
|
||||
}
|
||||
|
||||
public final void markMessageNotSent(final long timestamp) {
|
||||
mSentTimestamp = timestamp;
|
||||
mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
|
||||
}
|
||||
|
||||
public final void updateSizesForImageParts() {
|
||||
for (final MessagePartData part : getParts()) {
|
||||
part.decodeAndSaveSizeIfImage(false /* saveToStorage */);
|
||||
}
|
||||
}
|
||||
|
||||
public final void setRetryStartTimestamp(final long timestamp) {
|
||||
mRetryStartTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public final void setRawTelephonyStatus(final int rawStatus) {
|
||||
mRawStatus = rawStatus;
|
||||
}
|
||||
|
||||
public boolean hasContent() {
|
||||
return !TextUtils.isEmpty(mMmsSubject) ||
|
||||
getFirstAttachment() != null ||
|
||||
!TextUtils.isEmpty(getMessageText());
|
||||
}
|
||||
|
||||
public final void bindSelfId(final String selfId) {
|
||||
mSelfId = selfId;
|
||||
}
|
||||
|
||||
public final void bindParticipantId(final String participantId) {
|
||||
mParticipantId = participantId;
|
||||
}
|
||||
|
||||
protected MessageData(final Parcel in) {
|
||||
mMessageId = in.readString();
|
||||
mConversationId = in.readString();
|
||||
mParticipantId = in.readString();
|
||||
mSelfId = in.readString();
|
||||
mSentTimestamp = in.readLong();
|
||||
mReceivedTimestamp = in.readLong();
|
||||
mSeen = (in.readInt() != 0);
|
||||
mRead = (in.readInt() != 0);
|
||||
mProtocol = in.readInt();
|
||||
mStatus = in.readInt();
|
||||
final String smsMessageUri = in.readString();
|
||||
mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri));
|
||||
mSmsPriority = in.readInt();
|
||||
mSmsMessageSize = in.readLong();
|
||||
mMmsExpiry = in.readLong();
|
||||
mMmsSubject = in.readString();
|
||||
mMmsTransactionId = in.readString();
|
||||
mMmsContentLocation = in.readString();
|
||||
mRawStatus = in.readInt();
|
||||
mRetryStartTimestamp = in.readLong();
|
||||
|
||||
// Read parts
|
||||
mParts = new ArrayList<MessagePartData>();
|
||||
final int partCount = in.readInt();
|
||||
for (int i = 0; i < partCount; i++) {
|
||||
mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeString(mMessageId);
|
||||
dest.writeString(mConversationId);
|
||||
dest.writeString(mParticipantId);
|
||||
dest.writeString(mSelfId);
|
||||
dest.writeLong(mSentTimestamp);
|
||||
dest.writeLong(mReceivedTimestamp);
|
||||
dest.writeInt(mRead ? 1 : 0);
|
||||
dest.writeInt(mSeen ? 1 : 0);
|
||||
dest.writeInt(mProtocol);
|
||||
dest.writeInt(mStatus);
|
||||
final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString();
|
||||
dest.writeString(smsMessageUri);
|
||||
dest.writeInt(mSmsPriority);
|
||||
dest.writeLong(mSmsMessageSize);
|
||||
dest.writeLong(mMmsExpiry);
|
||||
dest.writeString(mMmsSubject);
|
||||
dest.writeString(mMmsTransactionId);
|
||||
dest.writeString(mMmsContentLocation);
|
||||
dest.writeInt(mRawStatus);
|
||||
dest.writeLong(mRetryStartTimestamp);
|
||||
|
||||
// Write parts
|
||||
dest.writeInt(mParts.size());
|
||||
for (final MessagePartData messagePartData : mParts) {
|
||||
dest.writeParcelable(messagePartData, flags);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MessageData> CREATOR
|
||||
= new Parcelable.Creator<MessageData>() {
|
||||
@Override
|
||||
public MessageData createFromParcel(final Parcel in) {
|
||||
return new MessageData(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageData[] newArray(final int size) {
|
||||
return new MessageData[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(mMessageId, mParts);
|
||||
}
|
||||
|
||||
public static String toString(String messageId, List<MessagePartData> parts) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (messageId != null) {
|
||||
sb.append(messageId);
|
||||
sb.append(": ");
|
||||
}
|
||||
for (MessagePartData part : parts) {
|
||||
sb.append(part.toString());
|
||||
sb.append(" ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.datamodel.MediaScratchFileProvider;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction;
|
||||
import com.android.messaging.datamodel.media.ImageRequest;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
|
||||
import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.GifTranscoder;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Represents a single message part. Messages consist of one or more parts which may contain
|
||||
* either text or media.
|
||||
*/
|
||||
public class MessagePartData implements Parcelable {
|
||||
public static final int UNSPECIFIED_SIZE = MessagingContentProvider.UNSPECIFIED_SIZE;
|
||||
public static final String[] ACCEPTABLE_IMAGE_TYPES =
|
||||
new String[] { ContentType.IMAGE_JPEG, ContentType.IMAGE_JPG, ContentType.IMAGE_PNG,
|
||||
ContentType.IMAGE_GIF };
|
||||
|
||||
private static final String[] sProjection = {
|
||||
PartColumns._ID,
|
||||
PartColumns.MESSAGE_ID,
|
||||
PartColumns.TEXT,
|
||||
PartColumns.CONTENT_URI,
|
||||
PartColumns.CONTENT_TYPE,
|
||||
PartColumns.WIDTH,
|
||||
PartColumns.HEIGHT,
|
||||
};
|
||||
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_MESSAGE_ID = 1;
|
||||
private static final int INDEX_TEXT = 2;
|
||||
private static final int INDEX_CONTENT_URI = 3;
|
||||
private static final int INDEX_CONTENT_TYPE = 4;
|
||||
private static final int INDEX_WIDTH = 5;
|
||||
private static final int INDEX_HEIGHT = 6;
|
||||
// This isn't part of the projection
|
||||
private static final int INDEX_CONVERSATION_ID = 7;
|
||||
|
||||
// SQL statement to insert a "complete" message part row (columns based on projection above).
|
||||
private static final String INSERT_MESSAGE_PART_SQL =
|
||||
"INSERT INTO " + DatabaseHelper.PARTS_TABLE + " ( "
|
||||
+ TextUtils.join(",", Arrays.copyOfRange(sProjection, 1, INDEX_CONVERSATION_ID))
|
||||
+ ", " + PartColumns.CONVERSATION_ID
|
||||
+ ") VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
// Used for stuff that's ignored or arbitrarily compressed.
|
||||
private static final long NO_MINIMUM_SIZE = 0;
|
||||
|
||||
private String mPartId;
|
||||
private String mMessageId;
|
||||
private String mText;
|
||||
private Uri mContentUri;
|
||||
private String mContentType;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
// This kind of part can only be attached once and with no other attachment
|
||||
private boolean mSinglePartOnly;
|
||||
|
||||
/** Transient data: true if destroy was already called */
|
||||
private boolean mDestroyed;
|
||||
|
||||
/**
|
||||
* Create an "empty" message part
|
||||
*/
|
||||
protected MessagePartData() {
|
||||
this(null, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a populated text message part
|
||||
*/
|
||||
protected MessagePartData(final String messageText) {
|
||||
this(null, messageText, ContentType.TEXT_PLAIN, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE,
|
||||
false /*singlePartOnly*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a populated attachment message part
|
||||
*/
|
||||
protected MessagePartData(final String contentType, final Uri contentUri,
|
||||
final int width, final int height) {
|
||||
this(null, null, contentType, contentUri, width, height, false /*singlePartOnly*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a populated attachment message part, with additional caption text
|
||||
*/
|
||||
protected MessagePartData(final String messageText, final String contentType,
|
||||
final Uri contentUri, final int width, final int height) {
|
||||
this(null, messageText, contentType, contentUri, width, height, false /*singlePartOnly*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a populated attachment message part, with additional caption text, single part only
|
||||
*/
|
||||
protected MessagePartData(final String messageText, final String contentType,
|
||||
final Uri contentUri, final int width, final int height, final boolean singlePartOnly) {
|
||||
this(null, messageText, contentType, contentUri, width, height, singlePartOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a populated message part
|
||||
*/
|
||||
private MessagePartData(final String messageId, final String messageText,
|
||||
final String contentType, final Uri contentUri, final int width, final int height,
|
||||
final boolean singlePartOnly) {
|
||||
mMessageId = messageId;
|
||||
mText = messageText;
|
||||
mContentType = contentType;
|
||||
mContentUri = contentUri;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mSinglePartOnly = singlePartOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "text" message part
|
||||
*/
|
||||
public static MessagePartData createTextMessagePart(final String messageText) {
|
||||
return new MessagePartData(messageText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "media" message part
|
||||
*/
|
||||
public static MessagePartData createMediaMessagePart(final String contentType,
|
||||
final Uri contentUri, final int width, final int height) {
|
||||
return new MessagePartData(contentType, contentUri, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "media" message part with caption
|
||||
*/
|
||||
public static MessagePartData createMediaMessagePart(final String caption,
|
||||
final String contentType, final Uri contentUri, final int width, final int height) {
|
||||
return new MessagePartData(null, caption, contentType, contentUri, width, height,
|
||||
false /*singlePartOnly*/
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty "text" message part
|
||||
*/
|
||||
public static MessagePartData createEmptyMessagePart() {
|
||||
return new MessagePartData("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new message part reading from the cursor
|
||||
*/
|
||||
public static MessagePartData createFromCursor(final Cursor cursor) {
|
||||
final MessagePartData part = new MessagePartData();
|
||||
part.bind(cursor);
|
||||
return part;
|
||||
}
|
||||
|
||||
public static String[] getProjection() {
|
||||
return sProjection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the part id.
|
||||
* Can be used to reset the partId just prior to persisting (which will assign a new partId)
|
||||
* or can be called on a part that does not yet have a valid part id to set it.
|
||||
*/
|
||||
public void updatePartId(final String partId) {
|
||||
Assert.isTrue(TextUtils.isEmpty(partId) || TextUtils.isEmpty(mPartId));
|
||||
mPartId = partId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the messageId for the part.
|
||||
* Can be used to reset the messageId prior to persisting (which will assign a new messageId)
|
||||
* or can be called on a part that does not yet have a valid messageId to set it.
|
||||
*/
|
||||
public void updateMessageId(final String messageId) {
|
||||
Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
|
||||
mMessageId = messageId;
|
||||
}
|
||||
|
||||
protected static String getMessageId(final Cursor cursor) {
|
||||
return cursor.getString(INDEX_MESSAGE_ID);
|
||||
}
|
||||
|
||||
protected void bind(final Cursor cursor) {
|
||||
mPartId = cursor.getString(INDEX_ID);
|
||||
mMessageId = cursor.getString(INDEX_MESSAGE_ID);
|
||||
mText = cursor.getString(INDEX_TEXT);
|
||||
mContentUri = UriUtil.uriFromString(cursor.getString(INDEX_CONTENT_URI));
|
||||
mContentType = cursor.getString(INDEX_CONTENT_TYPE);
|
||||
mWidth = cursor.getInt(INDEX_WIDTH);
|
||||
mHeight = cursor.getInt(INDEX_HEIGHT);
|
||||
}
|
||||
|
||||
public final void populate(final ContentValues values) {
|
||||
// Must have a valid messageId on a part
|
||||
Assert.isTrue(!TextUtils.isEmpty(mMessageId));
|
||||
values.put(PartColumns.MESSAGE_ID, mMessageId);
|
||||
values.put(PartColumns.TEXT, mText);
|
||||
values.put(PartColumns.CONTENT_URI, UriUtil.stringFromUri(mContentUri));
|
||||
values.put(PartColumns.CONTENT_TYPE, mContentType);
|
||||
if (mWidth != UNSPECIFIED_SIZE) {
|
||||
values.put(PartColumns.WIDTH, mWidth);
|
||||
}
|
||||
if (mHeight != UNSPECIFIED_SIZE) {
|
||||
values.put(PartColumns.HEIGHT, mHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note this is not thread safe so callers need to make sure they own the wrapper + statements
|
||||
* while they call this and use the returned value.
|
||||
*/
|
||||
public SQLiteStatement getInsertStatement(final DatabaseWrapper db,
|
||||
final String conversationId) {
|
||||
final SQLiteStatement insert = db.getStatementInTransaction(
|
||||
DatabaseWrapper.INDEX_INSERT_MESSAGE_PART, INSERT_MESSAGE_PART_SQL);
|
||||
insert.clearBindings();
|
||||
insert.bindString(INDEX_MESSAGE_ID, mMessageId);
|
||||
if (mText != null) {
|
||||
insert.bindString(INDEX_TEXT, mText);
|
||||
}
|
||||
if (mContentUri != null) {
|
||||
insert.bindString(INDEX_CONTENT_URI, mContentUri.toString());
|
||||
}
|
||||
if (mContentType != null) {
|
||||
insert.bindString(INDEX_CONTENT_TYPE, mContentType);
|
||||
}
|
||||
insert.bindLong(INDEX_WIDTH, mWidth);
|
||||
insert.bindLong(INDEX_HEIGHT, mHeight);
|
||||
insert.bindString(INDEX_CONVERSATION_ID, conversationId);
|
||||
return insert;
|
||||
}
|
||||
|
||||
public final String getPartId() {
|
||||
return mPartId;
|
||||
}
|
||||
|
||||
public final String getMessageId() {
|
||||
return mMessageId;
|
||||
}
|
||||
|
||||
public final String getText() {
|
||||
return mText;
|
||||
}
|
||||
|
||||
public final Uri getContentUri() {
|
||||
return mContentUri;
|
||||
}
|
||||
|
||||
public boolean isAttachment() {
|
||||
return mContentUri != null;
|
||||
}
|
||||
|
||||
public boolean isText() {
|
||||
return ContentType.isTextType(mContentType);
|
||||
}
|
||||
|
||||
public boolean isImage() {
|
||||
return ContentType.isImageType(mContentType);
|
||||
}
|
||||
|
||||
public boolean isMedia() {
|
||||
return ContentType.isMediaType(mContentType);
|
||||
}
|
||||
|
||||
public boolean isVCard() {
|
||||
return ContentType.isVCardType(mContentType);
|
||||
}
|
||||
|
||||
public boolean isAudio() {
|
||||
return ContentType.isAudioType(mContentType);
|
||||
}
|
||||
|
||||
public boolean isVideo() {
|
||||
return ContentType.isVideoType(mContentType);
|
||||
}
|
||||
|
||||
public final String getContentType() {
|
||||
return mContentType;
|
||||
}
|
||||
|
||||
public final int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public final int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if this part can only exist by itself, with no other attachments
|
||||
*/
|
||||
public boolean getSinglePartOnly() {
|
||||
return mSinglePartOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected MessagePartData(final Parcel in) {
|
||||
mMessageId = in.readString();
|
||||
mText = in.readString();
|
||||
mContentUri = UriUtil.uriFromString(in.readString());
|
||||
mContentType = in.readString();
|
||||
mWidth = in.readInt();
|
||||
mHeight = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
Assert.isTrue(!mDestroyed);
|
||||
dest.writeString(mMessageId);
|
||||
dest.writeString(mText);
|
||||
dest.writeString(UriUtil.stringFromUri(mContentUri));
|
||||
dest.writeString(mContentType);
|
||||
dest.writeInt(mWidth);
|
||||
dest.writeInt(mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof MessagePartData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MessagePartData lhs = (MessagePartData) o;
|
||||
return mWidth == lhs.mWidth && mHeight == lhs.mHeight &&
|
||||
TextUtils.equals(mMessageId, lhs.mMessageId) &&
|
||||
TextUtils.equals(mText, lhs.mText) &&
|
||||
TextUtils.equals(mContentType, lhs.mContentType) &&
|
||||
(mContentUri == null ? lhs.mContentUri == null
|
||||
: mContentUri.equals(lhs.mContentUri));
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + mWidth;
|
||||
result = 31 * result + mHeight;
|
||||
result = 31 * result + (mMessageId == null ? 0 : mMessageId.hashCode());
|
||||
result = 31 * result + (mText == null ? 0 : mText.hashCode());
|
||||
result = 31 * result + (mContentType == null ? 0 : mContentType.hashCode());
|
||||
result = 31 * result + (mContentUri == null ? 0 : mContentUri.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MessagePartData> CREATOR
|
||||
= new Parcelable.Creator<MessagePartData>() {
|
||||
@Override
|
||||
public MessagePartData createFromParcel(final Parcel in) {
|
||||
return new MessagePartData(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagePartData[] newArray(final int size) {
|
||||
return new MessagePartData[size];
|
||||
}
|
||||
};
|
||||
|
||||
protected Uri shouldDestroy() {
|
||||
// We should never double-destroy.
|
||||
Assert.isTrue(!mDestroyed);
|
||||
mDestroyed = true;
|
||||
Uri contentUri = mContentUri;
|
||||
mContentUri = null;
|
||||
mContentType = null;
|
||||
// Only destroy the image if it's staged in our scratch space.
|
||||
if (!MediaScratchFileProvider.isMediaScratchSpaceUri(contentUri)) {
|
||||
contentUri = null;
|
||||
}
|
||||
return contentUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* If application owns content associated with this part delete it (on background thread)
|
||||
*/
|
||||
public void destroyAsync() {
|
||||
final Uri contentUri = shouldDestroy();
|
||||
if (contentUri != null) {
|
||||
SafeAsyncTask.executeOnThreadPool(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Factory.get().getApplicationContext().getContentResolver().delete(
|
||||
contentUri, null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If application owns content associated with this part delete it
|
||||
*/
|
||||
public void destroySync() {
|
||||
final Uri contentUri = shouldDestroy();
|
||||
if (contentUri != null) {
|
||||
Factory.get().getApplicationContext().getContentResolver().delete(
|
||||
contentUri, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is an image part, decode the image header and potentially save the size to the db.
|
||||
*/
|
||||
public void decodeAndSaveSizeIfImage(final boolean saveToStorage) {
|
||||
if (isImage()) {
|
||||
final Rect imageSize = ImageUtils.decodeImageBounds(
|
||||
Factory.get().getApplicationContext(), mContentUri);
|
||||
if (imageSize.width() != ImageRequest.UNSPECIFIED_SIZE &&
|
||||
imageSize.height() != ImageRequest.UNSPECIFIED_SIZE) {
|
||||
mWidth = imageSize.width();
|
||||
mHeight = imageSize.height();
|
||||
if (saveToStorage) {
|
||||
UpdateMessagePartSizeAction.updateSize(mPartId, mWidth, mHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the minimum size that this MessagePartData could be compressed/downsampled/encoded
|
||||
* before sending to meet the maximum message size imposed by the carriers. This is used to
|
||||
* determine right before sending a message whether a message could possibly be sent. If not
|
||||
* then the user is given a chance to unselect some/all of the attachments.
|
||||
*
|
||||
* TODO: computing the minimum size could be expensive. Should we cache the
|
||||
* computed value in db to be retrieved later?
|
||||
*
|
||||
* @return the carrier-independent minimum size, in bytes.
|
||||
*/
|
||||
@DoesNotRunOnMainThread
|
||||
public long getMinimumSizeInBytesForSending() {
|
||||
Assert.isNotMainThread();
|
||||
if (!isAttachment()) {
|
||||
// No limit is imposed on non-attachment part (i.e. plain text), so treat it as zero.
|
||||
return NO_MINIMUM_SIZE;
|
||||
} else if (isImage()) {
|
||||
// GIFs are resized by the native transcoder (exposed by GifTranscoder).
|
||||
if (ImageUtils.isGif(mContentType, mContentUri)) {
|
||||
final long originalImageSize = UriUtil.getContentSize(mContentUri);
|
||||
// Wish we could save the size here, but we don't have a part id yet
|
||||
decodeAndSaveSizeIfImage(false /* saveToStorage */);
|
||||
return GifTranscoder.canBeTranscoded(mWidth, mHeight) ?
|
||||
GifTranscoder.estimateFileSizeAfterTranscode(originalImageSize)
|
||||
: originalImageSize;
|
||||
}
|
||||
// Other images should be arbitrarily resized by ImageResizer before sending.
|
||||
return MmsUtils.MIN_IMAGE_BYTE_SIZE;
|
||||
} else if (isAudio()) {
|
||||
// Audios are already recorded with the lowest sampling settings (AMR_NB), so just
|
||||
// return the file size as the minimum size.
|
||||
return UriUtil.getContentSize(mContentUri);
|
||||
} else if (isVideo()) {
|
||||
final int mediaDurationMs = UriUtil.getMediaDurationMs(mContentUri);
|
||||
return MmsUtils.MIN_VIDEO_BYTES_PER_SECOND * mediaDurationMs
|
||||
/ TimeUnit.SECONDS.toMillis(1);
|
||||
} else if (isVCard()) {
|
||||
// We can't compress vCards.
|
||||
return UriUtil.getContentSize(mContentUri);
|
||||
} else {
|
||||
// This is some unknown media type that we don't know how to handle. Log an error
|
||||
// and try sending it anyway.
|
||||
LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Unknown attachment type " + getContentType());
|
||||
return NO_MINIMUM_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isText()) {
|
||||
return LogUtil.sanitizePII(getText());
|
||||
} else {
|
||||
return getContentType() + " (" + getContentUri() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if this part can only exist by itself, with no other attachments
|
||||
*/
|
||||
public boolean isSinglePartOnly() {
|
||||
return mSinglePartOnly;
|
||||
}
|
||||
|
||||
public void setSinglePartOnly(final boolean isSinglePartOnly) {
|
||||
mSinglePartOnly = isSinglePartOnly;
|
||||
}
|
||||
}
|
||||
@@ -1,569 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v7.mms.MmsManager;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.ex.chips.RecipientEntry;
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.sms.MmsSmsUtils;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.TextUtil;
|
||||
|
||||
/**
|
||||
* A class that encapsulates all of the data for a specific participant in a conversation.
|
||||
*/
|
||||
public class ParticipantData implements Parcelable {
|
||||
// We always use -1 as default/invalid sub id although system may give us anything negative
|
||||
public static final int DEFAULT_SELF_SUB_ID = MmsManager.DEFAULT_SUB_ID;
|
||||
|
||||
// This needs to be something apart from valid or DEFAULT_SELF_SUB_ID
|
||||
public static final int OTHER_THAN_SELF_SUB_ID = DEFAULT_SELF_SUB_ID - 1;
|
||||
|
||||
// Active slot ids are non-negative. Using -1 to designate to inactive self participants.
|
||||
public static final int INVALID_SLOT_ID = -1;
|
||||
|
||||
// TODO: may make sense to move this to common place?
|
||||
public static final long PARTICIPANT_CONTACT_ID_NOT_RESOLVED = -1;
|
||||
public static final long PARTICIPANT_CONTACT_ID_NOT_FOUND = -2;
|
||||
|
||||
public static class ParticipantsQuery {
|
||||
public static final String[] PROJECTION = new String[] {
|
||||
ParticipantColumns._ID,
|
||||
ParticipantColumns.SUB_ID,
|
||||
ParticipantColumns.SIM_SLOT_ID,
|
||||
ParticipantColumns.NORMALIZED_DESTINATION,
|
||||
ParticipantColumns.SEND_DESTINATION,
|
||||
ParticipantColumns.DISPLAY_DESTINATION,
|
||||
ParticipantColumns.FULL_NAME,
|
||||
ParticipantColumns.FIRST_NAME,
|
||||
ParticipantColumns.PROFILE_PHOTO_URI,
|
||||
ParticipantColumns.CONTACT_ID,
|
||||
ParticipantColumns.LOOKUP_KEY,
|
||||
ParticipantColumns.BLOCKED,
|
||||
ParticipantColumns.SUBSCRIPTION_COLOR,
|
||||
ParticipantColumns.SUBSCRIPTION_NAME,
|
||||
ParticipantColumns.CONTACT_DESTINATION,
|
||||
};
|
||||
|
||||
public static final int INDEX_ID = 0;
|
||||
public static final int INDEX_SUB_ID = 1;
|
||||
public static final int INDEX_SIM_SLOT_ID = 2;
|
||||
public static final int INDEX_NORMALIZED_DESTINATION = 3;
|
||||
public static final int INDEX_SEND_DESTINATION = 4;
|
||||
public static final int INDEX_DISPLAY_DESTINATION = 5;
|
||||
public static final int INDEX_FULL_NAME = 6;
|
||||
public static final int INDEX_FIRST_NAME = 7;
|
||||
public static final int INDEX_PROFILE_PHOTO_URI = 8;
|
||||
public static final int INDEX_CONTACT_ID = 9;
|
||||
public static final int INDEX_LOOKUP_KEY = 10;
|
||||
public static final int INDEX_BLOCKED = 11;
|
||||
public static final int INDEX_SUBSCRIPTION_COLOR = 12;
|
||||
public static final int INDEX_SUBSCRIPTION_NAME = 13;
|
||||
public static final int INDEX_CONTACT_DESTINATION = 14;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The MMS unknown sender participant entity
|
||||
*/
|
||||
public static String getUnknownSenderDestination() {
|
||||
// This is a hard coded string rather than a localized one because we don't want it to
|
||||
// change when you change locale.
|
||||
return "\u02BCUNKNOWN_SENDER!\u02BC";
|
||||
}
|
||||
|
||||
private String mParticipantId;
|
||||
private int mSubId;
|
||||
private int mSlotId;
|
||||
private String mNormalizedDestination;
|
||||
private String mSendDestination;
|
||||
private String mDisplayDestination;
|
||||
private String mContactDestination;
|
||||
private String mFullName;
|
||||
private String mFirstName;
|
||||
private String mProfilePhotoUri;
|
||||
private long mContactId;
|
||||
private String mLookupKey;
|
||||
private int mSubscriptionColor;
|
||||
private String mSubscriptionName;
|
||||
private boolean mIsEmailAddress;
|
||||
private boolean mBlocked;
|
||||
|
||||
// Don't call constructor directly
|
||||
private ParticipantData() {
|
||||
}
|
||||
|
||||
public static ParticipantData getFromCursor(final Cursor cursor) {
|
||||
final ParticipantData pd = new ParticipantData();
|
||||
pd.mParticipantId = cursor.getString(ParticipantsQuery.INDEX_ID);
|
||||
pd.mSubId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
|
||||
pd.mSlotId = cursor.getInt(ParticipantsQuery.INDEX_SIM_SLOT_ID);
|
||||
pd.mNormalizedDestination = cursor.getString(
|
||||
ParticipantsQuery.INDEX_NORMALIZED_DESTINATION);
|
||||
pd.mSendDestination = cursor.getString(ParticipantsQuery.INDEX_SEND_DESTINATION);
|
||||
pd.mDisplayDestination = cursor.getString(ParticipantsQuery.INDEX_DISPLAY_DESTINATION);
|
||||
pd.mContactDestination = cursor.getString(ParticipantsQuery.INDEX_CONTACT_DESTINATION);
|
||||
pd.mFullName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
|
||||
pd.mFirstName = cursor.getString(ParticipantsQuery.INDEX_FIRST_NAME);
|
||||
pd.mProfilePhotoUri = cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
|
||||
pd.mContactId = cursor.getLong(ParticipantsQuery.INDEX_CONTACT_ID);
|
||||
pd.mLookupKey = cursor.getString(ParticipantsQuery.INDEX_LOOKUP_KEY);
|
||||
pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
|
||||
pd.mBlocked = cursor.getInt(ParticipantsQuery.INDEX_BLOCKED) != 0;
|
||||
pd.mSubscriptionColor = cursor.getInt(ParticipantsQuery.INDEX_SUBSCRIPTION_COLOR);
|
||||
pd.mSubscriptionName = cursor.getString(ParticipantsQuery.INDEX_SUBSCRIPTION_NAME);
|
||||
pd.maybeSetupUnknownSender();
|
||||
return pd;
|
||||
}
|
||||
|
||||
public static ParticipantData getFromId(final DatabaseWrapper dbWrapper,
|
||||
final String participantId) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
|
||||
ParticipantsQuery.PROJECTION,
|
||||
ParticipantColumns._ID + " =?",
|
||||
new String[] { participantId }, null, null, null);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
return ParticipantData.getFromCursor(cursor);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ParticipantData getFromRecipientEntry(final RecipientEntry recipientEntry) {
|
||||
final ParticipantData pd = new ParticipantData();
|
||||
pd.mParticipantId = null;
|
||||
pd.mSubId = OTHER_THAN_SELF_SUB_ID;
|
||||
pd.mSlotId = INVALID_SLOT_ID;
|
||||
pd.mSendDestination = TextUtil.replaceUnicodeDigits(recipientEntry.getDestination());
|
||||
pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
|
||||
pd.mNormalizedDestination = pd.mIsEmailAddress ?
|
||||
pd.mSendDestination :
|
||||
PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
|
||||
pd.mDisplayDestination = pd.mIsEmailAddress ?
|
||||
pd.mNormalizedDestination :
|
||||
PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
|
||||
pd.mFullName = recipientEntry.getDisplayName();
|
||||
pd.mFirstName = null;
|
||||
pd.mProfilePhotoUri = (recipientEntry.getPhotoThumbnailUri() == null) ? null :
|
||||
recipientEntry.getPhotoThumbnailUri().toString();
|
||||
pd.mContactId = recipientEntry.getContactId();
|
||||
if (pd.mContactId < 0) {
|
||||
// ParticipantData only supports real contact ids (>=0) based on faith that the contacts
|
||||
// provider will continue to only use non-negative ids. The UI uses contactId < 0 for
|
||||
// special handling. We convert those to 'not resolved'
|
||||
pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
|
||||
}
|
||||
pd.mLookupKey = recipientEntry.getLookupKey();
|
||||
pd.mBlocked = false;
|
||||
pd.mSubscriptionColor = Color.TRANSPARENT;
|
||||
pd.mSubscriptionName = null;
|
||||
pd.maybeSetupUnknownSender();
|
||||
return pd;
|
||||
}
|
||||
|
||||
// Shared code for getFromRawPhoneBySystemLocale and getFromRawPhoneBySimLocale
|
||||
private static ParticipantData getFromRawPhone(final String phoneNumber) {
|
||||
Assert.isTrue(phoneNumber != null);
|
||||
final ParticipantData pd = new ParticipantData();
|
||||
pd.mParticipantId = null;
|
||||
pd.mSubId = OTHER_THAN_SELF_SUB_ID;
|
||||
pd.mSlotId = INVALID_SLOT_ID;
|
||||
pd.mSendDestination = TextUtil.replaceUnicodeDigits(phoneNumber);
|
||||
pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
|
||||
pd.mFullName = null;
|
||||
pd.mFirstName = null;
|
||||
pd.mProfilePhotoUri = null;
|
||||
pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
|
||||
pd.mLookupKey = null;
|
||||
pd.mBlocked = false;
|
||||
pd.mSubscriptionColor = Color.TRANSPARENT;
|
||||
pd.mSubscriptionName = null;
|
||||
return pd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance from a raw phone number and using system locale to normalize it.
|
||||
*
|
||||
* Use this when creating a participant that is for displaying UI and not associated
|
||||
* with a specific SIM. For example, when creating a conversation using user entered
|
||||
* phone number.
|
||||
*
|
||||
* @param phoneNumber The raw phone number
|
||||
* @return instance
|
||||
*/
|
||||
public static ParticipantData getFromRawPhoneBySystemLocale(final String phoneNumber) {
|
||||
final ParticipantData pd = getFromRawPhone(phoneNumber);
|
||||
pd.mNormalizedDestination = pd.mIsEmailAddress ?
|
||||
pd.mSendDestination :
|
||||
PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
|
||||
pd.mDisplayDestination = pd.mIsEmailAddress ?
|
||||
pd.mNormalizedDestination :
|
||||
PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
|
||||
pd.maybeSetupUnknownSender();
|
||||
return pd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance from a raw phone number and using SIM or system locale to normalize it.
|
||||
*
|
||||
* Use this when creating a participant that is associated with a specific SIM. For example,
|
||||
* the sender of a received message or the recipient of a sending message that is already
|
||||
* targeted at a specific SIM.
|
||||
*
|
||||
* @param phoneNumber The raw phone number
|
||||
* @return instance
|
||||
*/
|
||||
public static ParticipantData getFromRawPhoneBySimLocale(
|
||||
final String phoneNumber, final int subId) {
|
||||
final ParticipantData pd = getFromRawPhone(phoneNumber);
|
||||
pd.mNormalizedDestination = pd.mIsEmailAddress ?
|
||||
pd.mSendDestination :
|
||||
PhoneUtils.get(subId).getCanonicalBySimLocale(pd.mSendDestination);
|
||||
pd.mDisplayDestination = pd.mIsEmailAddress ?
|
||||
pd.mNormalizedDestination :
|
||||
PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
|
||||
pd.maybeSetupUnknownSender();
|
||||
return pd;
|
||||
}
|
||||
|
||||
public static ParticipantData getSelfParticipant(final int subId) {
|
||||
Assert.isTrue(subId != OTHER_THAN_SELF_SUB_ID);
|
||||
final ParticipantData pd = new ParticipantData();
|
||||
pd.mParticipantId = null;
|
||||
pd.mSubId = subId;
|
||||
pd.mSlotId = INVALID_SLOT_ID;
|
||||
pd.mIsEmailAddress = false;
|
||||
pd.mSendDestination = null;
|
||||
pd.mNormalizedDestination = null;
|
||||
pd.mDisplayDestination = null;
|
||||
pd.mFullName = null;
|
||||
pd.mFirstName = null;
|
||||
pd.mProfilePhotoUri = null;
|
||||
pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
|
||||
pd.mLookupKey = null;
|
||||
pd.mBlocked = false;
|
||||
pd.mSubscriptionColor = Color.TRANSPARENT;
|
||||
pd.mSubscriptionName = null;
|
||||
return pd;
|
||||
}
|
||||
|
||||
private void maybeSetupUnknownSender() {
|
||||
if (isUnknownSender()) {
|
||||
// Because your locale may change, we setup the display string for the unknown sender
|
||||
// on the fly rather than relying on the version in the database.
|
||||
final Resources resources = Factory.get().getApplicationContext().getResources();
|
||||
mDisplayDestination = resources.getString(R.string.unknown_sender);
|
||||
mFullName = mDisplayDestination;
|
||||
}
|
||||
}
|
||||
|
||||
public String getNormalizedDestination() {
|
||||
return mNormalizedDestination;
|
||||
}
|
||||
|
||||
public String getSendDestination() {
|
||||
return mSendDestination;
|
||||
}
|
||||
|
||||
public String getDisplayDestination() {
|
||||
return mDisplayDestination;
|
||||
}
|
||||
|
||||
public String getContactDestination() {
|
||||
return mContactDestination;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return mFullName;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return mFirstName;
|
||||
}
|
||||
|
||||
public String getDisplayName(final boolean preferFullName) {
|
||||
if (preferFullName) {
|
||||
// Prefer full name over first name
|
||||
if (!TextUtils.isEmpty(mFullName)) {
|
||||
return mFullName;
|
||||
}
|
||||
if (!TextUtils.isEmpty(mFirstName)) {
|
||||
return mFirstName;
|
||||
}
|
||||
} else {
|
||||
// Prefer first name over full name
|
||||
if (!TextUtils.isEmpty(mFirstName)) {
|
||||
return mFirstName;
|
||||
}
|
||||
if (!TextUtils.isEmpty(mFullName)) {
|
||||
return mFullName;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to the display destination
|
||||
if (!TextUtils.isEmpty(mDisplayDestination)) {
|
||||
return mDisplayDestination;
|
||||
}
|
||||
|
||||
return Factory.get().getApplicationContext().getResources().getString(
|
||||
R.string.unknown_sender);
|
||||
}
|
||||
|
||||
public String getProfilePhotoUri() {
|
||||
return mProfilePhotoUri;
|
||||
}
|
||||
|
||||
public long getContactId() {
|
||||
return mContactId;
|
||||
}
|
||||
|
||||
public String getLookupKey() {
|
||||
return mLookupKey;
|
||||
}
|
||||
|
||||
public boolean updatePhoneNumberForSelfIfChanged() {
|
||||
final String phoneNumber =
|
||||
PhoneUtils.get(mSubId).getCanonicalForSelf(true/*allowOverride*/);
|
||||
boolean changed = false;
|
||||
if (isSelf() && !TextUtils.equals(phoneNumber, mNormalizedDestination)) {
|
||||
mNormalizedDestination = phoneNumber;
|
||||
mSendDestination = phoneNumber;
|
||||
mDisplayDestination = mIsEmailAddress ?
|
||||
phoneNumber :
|
||||
PhoneUtils.getDefault().formatForDisplay(phoneNumber);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo) {
|
||||
boolean changed = false;
|
||||
if (isSelf()) {
|
||||
if (subscriptionInfo == null) {
|
||||
// The subscription is inactive. Check if the participant is still active.
|
||||
if (isActiveSubscription()) {
|
||||
mSlotId = INVALID_SLOT_ID;
|
||||
mSubscriptionColor = Color.TRANSPARENT;
|
||||
mSubscriptionName = "";
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
final int slotId = subscriptionInfo.getSimSlotIndex();
|
||||
final int color = subscriptionInfo.getIconTint();
|
||||
final CharSequence name = subscriptionInfo.getDisplayName();
|
||||
if (mSlotId != slotId || mSubscriptionColor != color || mSubscriptionName != name) {
|
||||
mSlotId = slotId;
|
||||
mSubscriptionColor = color;
|
||||
mSubscriptionName = name.toString();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void setFullName(final String fullName) {
|
||||
mFullName = fullName;
|
||||
}
|
||||
|
||||
public void setFirstName(final String firstName) {
|
||||
mFirstName = firstName;
|
||||
}
|
||||
|
||||
public void setProfilePhotoUri(final String profilePhotoUri) {
|
||||
mProfilePhotoUri = profilePhotoUri;
|
||||
}
|
||||
|
||||
public void setContactId(final long contactId) {
|
||||
mContactId = contactId;
|
||||
}
|
||||
|
||||
public void setLookupKey(final String lookupKey) {
|
||||
mLookupKey = lookupKey;
|
||||
}
|
||||
|
||||
public void setSendDestination(final String destination) {
|
||||
mSendDestination = destination;
|
||||
}
|
||||
|
||||
public void setContactDestination(final String destination) {
|
||||
mContactDestination = destination;
|
||||
}
|
||||
|
||||
public int getSubId() {
|
||||
return mSubId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this sub is active. Note that {@link ParticipantData#DEFAULT_SELF_SUB_ID} is
|
||||
* is considered as active if there is any active SIM.
|
||||
*/
|
||||
public boolean isActiveSubscription() {
|
||||
return mSlotId != INVALID_SLOT_ID;
|
||||
}
|
||||
|
||||
public boolean isDefaultSelf() {
|
||||
return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID;
|
||||
}
|
||||
|
||||
public int getSlotId() {
|
||||
return mSlotId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slot IDs in the subscription manager is zero-based, but we want to show it
|
||||
* as 1-based in UI.
|
||||
*/
|
||||
public int getDisplaySlotId() {
|
||||
return getSlotId() + 1;
|
||||
}
|
||||
|
||||
public int getSubscriptionColor() {
|
||||
Assert.isTrue(isActiveSubscription());
|
||||
// Force the alpha channel to 0xff to ensure the returned color is solid.
|
||||
return mSubscriptionColor | 0xff000000;
|
||||
}
|
||||
|
||||
public String getSubscriptionName() {
|
||||
Assert.isTrue(isActiveSubscription());
|
||||
return mSubscriptionName;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mParticipantId;
|
||||
}
|
||||
|
||||
public boolean isSelf() {
|
||||
return (mSubId != OTHER_THAN_SELF_SUB_ID);
|
||||
}
|
||||
|
||||
public boolean isEmail() {
|
||||
return mIsEmailAddress;
|
||||
}
|
||||
|
||||
public boolean isContactIdResolved() {
|
||||
return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED);
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return mBlocked;
|
||||
}
|
||||
|
||||
public boolean isUnknownSender() {
|
||||
final String unknownSender = ParticipantData.getUnknownSenderDestination();
|
||||
return (TextUtils.equals(mSendDestination, unknownSender));
|
||||
}
|
||||
|
||||
public ContentValues toContentValues() {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ParticipantColumns.SUB_ID, mSubId);
|
||||
values.put(ParticipantColumns.SIM_SLOT_ID, mSlotId);
|
||||
values.put(DatabaseHelper.ParticipantColumns.SEND_DESTINATION, mSendDestination);
|
||||
|
||||
if (!isUnknownSender()) {
|
||||
values.put(DatabaseHelper.ParticipantColumns.DISPLAY_DESTINATION, mDisplayDestination);
|
||||
values.put(DatabaseHelper.ParticipantColumns.NORMALIZED_DESTINATION,
|
||||
mNormalizedDestination);
|
||||
values.put(ParticipantColumns.FULL_NAME, mFullName);
|
||||
values.put(ParticipantColumns.FIRST_NAME, mFirstName);
|
||||
}
|
||||
|
||||
values.put(ParticipantColumns.PROFILE_PHOTO_URI, mProfilePhotoUri);
|
||||
values.put(ParticipantColumns.CONTACT_ID, mContactId);
|
||||
values.put(ParticipantColumns.LOOKUP_KEY, mLookupKey);
|
||||
values.put(ParticipantColumns.BLOCKED, mBlocked);
|
||||
values.put(ParticipantColumns.SUBSCRIPTION_COLOR, mSubscriptionColor);
|
||||
values.put(ParticipantColumns.SUBSCRIPTION_NAME, mSubscriptionName);
|
||||
return values;
|
||||
}
|
||||
|
||||
public ParticipantData(final Parcel in) {
|
||||
mParticipantId = in.readString();
|
||||
mSubId = in.readInt();
|
||||
mSlotId = in.readInt();
|
||||
mNormalizedDestination = in.readString();
|
||||
mSendDestination = in.readString();
|
||||
mDisplayDestination = in.readString();
|
||||
mFullName = in.readString();
|
||||
mFirstName = in.readString();
|
||||
mProfilePhotoUri = in.readString();
|
||||
mContactId = in.readLong();
|
||||
mLookupKey = in.readString();
|
||||
mIsEmailAddress = in.readInt() != 0;
|
||||
mBlocked = in.readInt() != 0;
|
||||
mSubscriptionColor = in.readInt();
|
||||
mSubscriptionName = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel dest, final int flags) {
|
||||
dest.writeString(mParticipantId);
|
||||
dest.writeInt(mSubId);
|
||||
dest.writeInt(mSlotId);
|
||||
dest.writeString(mNormalizedDestination);
|
||||
dest.writeString(mSendDestination);
|
||||
dest.writeString(mDisplayDestination);
|
||||
dest.writeString(mFullName);
|
||||
dest.writeString(mFirstName);
|
||||
dest.writeString(mProfilePhotoUri);
|
||||
dest.writeLong(mContactId);
|
||||
dest.writeString(mLookupKey);
|
||||
dest.writeInt(mIsEmailAddress ? 1 : 0);
|
||||
dest.writeInt(mBlocked ? 1 : 0);
|
||||
dest.writeInt(mSubscriptionColor);
|
||||
dest.writeString(mSubscriptionName);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ParticipantData> CREATOR
|
||||
= new Parcelable.Creator<ParticipantData>() {
|
||||
@Override
|
||||
public ParticipantData createFromParcel(final Parcel in) {
|
||||
return new ParticipantData(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParticipantData[] newArray(final int size) {
|
||||
return new ParticipantData[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.datamodel.action.BugleActionToasts;
|
||||
import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
|
||||
/**
|
||||
* Helps visualize a ParticipantData in a PersonItemView
|
||||
*/
|
||||
public class ParticipantListItemData extends PersonItemData {
|
||||
private final Uri mAvatarUri;
|
||||
private final String mDisplayName;
|
||||
private final String mDetails;
|
||||
private final long mContactId;
|
||||
private final String mLookupKey;
|
||||
private final String mNormalizedDestination;
|
||||
|
||||
/**
|
||||
* Constructor. Takes necessary info from the incoming ParticipantData.
|
||||
*/
|
||||
public ParticipantListItemData(final ParticipantData participant) {
|
||||
mAvatarUri = AvatarUriUtil.createAvatarUri(participant);
|
||||
mContactId = participant.getContactId();
|
||||
mLookupKey = participant.getLookupKey();
|
||||
mNormalizedDestination = participant.getNormalizedDestination();
|
||||
if (TextUtils.isEmpty(participant.getFullName())) {
|
||||
mDisplayName = participant.getSendDestination();
|
||||
mDetails = null;
|
||||
} else {
|
||||
mDisplayName = participant.getFullName();
|
||||
mDetails = (participant.isUnknownSender()) ? null : participant.getSendDestination();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getAvatarUri() {
|
||||
return mAvatarUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetails() {
|
||||
return mDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getClickIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContactId() {
|
||||
return mContactId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLookupKey() {
|
||||
return mLookupKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNormalizedDestination() {
|
||||
return mNormalizedDestination;
|
||||
}
|
||||
|
||||
public void unblock(final Context context) {
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
mNormalizedDestination, false, null,
|
||||
BugleActionToasts.makeUpdateDestinationBlockedActionListener(context));
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ContentType;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.SafeAsyncTask;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
|
||||
/**
|
||||
* Represents a "pending" message part that acts as a placeholder for the actual attachment being
|
||||
* loaded. It handles the task to load and persist the attachment from a Uri to local scratch
|
||||
* folder. This item is not persisted to the database.
|
||||
*/
|
||||
public class PendingAttachmentData extends MessagePartData {
|
||||
/** The pending state. This is the initial state where we haven't started loading yet */
|
||||
public static final int STATE_PENDING = 0;
|
||||
|
||||
/** The state for when we are currently loading the attachment to the scratch space */
|
||||
public static final int STATE_LOADING = 1;
|
||||
|
||||
/** The attachment has been successfully loaded and no longer pending */
|
||||
public static final int STATE_LOADED = 2;
|
||||
|
||||
/** The attachment failed to load */
|
||||
public static final int STATE_FAILED = 3;
|
||||
|
||||
private static final int LOAD_MEDIA_TIME_LIMIT_MILLIS = 60 * 1000; // 60s
|
||||
|
||||
/** The current state of the pending attachment. Refer to the STATE_* states above */
|
||||
private int mCurrentState;
|
||||
|
||||
/**
|
||||
* Create a new instance of PendingAttachmentData with an output Uri.
|
||||
* @param sourceUri the source Uri of the attachment. The Uri maybe temporary or remote,
|
||||
* so we need to persist it to local storage.
|
||||
*/
|
||||
protected PendingAttachmentData(final String caption, final String contentType,
|
||||
@NonNull final Uri sourceUri, final int width, final int height,
|
||||
final boolean onlySingleAttachment) {
|
||||
super(caption, contentType, sourceUri, width, height, onlySingleAttachment);
|
||||
mCurrentState = STATE_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pending attachment data that is able to load from the given source uri and
|
||||
* persist the media resource locally in the scratch folder.
|
||||
*/
|
||||
public static PendingAttachmentData createPendingAttachmentData(final String contentType,
|
||||
final Uri sourceUri) {
|
||||
return createPendingAttachmentData(null, contentType, sourceUri, UNSPECIFIED_SIZE,
|
||||
UNSPECIFIED_SIZE);
|
||||
}
|
||||
|
||||
public static PendingAttachmentData createPendingAttachmentData(final String caption,
|
||||
final String contentType, final Uri sourceUri, final int width, final int height) {
|
||||
Assert.isTrue(ContentType.isMediaType(contentType));
|
||||
return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
|
||||
false /*onlySingleAttachment*/);
|
||||
}
|
||||
|
||||
public static PendingAttachmentData createPendingAttachmentData(final String caption,
|
||||
final String contentType, final Uri sourceUri, final int width, final int height,
|
||||
final boolean onlySingleAttachment) {
|
||||
Assert.isTrue(ContentType.isMediaType(contentType));
|
||||
return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
|
||||
onlySingleAttachment);
|
||||
}
|
||||
|
||||
public int getCurrentState() {
|
||||
return mCurrentState;
|
||||
}
|
||||
|
||||
public void loadAttachmentForDraft(final DraftMessageData draftMessageData,
|
||||
final String bindingId) {
|
||||
if (mCurrentState != STATE_PENDING) {
|
||||
return;
|
||||
}
|
||||
mCurrentState = STATE_LOADING;
|
||||
|
||||
// Kick off a SafeAsyncTask to load the content of the media and persist it locally.
|
||||
// Note: we need to persist the media locally even if it's not remote, because we
|
||||
// want to be able to resend the media in case the message failed to send.
|
||||
new SafeAsyncTask<Void, Void, MessagePartData>(LOAD_MEDIA_TIME_LIMIT_MILLIS,
|
||||
true /* cancelExecutionOnTimeout */) {
|
||||
@Override
|
||||
protected MessagePartData doInBackgroundTimed(final Void... params) {
|
||||
final Uri contentUri = getContentUri();
|
||||
final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri);
|
||||
if (persistedUri != null) {
|
||||
return MessagePartData.createMediaMessagePart(
|
||||
getText(),
|
||||
getContentType(),
|
||||
persistedUri,
|
||||
getWidth(),
|
||||
getHeight());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media");
|
||||
mCurrentState = STATE_FAILED;
|
||||
if (draftMessageData.isBound(bindingId)) {
|
||||
draftMessageData.removePendingAttachment(PendingAttachmentData.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final MessagePartData attachment) {
|
||||
if (attachment != null) {
|
||||
mCurrentState = STATE_LOADED;
|
||||
if (draftMessageData.isBound(bindingId)) {
|
||||
draftMessageData.updatePendingAttachment(attachment,
|
||||
PendingAttachmentData.this);
|
||||
} else {
|
||||
// The draft message data is no longer bound, drop the loaded attachment.
|
||||
attachment.destroyAsync();
|
||||
}
|
||||
} else {
|
||||
// Media load failed. We already logged in doInBackground() so don't need to
|
||||
// do that again.
|
||||
mCurrentState = STATE_FAILED;
|
||||
if (draftMessageData.isBound(bindingId)) {
|
||||
draftMessageData.onPendingAttachmentLoadFailed(PendingAttachmentData.this);
|
||||
draftMessageData.removePendingAttachment(PendingAttachmentData.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}.executeOnThreadPool();
|
||||
}
|
||||
|
||||
protected PendingAttachmentData(final Parcel in) {
|
||||
super(in);
|
||||
mCurrentState = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(final Parcel out, final int flags) {
|
||||
super.writeToParcel(out, flags);
|
||||
out.writeInt(mCurrentState);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<PendingAttachmentData> CREATOR
|
||||
= new Parcelable.Creator<PendingAttachmentData>() {
|
||||
@Override
|
||||
public PendingAttachmentData createFromParcel(final Parcel in) {
|
||||
return new PendingAttachmentData(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingAttachmentData[] newArray(final int size) {
|
||||
return new PendingAttachmentData[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.action.BugleActionToasts;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
|
||||
import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Services data needs for PeopleAndOptionsFragment.
|
||||
*/
|
||||
public class PeopleAndOptionsData extends BindableData implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
public interface PeopleAndOptionsDataListener {
|
||||
void onOptionsCursorUpdated(PeopleAndOptionsData data, Cursor cursor);
|
||||
void onParticipantsListLoaded(PeopleAndOptionsData data,
|
||||
List<ParticipantData> participants);
|
||||
}
|
||||
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
private final Context mContext;
|
||||
private final String mConversationId;
|
||||
private final ConversationParticipantsData mParticipantData;
|
||||
private LoaderManager mLoaderManager;
|
||||
private PeopleAndOptionsDataListener mListener;
|
||||
|
||||
public PeopleAndOptionsData(final String conversationId, final Context context,
|
||||
final PeopleAndOptionsDataListener listener) {
|
||||
mListener = listener;
|
||||
mContext = context;
|
||||
mConversationId = conversationId;
|
||||
mParticipantData = new ConversationParticipantsData();
|
||||
}
|
||||
|
||||
private static final int CONVERSATION_OPTIONS_LOADER = 1;
|
||||
private static final int PARTICIPANT_LOADER = 2;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
switch (id) {
|
||||
case CONVERSATION_OPTIONS_LOADER: {
|
||||
final Uri uri =
|
||||
MessagingContentProvider.buildConversationMetadataUri(mConversationId);
|
||||
return new BoundCursorLoader(bindingId, mContext, uri,
|
||||
PeopleOptionsItemData.PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
case PARTICIPANT_LOADER: {
|
||||
final Uri uri =
|
||||
MessagingContentProvider
|
||||
.buildConversationParticipantsUri(mConversationId);
|
||||
return new BoundCursorLoader(bindingId, mContext, uri,
|
||||
ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
default:
|
||||
Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding PeopleAndOptionsFragment");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
if (isBound(cursorLoader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case CONVERSATION_OPTIONS_LOADER:
|
||||
mListener.onOptionsCursorUpdated(this, data);
|
||||
break;
|
||||
|
||||
case PARTICIPANT_LOADER:
|
||||
mParticipantData.bind(data);
|
||||
mListener.onParticipantsListLoaded(this,
|
||||
mParticipantData.getParticipantListExcludingSelf());
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG,
|
||||
"Loader finished after unbinding PeopleAndOptionsFragment");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> loader) {
|
||||
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
|
||||
if (isBound(cursorLoader.getBindingId())) {
|
||||
switch (loader.getId()) {
|
||||
case CONVERSATION_OPTIONS_LOADER:
|
||||
mListener.onOptionsCursorUpdated(this, null);
|
||||
break;
|
||||
|
||||
case PARTICIPANT_LOADER:
|
||||
mParticipantData.bind(null);
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding PeopleAndOptionsFragment");
|
||||
}
|
||||
}
|
||||
|
||||
public void init(final LoaderManager loaderManager,
|
||||
final BindingBase<PeopleAndOptionsData> binding) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(BINDING_ID, binding.getBindingId());
|
||||
mLoaderManager = loaderManager;
|
||||
mLoaderManager.initLoader(CONVERSATION_OPTIONS_LOADER, args, this);
|
||||
mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
|
||||
// This could be null if we bind but the caller doesn't init the BindableData
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(CONVERSATION_OPTIONS_LOADER);
|
||||
mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void enableConversationNotifications(final BindingBase<PeopleAndOptionsData> binding,
|
||||
final boolean enable) {
|
||||
final String bindingId = binding.getBindingId();
|
||||
if (isBound(bindingId)) {
|
||||
UpdateConversationOptionsAction.enableConversationNotifications(
|
||||
mConversationId, enable);
|
||||
}
|
||||
}
|
||||
|
||||
public void setConversationNotificationSound(final BindingBase<PeopleAndOptionsData> binding,
|
||||
final String ringtoneUri) {
|
||||
final String bindingId = binding.getBindingId();
|
||||
if (isBound(bindingId)) {
|
||||
UpdateConversationOptionsAction.setConversationNotificationSound(mConversationId,
|
||||
ringtoneUri);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableConversationNotificationVibration(
|
||||
final BindingBase<PeopleAndOptionsData> binding, final boolean enable) {
|
||||
final String bindingId = binding.getBindingId();
|
||||
if (isBound(bindingId)) {
|
||||
UpdateConversationOptionsAction.enableVibrationForConversationNotification(
|
||||
mConversationId, enable);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDestinationBlocked(final BindingBase<PeopleAndOptionsData> binding,
|
||||
final boolean blocked) {
|
||||
final String bindingId = binding.getBindingId();
|
||||
final ParticipantData participantData = mParticipantData.getOtherParticipant();
|
||||
if (isBound(bindingId) && participantData != null) {
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
participantData.getNormalizedDestination(),
|
||||
blocked, mConversationId,
|
||||
BugleActionToasts.makeUpdateDestinationBlockedActionListener(mContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.RingtoneUtil;
|
||||
|
||||
public class PeopleOptionsItemData {
|
||||
public static final String[] PROJECTION = {
|
||||
ConversationListViewColumns.NOTIFICATION_ENABLED,
|
||||
ConversationListViewColumns.NOTIFICATION_SOUND_URI,
|
||||
ConversationListViewColumns.NOTIFICATION_VIBRATION,
|
||||
};
|
||||
|
||||
// Column index for query projection.
|
||||
private static final int INDEX_NOTIFICATION_ENABLED = 0;
|
||||
private static final int INDEX_NOTIFICATION_SOUND_URI = 1;
|
||||
private static final int INDEX_NOTIFICATION_VIBRATION = 2;
|
||||
|
||||
// Identification for each setting that's surfaced to the UI layer.
|
||||
public static final int SETTING_NOTIFICATION_ENABLED = 0;
|
||||
public static final int SETTING_NOTIFICATION_SOUND_URI = 1;
|
||||
public static final int SETTING_NOTIFICATION_VIBRATION = 2;
|
||||
public static final int SETTING_BLOCKED = 3;
|
||||
public static final int SETTINGS_COUNT = 4;
|
||||
|
||||
// Type of UI switch to show for the toggle button.
|
||||
public static final int TOGGLE_TYPE_CHECKBOX = 0;
|
||||
public static final int TOGGLE_TYPE_SWITCH = 1;
|
||||
|
||||
private String mTitle;
|
||||
private String mSubtitle;
|
||||
private Uri mRingtoneUri;
|
||||
private boolean mCheckable;
|
||||
private boolean mChecked;
|
||||
private boolean mEnabled;
|
||||
private int mItemId;
|
||||
private ParticipantData mOtherParticipant;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public PeopleOptionsItemData(final Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to a specific setting column on conversation metadata cursor. (Note
|
||||
* that it binds to columns because it treats individual columns of the cursor as
|
||||
* separate options to display for the conversation, e.g. notification settings).
|
||||
*/
|
||||
public void bind(
|
||||
final Cursor cursor, final ParticipantData otherParticipant, final int settingType) {
|
||||
mSubtitle = null;
|
||||
mRingtoneUri = null;
|
||||
mCheckable = true;
|
||||
mEnabled = true;
|
||||
mItemId = settingType;
|
||||
mOtherParticipant = otherParticipant;
|
||||
|
||||
final boolean notificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
|
||||
switch (settingType) {
|
||||
case SETTING_NOTIFICATION_ENABLED:
|
||||
mTitle = mContext.getString(R.string.notifications_enabled_conversation_pref_title);
|
||||
mChecked = notificationEnabled;
|
||||
break;
|
||||
|
||||
case SETTING_NOTIFICATION_SOUND_URI:
|
||||
mTitle = mContext.getString(R.string.notification_sound_pref_title);
|
||||
final String ringtoneString = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
|
||||
Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(ringtoneString);
|
||||
|
||||
mSubtitle = mContext.getString(R.string.silent_ringtone);
|
||||
if (ringtoneUri != null) {
|
||||
final Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
|
||||
if (ringtone != null) {
|
||||
mSubtitle = ringtone.getTitle(mContext);
|
||||
}
|
||||
}
|
||||
mCheckable = false;
|
||||
mRingtoneUri = ringtoneUri;
|
||||
mEnabled = notificationEnabled;
|
||||
break;
|
||||
|
||||
case SETTING_NOTIFICATION_VIBRATION:
|
||||
mTitle = mContext.getString(R.string.notification_vibrate_pref_title);
|
||||
mChecked = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
|
||||
mEnabled = notificationEnabled;
|
||||
break;
|
||||
|
||||
case SETTING_BLOCKED:
|
||||
Assert.notNull(otherParticipant);
|
||||
final int resourceId = otherParticipant.isBlocked() ?
|
||||
R.string.unblock_contact_title : R.string.block_contact_title;
|
||||
mTitle = mContext.getString(resourceId, otherParticipant.getDisplayDestination());
|
||||
mCheckable = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.fail("Unsupported conversation option type!");
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return mSubtitle;
|
||||
}
|
||||
|
||||
public boolean getCheckable() {
|
||||
return mCheckable;
|
||||
}
|
||||
|
||||
public boolean getChecked() {
|
||||
return mChecked;
|
||||
}
|
||||
|
||||
public boolean getEnabled() {
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return mItemId;
|
||||
}
|
||||
|
||||
public Uri getRingtoneUri() {
|
||||
return mRingtoneUri;
|
||||
}
|
||||
|
||||
public ParticipantData getOtherParticipant() {
|
||||
return mOtherParticipant;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
|
||||
/**
|
||||
* Bridges between any particpant/contact related data and data displayed in the PersonItemView.
|
||||
*/
|
||||
public abstract class PersonItemData extends BindableData {
|
||||
/**
|
||||
* The UI component that listens for data change and update accordingly.
|
||||
*/
|
||||
public interface PersonItemDataListener {
|
||||
void onPersonDataUpdated(PersonItemData data);
|
||||
void onPersonDataFailed(PersonItemData data, Exception exception);
|
||||
}
|
||||
|
||||
private PersonItemDataListener mListener;
|
||||
|
||||
public abstract Uri getAvatarUri();
|
||||
public abstract String getDisplayName();
|
||||
public abstract String getDetails();
|
||||
public abstract Intent getClickIntent();
|
||||
public abstract long getContactId();
|
||||
public abstract String getLookupKey();
|
||||
public abstract String getNormalizedDestination();
|
||||
|
||||
public void setListener(final PersonItemDataListener listener) {
|
||||
if (isBound()) {
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyDataUpdated() {
|
||||
if (isBound() && mListener != null) {
|
||||
mListener.onPersonDataUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyDataFailed(final Exception exception) {
|
||||
if (isBound() && mListener != null) {
|
||||
mListener.onPersonDataFailed(this, exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.util.ArrayMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.messaging.util.OsUtil;
|
||||
|
||||
/**
|
||||
* A class that contains the list of all self participants potentially involved in a conversation.
|
||||
* This class contains both active/inactive self entries when there is multi-SIM support.
|
||||
*/
|
||||
public class SelfParticipantsData {
|
||||
/**
|
||||
* The map from self participant ids to self-participant data entries in the participants table.
|
||||
* This includes both active, inactive and default (with subId ==
|
||||
* {@link ParticipantData#DEFAULT_SELF_SUB_ID}) subscriptions.
|
||||
*/
|
||||
private final ArrayMap<String, ParticipantData> mSelfParticipantMap;
|
||||
|
||||
public SelfParticipantsData() {
|
||||
mSelfParticipantMap = new ArrayMap<String, ParticipantData>();
|
||||
}
|
||||
|
||||
public void bind(final Cursor cursor) {
|
||||
mSelfParticipantMap.clear();
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor);
|
||||
mSelfParticipantMap.put(newParticipant.getId(), newParticipant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of self participants for all subscriptions.
|
||||
* @param activeOnly if set, returns active self entries only (i.e. those with SIMs plugged in).
|
||||
*/
|
||||
public List<ParticipantData> getSelfParticipants(final boolean activeOnly) {
|
||||
List<ParticipantData> list = new ArrayList<ParticipantData>();
|
||||
for (final ParticipantData self : mSelfParticipantMap.values()) {
|
||||
if (!activeOnly || self.isActiveSubscription()) {
|
||||
list.add(self);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the self participant corresponding to the given self id.
|
||||
*/
|
||||
ParticipantData getSelfParticipantById(final String selfId) {
|
||||
return mSelfParticipantMap.get(selfId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a given self id represents the default self.
|
||||
*/
|
||||
boolean isDefaultSelf(final String selfId) {
|
||||
if (!OsUtil.isAtLeastL_MR1()) {
|
||||
return true;
|
||||
}
|
||||
final ParticipantData self = getSelfParticipantById(selfId);
|
||||
return self == null ? false : self.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID;
|
||||
}
|
||||
|
||||
public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) {
|
||||
int count = 0;
|
||||
for (final ParticipantData self : mSelfParticipantMap.values()) {
|
||||
if (!self.isDefaultSelf() && (!activeOnly || self.isActiveSubscription())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public ParticipantData getDefaultSelfParticipant() {
|
||||
for (final ParticipantData self : mSelfParticipantMap.values()) {
|
||||
if (self.isDefaultSelf()) {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isLoaded() {
|
||||
return !mSelfParticipantMap.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.BoundCursorLoader;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Services SettingsFragment's data needs for loading active self participants to display
|
||||
* the list of active subscriptions.
|
||||
*/
|
||||
public class SettingsData extends BindableData implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
public interface SettingsDataListener {
|
||||
void onSelfParticipantDataLoaded(SettingsData data);
|
||||
}
|
||||
|
||||
public static class SettingsItem {
|
||||
public static final int TYPE_GENERAL_SETTINGS = 1;
|
||||
public static final int TYPE_PER_SUBSCRIPTION_SETTINGS = 2;
|
||||
|
||||
private final String mDisplayName;
|
||||
private final String mDisplayDetail;
|
||||
private final String mActivityTitle;
|
||||
private final int mType;
|
||||
private final int mSubId;
|
||||
|
||||
private SettingsItem(final String displayName, final String displayDetail,
|
||||
final String activityTitle, final int type, final int subId) {
|
||||
mDisplayName = displayName;
|
||||
mDisplayDetail = displayDetail;
|
||||
mActivityTitle = activityTitle;
|
||||
mType = type;
|
||||
mSubId = subId;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
public String getDisplayDetail() {
|
||||
return mDisplayDetail;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public int getSubId() {
|
||||
return mSubId;
|
||||
}
|
||||
|
||||
public String getActivityTitle() {
|
||||
return mActivityTitle;
|
||||
}
|
||||
|
||||
public static SettingsItem fromSelfParticipant(final Context context,
|
||||
final ParticipantData self) {
|
||||
Assert.isTrue(self.isSelf());
|
||||
Assert.isTrue(self.isActiveSubscription());
|
||||
final String displayDetail = TextUtils.isEmpty(self.getDisplayDestination()) ?
|
||||
context.getString(R.string.sim_settings_unknown_number) :
|
||||
self.getDisplayDestination();
|
||||
final String displayName = context.getString(R.string.sim_specific_settings,
|
||||
self.getSubscriptionName());
|
||||
return new SettingsItem(displayName, displayDetail, displayName,
|
||||
TYPE_PER_SUBSCRIPTION_SETTINGS, self.getSubId());
|
||||
}
|
||||
|
||||
public static SettingsItem createGeneralSettingsItem(final Context context) {
|
||||
return new SettingsItem(context.getString(R.string.general_settings),
|
||||
null, context.getString(R.string.general_settings_activity_title),
|
||||
TYPE_GENERAL_SETTINGS, -1);
|
||||
}
|
||||
|
||||
public static SettingsItem createDefaultMmsSettingsItem(final Context context,
|
||||
final int subId) {
|
||||
return new SettingsItem(context.getString(R.string.advanced_settings),
|
||||
null, context.getString(R.string.advanced_settings_activity_title),
|
||||
TYPE_PER_SUBSCRIPTION_SETTINGS, subId);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String BINDING_ID = "bindingId";
|
||||
private final Context mContext;
|
||||
private final SelfParticipantsData mSelfParticipantsData;
|
||||
private LoaderManager mLoaderManager;
|
||||
private SettingsDataListener mListener;
|
||||
|
||||
public SettingsData(final Context context,
|
||||
final SettingsDataListener listener) {
|
||||
mListener = listener;
|
||||
mContext = context;
|
||||
mSelfParticipantsData = new SelfParticipantsData();
|
||||
}
|
||||
|
||||
private static final int SELF_PARTICIPANT_LOADER = 1;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
|
||||
Assert.equals(SELF_PARTICIPANT_LOADER, id);
|
||||
Loader<Cursor> loader = null;
|
||||
|
||||
final String bindingId = args.getString(BINDING_ID);
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(bindingId)) {
|
||||
loader = new BoundCursorLoader(bindingId, mContext,
|
||||
MessagingContentProvider.PARTICIPANTS_URI,
|
||||
ParticipantData.ParticipantsQuery.PROJECTION,
|
||||
ParticipantColumns.SUB_ID + " <> ?",
|
||||
new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
|
||||
null);
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Creating self loader after unbinding");
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mSelfParticipantsData.bind(data);
|
||||
mListener.onSelfParticipantDataLoaded(this);
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Self loader finished after unbinding");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(final Loader<Cursor> generic) {
|
||||
final BoundCursorLoader loader = (BoundCursorLoader) generic;
|
||||
|
||||
// Check if data still bound to the requesting ui element
|
||||
if (isBound(loader.getBindingId())) {
|
||||
mSelfParticipantsData.bind(null);
|
||||
} else {
|
||||
LogUtil.w(LogUtil.BUGLE_TAG, "Self loader reset after unbinding");
|
||||
}
|
||||
}
|
||||
|
||||
public void init(final LoaderManager loaderManager,
|
||||
final BindingBase<SettingsData> binding) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(BINDING_ID, binding.getBindingId());
|
||||
mLoaderManager = loaderManager;
|
||||
mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterListeners() {
|
||||
mListener = null;
|
||||
|
||||
// This could be null if we bind but the caller doesn't init the BindableData
|
||||
if (mLoaderManager != null) {
|
||||
mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER);
|
||||
mLoaderManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<SettingsItem> getSettingsItems() {
|
||||
final List<ParticipantData> selfs = mSelfParticipantsData.getSelfParticipants(true);
|
||||
final List<SettingsItem> settingsItems = new ArrayList<SettingsItem>();
|
||||
// First goes the general settings, followed by per-subscription settings.
|
||||
settingsItems.add(SettingsItem.createGeneralSettingsItem(mContext));
|
||||
// For per-subscription settings, show the actual SIM name with phone number if the
|
||||
// platorm is at least L-MR1 and there are multiple active SIMs.
|
||||
final int activeSubCountExcludingDefault =
|
||||
mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(true);
|
||||
if (OsUtil.isAtLeastL_MR1() && activeSubCountExcludingDefault > 0) {
|
||||
for (ParticipantData self : selfs) {
|
||||
if (!self.isDefaultSelf()) {
|
||||
if (activeSubCountExcludingDefault > 1) {
|
||||
settingsItems.add(SettingsItem.fromSelfParticipant(mContext, self));
|
||||
} else {
|
||||
// This is the only active non-default SIM.
|
||||
settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext,
|
||||
self.getSubId()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Either pre-L-MR1, or there's no active SIM, so show the default MMS settings.
|
||||
settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext,
|
||||
ParticipantData.DEFAULT_SELF_SUB_ID));
|
||||
}
|
||||
return settingsItems;
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This is a UI facing data model component that holds a list of
|
||||
* {@link SubscriptionListData.SubscriptionListEntry}'s, one for each *active* subscriptions.
|
||||
*
|
||||
* This is used to:
|
||||
* 1) Show a list of SIMs in the SIM Selector
|
||||
* 2) Show the currently selected SIM in the compose message view
|
||||
* 3) Show SIM indicators on conversation message views
|
||||
*
|
||||
* It builds on top of SelfParticipantsData and performs additional logic such as determining
|
||||
* the set of icons to use for the individual Subs.
|
||||
*/
|
||||
public class SubscriptionListData {
|
||||
/**
|
||||
* Represents a single sub that backs UI.
|
||||
*/
|
||||
public static class SubscriptionListEntry {
|
||||
public final String selfParticipantId;
|
||||
public final Uri iconUri;
|
||||
public final Uri selectedIconUri;
|
||||
public final String displayName;
|
||||
public final int displayColor;
|
||||
public final String displayDestination;
|
||||
|
||||
private SubscriptionListEntry(final String selfParticipantId, final Uri iconUri,
|
||||
final Uri selectedIconUri, final String displayName, final int displayColor,
|
||||
final String displayDestination) {
|
||||
this.selfParticipantId = selfParticipantId;
|
||||
this.iconUri = iconUri;
|
||||
this.selectedIconUri = selectedIconUri;
|
||||
this.displayName = displayName;
|
||||
this.displayColor = displayColor;
|
||||
this.displayDestination = displayDestination;
|
||||
}
|
||||
|
||||
static SubscriptionListEntry fromSelfParticipantData(
|
||||
final ParticipantData selfParticipantData, final Context context) {
|
||||
Assert.isTrue(selfParticipantData.isSelf());
|
||||
Assert.isTrue(selfParticipantData.isActiveSubscription());
|
||||
final int slotId = selfParticipantData.getDisplaySlotId();
|
||||
final String iconIdentifier = String.format(Locale.getDefault(), "%d", slotId);
|
||||
final String subscriptionName = selfParticipantData.getSubscriptionName();
|
||||
final String displayName = TextUtils.isEmpty(subscriptionName) ?
|
||||
context.getString(R.string.sim_slot_identifier, slotId) : subscriptionName;
|
||||
return new SubscriptionListEntry(selfParticipantData.getId(),
|
||||
AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier,
|
||||
false /* selected */, false /* incoming */),
|
||||
AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier,
|
||||
true /* selected */, false /* incoming */),
|
||||
displayName, selfParticipantData.getSubscriptionColor(),
|
||||
selfParticipantData.getDisplayDestination());
|
||||
}
|
||||
}
|
||||
|
||||
private final List<SubscriptionListEntry> mEntriesExcludingDefault;
|
||||
private SubscriptionListEntry mDefaultEntry;
|
||||
private final Context mContext;
|
||||
|
||||
public SubscriptionListData(final Context context) {
|
||||
mEntriesExcludingDefault = new ArrayList<SubscriptionListEntry>();
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void bind(final List<ParticipantData> subs) {
|
||||
mEntriesExcludingDefault.clear();
|
||||
mDefaultEntry = null;
|
||||
for (final ParticipantData sub : subs) {
|
||||
final SubscriptionListEntry entry =
|
||||
SubscriptionListEntry.fromSelfParticipantData(sub, mContext);
|
||||
if (!sub.isDefaultSelf()) {
|
||||
mEntriesExcludingDefault.add(entry);
|
||||
} else {
|
||||
mDefaultEntry = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<SubscriptionListEntry> getActiveSubscriptionEntriesExcludingDefault() {
|
||||
return mEntriesExcludingDefault;
|
||||
}
|
||||
|
||||
public SubscriptionListEntry getActiveSubscriptionEntryBySelfId(final String selfId,
|
||||
final boolean excludeDefault) {
|
||||
if (mDefaultEntry != null && TextUtils.equals(mDefaultEntry.selfParticipantId, selfId)) {
|
||||
return excludeDefault ? null : mDefaultEntry;
|
||||
}
|
||||
|
||||
for (final SubscriptionListEntry entry : mEntriesExcludingDefault) {
|
||||
if (TextUtils.equals(entry.selfParticipantId, selfId)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasData() {
|
||||
return !mEntriesExcludingDefault.isEmpty() || mDefaultEntry != null;
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.datamodel.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.datamodel.media.BindableMediaRequest;
|
||||
import com.android.messaging.datamodel.media.MediaRequest;
|
||||
import com.android.messaging.datamodel.media.MediaResourceManager;
|
||||
import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
|
||||
import com.android.messaging.datamodel.media.VCardRequestDescriptor;
|
||||
import com.android.messaging.datamodel.media.VCardResource;
|
||||
import com.android.messaging.datamodel.media.VCardResourceEntry;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Data class for visualizing and loading data for a VCard contact.
|
||||
*/
|
||||
public class VCardContactItemData extends PersonItemData
|
||||
implements MediaResourceLoadListener<VCardResource> {
|
||||
private final Context mContext;
|
||||
private final Uri mVCardUri;
|
||||
private String mDetails;
|
||||
private final Binding<BindableMediaRequest<VCardResource>> mBinding =
|
||||
BindingBase.createBinding(this);
|
||||
private VCardResource mVCardResource;
|
||||
|
||||
private static final Uri sDefaultAvatarUri =
|
||||
AvatarUriUtil.createAvatarUri(null, null, null, null);
|
||||
|
||||
/**
|
||||
* Constructor. This parses data from the given MessagePartData describing the vcard
|
||||
*/
|
||||
public VCardContactItemData(final Context context, final MessagePartData messagePartData) {
|
||||
this(context, messagePartData.getContentUri());
|
||||
Assert.isTrue(messagePartData.isVCard());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. This parses data from the given VCard Uri
|
||||
*/
|
||||
public VCardContactItemData(final Context context, final Uri vCardUri) {
|
||||
mContext = context;
|
||||
mDetails = mContext.getString(R.string.loading_vcard);
|
||||
mVCardUri = vCardUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getAvatarUri() {
|
||||
if (hasValidVCard()) {
|
||||
final List<VCardResourceEntry> vcards = mVCardResource.getVCards();
|
||||
Assert.isTrue(vcards.size() > 0);
|
||||
if (vcards.size() == 1) {
|
||||
return vcards.get(0).getAvatarUri();
|
||||
}
|
||||
}
|
||||
return sDefaultAvatarUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
if (hasValidVCard()) {
|
||||
final List<VCardResourceEntry> vcards = mVCardResource.getVCards();
|
||||
Assert.isTrue(vcards.size() > 0);
|
||||
if (vcards.size() == 1) {
|
||||
return vcards.get(0).getDisplayName();
|
||||
} else {
|
||||
return mContext.getResources().getQuantityString(
|
||||
R.plurals.vcard_multiple_display_name, vcards.size(), vcards.size());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetails() {
|
||||
return mDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getClickIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContactId() {
|
||||
return ContactUtil.INVALID_CONTACT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLookupKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNormalizedDestination() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public VCardResource getVCardResource() {
|
||||
return hasValidVCard() ? mVCardResource : null;
|
||||
}
|
||||
|
||||
public Uri getVCardUri() {
|
||||
return hasValidVCard() ? mVCardUri : null;
|
||||
}
|
||||
|
||||
public boolean hasValidVCard() {
|
||||
return isBound() && mVCardResource != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(final String bindingId) {
|
||||
super.bind(bindingId);
|
||||
|
||||
// Bind and request the VCard from media resource manager.
|
||||
mBinding.bind(new VCardRequestDescriptor(mVCardUri).buildAsyncMediaRequest(mContext, this));
|
||||
MediaResourceManager.get().requestMediaResourceAsync(mBinding.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind(final String bindingId) {
|
||||
super.unbind(bindingId);
|
||||
mBinding.unbind();
|
||||
if (mVCardResource != null) {
|
||||
mVCardResource.release();
|
||||
mVCardResource = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof VCardContactItemData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VCardContactItemData lhs = (VCardContactItemData) o;
|
||||
return mVCardUri.equals(lhs.mVCardUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaResourceLoaded(final MediaRequest<VCardResource> request,
|
||||
final VCardResource resource, final boolean isCached) {
|
||||
Assert.isTrue(mVCardResource == null);
|
||||
mBinding.ensureBound();
|
||||
mDetails = mContext.getString(R.string.vcard_tap_hint);
|
||||
mVCardResource = resource;
|
||||
mVCardResource.addRef();
|
||||
notifyDataUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaResourceLoadError(final MediaRequest<VCardResource> request,
|
||||
final Exception exception) {
|
||||
mBinding.ensureBound();
|
||||
mDetails = mContext.getString(R.string.failed_loading_vcard);
|
||||
notifyDataFailed(exception);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user