diff options
author | tnmy-cyngn <tlnu@cyngn.com> | 2016-05-09 01:22:13 +0530 |
---|---|---|
committer | Gerrit Code Review <gerrit@cyanogenmod.org> | 2016-05-11 10:38:17 -0700 |
commit | 0dabdec1f6f6f90b6a0cd45646bdbf5fa79cde74 (patch) | |
tree | 7a7cdf30f367db4f93ad187f00ff980bf3b5bbd5 /src | |
parent | 1f769b00e211fae9f482f063e27efeab39c874ab (diff) | |
download | android_packages_apps_Messaging-0dabdec1f6f6f90b6a0cd45646bdbf5fa79cde74.tar.gz android_packages_apps_Messaging-0dabdec1f6f6f90b6a0cd45646bdbf5fa79cde74.tar.bz2 android_packages_apps_Messaging-0dabdec1f6f6f90b6a0cd45646bdbf5fa79cde74.zip |
Save messages to SIM feature
Porting save message to SIM feature from 12.1 to 13.0
Bug-Id: PAELLA-221
Change-Id: Id340d2cd5bb1ec0fa82cfbea8575999e65f4c34e
Diffstat (limited to 'src')
9 files changed, 1076 insertions, 19 deletions
diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageData.java b/src/com/android/messaging/datamodel/data/ConversationMessageData.java index 19e1b97..9a160e9 100644 --- a/src/com/android/messaging/datamodel/data/ConversationMessageData.java +++ b/src/com/android/messaging/datamodel/data/ConversationMessageData.java @@ -19,6 +19,7 @@ import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; import android.provider.ContactsContract; +import android.telephony.SmsManager; import android.text.TextUtils; import android.text.format.DateUtils; @@ -134,6 +135,25 @@ public class ConversationMessageData { } } + public void bindToSimMessages(Cursor cursor) { + mMessageId = String.valueOf(cursor.getInt(SimMessageData.INDEX_INDEX_ON_ICC)); + int IccStatus = cursor.getInt(SimMessageData.INDEX_STATUS); + if (IccStatus == SmsManager.STATUS_ON_ICC_SENT) { + mStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE; + mSentTimestamp = cursor.getLong(SimMessageData.INDEX_DATE); + } else { + mStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE; + mReceivedTimestamp = cursor.getLong(SimMessageData.INDEX_DATE); + } + mSenderDisplayDestination = cursor.getString(SimMessageData.INDEX_ADDRESS); + mPartsCount = 1; + mParts = new ArrayList<MessagePartData>(); + mParts.add(new MessagePartData(cursor.getString(SimMessageData.INDEX_BODY))); + mSeen = true; + mRead = true; + mProtocol = MessageData.PROTOCOL_SMS; + } + private boolean canClusterWithMessage(final Cursor cursor) { final String otherParticipantId = cursor.getString(INDEX_PARTICIPANT_ID); if (!TextUtils.equals(getParticipantId(), otherParticipantId)) { diff --git a/src/com/android/messaging/datamodel/data/SimMessageData.java b/src/com/android/messaging/datamodel/data/SimMessageData.java new file mode 100644 index 0000000..937b0e2 --- /dev/null +++ b/src/com/android/messaging/datamodel/data/SimMessageData.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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; + +public class SimMessageData { + + public static final int INDEX_SERVICE_CENTER_ADDRESS = 0; + public static final int INDEX_ADDRESS = 1; + public static final int INDEX_MESSAGE_CLASS = 2; + public static final int INDEX_BODY = 3; + public static final int INDEX_DATE = 4; + public static final int INDEX_STATUS = 5; + public static final int INDEX_INDEX_ON_ICC = 6; + public static final int INDEX_IS_STATUS_REPORT = 7; + public static final int INDEX_TRANSPORT_TYPE = 8; + public static final int INDEX_TYPE = 9; + public static final int INDEX_LOCKED = 10; + public static final int INDEX_ERROR_CODE = 11; + public static final int INDEX_ID = 12; + public static final int INDEX_SUB_ID = 13; +} diff --git a/src/com/android/messaging/sms/SimMessagesUtils.java b/src/com/android/messaging/sms/SimMessagesUtils.java new file mode 100644 index 0000000..f7be881 --- /dev/null +++ b/src/com/android/messaging/sms/SimMessagesUtils.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2016 The CyanogenMod 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.sms; + +import android.content.Context; +import android.net.Uri; +import android.telephony.PhoneNumberUtils; +import android.telephony.SmsManager; +import android.telephony.SmsMessage; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import com.android.internal.telephony.EncodeException; +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.SmsHeader; +import com.android.internal.telephony.cdma.sms.BearerData; +import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; +import com.android.internal.telephony.cdma.sms.UserData; +import com.android.messaging.datamodel.data.ConversationMessageData; +import com.android.messaging.datamodel.data.ConversationParticipantsData; +import com.android.messaging.R; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Locale; + +import static android.telephony.SmsMessage.ENCODING_16BIT; +import static android.telephony.SmsMessage.ENCODING_7BIT; +import static android.telephony.SmsMessage.ENCODING_UNKNOWN; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; +import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; + +public class SimMessagesUtils { + + private static final String TAG = SimMessagesUtils.class.getSimpleName(); + public static final int SUB_INVALID = -1; // for single card product + public static final int SUB1 = 0; // for DSDS product of slot one + public static final int SUB2 = 1; // for DSDS product of slot two + + public static final Uri ICC_URI = Uri.parse("content://sms/icc"); + public static final Uri ICC1_URI = Uri.parse("content://sms/icc1"); + public static final Uri ICC2_URI = Uri.parse("content://sms/icc2"); + private static final int TIMESTAMP_LENGTH = 7; // See TS 23.040 9.2.3.11 + public static String WAPPUSH = "Browser Information"; // Wap push key + + /** + * Return the icc uri according to subscription + */ + public static Uri getIccUriBySlot(int slot) { + switch (slot) { + case SUB1: + return ICC1_URI; + case SUB2: + return ICC2_URI; + default: + return ICC_URI; + } + } + + public static boolean isMultiSimEnabledMms() { + return TelephonyManager.getDefault().isMultiSimEnabled(); + } + + /** + * Return whether it has card no matter in DSDS or not + */ + public static boolean hasIccCard() { + return TelephonyManager.getDefault().hasIccCard(); + } + + /** + * Return whether the card in the given slot is activated + */ + public static boolean isIccCardActivated(int slot) { + TelephonyManager tm = TelephonyManager.getDefault(); + final int simState = tm.getSimState(slot); + return (simState != TelephonyManager.SIM_STATE_ABSENT) + && (simState != TelephonyManager.SIM_STATE_UNKNOWN); + } + + public static boolean copyToSim(ConversationMessageData messageData, + ConversationParticipantsData participants, int subId) { + String address; + address = (participants.getOtherParticipant() == null) ? + null : + participants.getOtherParticipant().getDisplayDestination(); + if(TextUtils.isEmpty(address)) { + return false; + } + if (SimMessagesUtils.isWapPushNumber(address)) { + String[] number = address.split(":"); + address = number[0]; + } + + String text = messageData.getText(); + if (TextUtils.isEmpty(text)) { + return false; + } + long timestamp = messageData.getReceivedTimeStamp() != 0 ? + messageData.getReceivedTimeStamp() : + System.currentTimeMillis(); + + SmsManager sm = SmsManager.getDefault(); + ArrayList<String> messages = sm.divideMessage(text); + + boolean ret = true; + for (String message : messages) { + byte pdu[] = null; + int status; + if (messageData.getIsIncoming()) { + pdu = SimMessagesUtils.getDeliveryPdu(null, address, + message, timestamp, subId); + status = SmsManager.STATUS_ON_ICC_READ; + } else { + pdu = SmsMessage.getSubmitPdu(null, address, message, + false, subId).encodedMessage; + status = SmsManager.STATUS_ON_ICC_SENT; + } + ret &= TelephonyManager.getDefault().isMultiSimEnabled() + ? SmsManager.getSmsManagerForSubscriptionId(subId) + .copyMessageToIcc(null, pdu, status) + : sm.copyMessageToIcc(null, pdu, status); + if (!ret) { + break; + } + } + return ret; + } + + private static boolean isCDMAPhone(int subscription) { + int activePhone = isMultiSimEnabledMms() + ? TelephonyManager.getDefault().getCurrentPhoneType(subscription) + : TelephonyManager.getDefault().getPhoneType(); + return activePhone == TelephonyManager.PHONE_TYPE_CDMA; + } + + public static byte[] getDeliveryPdu(String scAddress, String destinationAddress, String message, + long date, int subscription) { + if (isCDMAPhone(subscription)) { + return getCDMADeliveryPdu(scAddress, destinationAddress, message, date); + } else { + return getDeliveryPdu(scAddress, destinationAddress, message, date, null, + ENCODING_UNKNOWN); + } + } + + public static byte[] getCDMADeliveryPdu(String scAddress, String destinationAddress, + String message, long date) { + // Perform null parameter checks. + if (message == null || destinationAddress == null) { + Log.d(TAG, "getCDMADeliveryPdu,message =null"); + return null; + } + + // according to submit pdu encoding as written in privateGetSubmitPdu + + // MTI = SMS-DELIVERY, UDHI = header != null + byte[] header = null; + byte mtiByte = (byte) (0x00 | (header != null ? 0x40 : 0x00)); + ByteArrayOutputStream headerStream = getDeliveryPduHeader(destinationAddress, mtiByte); + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40); + + DataOutputStream dos = new DataOutputStream(byteStream); + // int status,Status of message. See TS 27.005 3.1, "<stat>" + + /* 0 = "REC UNREAD" */ + /* 1 = "REC READ" */ + /* 2 = "STO UNSENT" */ + /* 3 = "STO SENT" */ + + try { + // int uTeleserviceID; + int uTeleserviceID = 0; //.TELESERVICE_CT_WAP;// int + dos.writeInt(uTeleserviceID); + + // unsigned char bIsServicePresent + byte bIsServicePresent = 0;// byte + dos.writeInt(bIsServicePresent); + + // uServicecategory + int uServicecategory = 0;// int + dos.writeInt(uServicecategory); + + // RIL_CDMA_SMS_Address + // digit_mode + // number_mode + // number_type + // number_plan + // number_of_digits + // digits[] + CdmaSmsAddress destAddr = CdmaSmsAddress.parse(PhoneNumberUtils + .cdmaCheckAndProcessPlusCode(destinationAddress)); + if (destAddr == null) + return null; + dos.writeByte(destAddr.digitMode);// int + dos.writeByte(destAddr.numberMode);// int + dos.writeByte(destAddr.ton);// int + dos.writeByte(destAddr.numberPlan);// int + dos.writeByte(destAddr.numberOfDigits);// byte + dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits + + // RIL_CDMA_SMS_Subaddress + // Subaddress is not supported. + dos.writeByte(0); // subaddressType int + dos.writeByte(0); // subaddr_odd byte + dos.writeByte(0); // subaddr_nbr_of_digits byte + + SmsHeader smsHeader = new SmsHeader().fromByteArray(headerStream.toByteArray()); + UserData uData = new UserData(); + uData.payloadStr = message; + // uData.userDataHeader = smsHeader; + uData.msgEncodingSet = true; + uData.msgEncoding = UserData.ENCODING_UNICODE_16; + + BearerData bearerData = new BearerData(); + bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; + + bearerData.deliveryAckReq = false; + bearerData.userAckReq = false; + bearerData.readAckReq = false; + bearerData.reportReq = false; + + bearerData.userData = uData; + + byte[] encodedBearerData = BearerData.encode(bearerData); + if (null != encodedBearerData) { + // bearer data len + dos.writeByte(encodedBearerData.length);// int + Log.d(TAG, "encodedBearerData length=" + encodedBearerData.length); + + // aBearerData + dos.write(encodedBearerData, 0, encodedBearerData.length); + } else { + dos.writeByte(0); + } + + } catch (IOException e) { + Log.e(TAG, "Error writing dos", e); + } finally { + try { + if (null != byteStream) { + byteStream.close(); + } + + if (null != dos) { + dos.close(); + } + + if (null != headerStream) { + headerStream.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error close dos", e); + } + } + + return byteStream.toByteArray(); + } + + /** + * Generate a Delivery PDU byte array. see getSubmitPdu for reference. + */ + public static byte[] getDeliveryPdu(String scAddress, String destinationAddress, String message, + long date, byte[] header, int encoding) { + // Perform null parameter checks. + if (message == null || destinationAddress == null) { + return null; + } + + // MTI = SMS-DELIVERY, UDHI = header != null + byte mtiByte = (byte)(0x00 | (header != null ? 0x40 : 0x00)); + ByteArrayOutputStream bo = getDeliveryPduHeader(destinationAddress, mtiByte); + // User Data (and length) + byte[] userData; + if (encoding == ENCODING_UNKNOWN) { + // First, try encoding it with the GSM alphabet + encoding = ENCODING_7BIT; + } + try { + if (encoding == ENCODING_7BIT) { + userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 0, 0); + } else { //assume UCS-2 + try { + userData = encodeUCS2(message, header); + } catch (UnsupportedEncodingException uex) { + Log.e("GSM", "Implausible UnsupportedEncodingException ", + uex); + return null; + } + } + } catch (EncodeException ex) { + // Encoding to the 7-bit alphabet failed. Let's see if we can + // encode it as a UCS-2 encoded message + try { + userData = encodeUCS2(message, header); + encoding = ENCODING_16BIT; + } catch (UnsupportedEncodingException uex) { + Log.e("GSM", "Implausible UnsupportedEncodingException ", + uex); + return null; + } + } + + if (encoding == ENCODING_7BIT) { + if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) { + // Message too long + return null; + } + bo.write(0x00); + } else { //assume UCS-2 + if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) { + // Message too long + return null; + } + // TP-Data-Coding-Scheme + // Class 3, UCS-2 encoding, uncompressed + bo.write(0x0b); + } + byte[] timestamp = getTimestamp(date); + bo.write(timestamp, 0, timestamp.length); + + bo.write(userData, 0, userData.length); + return bo.toByteArray(); + } + + private static ByteArrayOutputStream getDeliveryPduHeader( + String destinationAddress, byte mtiByte) { + ByteArrayOutputStream bo = new ByteArrayOutputStream( + MAX_USER_DATA_BYTES + 40); + bo.write(mtiByte); + + byte[] daBytes; + daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress); + + // destination address length in BCD digits, ignoring TON byte and pad + // TODO Should be better. + bo.write((daBytes.length - 1) * 2 + - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0)); + + // destination address + bo.write(daBytes, 0, daBytes.length); + + // TP-Protocol-Identifier + bo.write(0); + return bo; + } + + private static byte[] getTimestamp(long time) { + // See TS 23.040 9.2.3.11 + byte[] timestamp = new byte[TIMESTAMP_LENGTH]; + SimpleDateFormat sdf = new SimpleDateFormat("yyMMddkkmmss:Z", Locale.US); + String[] date = sdf.format(time).split(":"); + // generate timezone value + String timezone = date[date.length - 1]; + String signMark = timezone.substring(0, 1); + int hour = Integer.parseInt(timezone.substring(1, 3)); + int min = Integer.parseInt(timezone.substring(3)); + int timezoneValue = hour * 4 + min / 15; + // append timezone value to date[0] (time string) + String timestampStr = date[0] + timezoneValue; + + int digitCount = 0; + for (int i = 0; i < timestampStr.length(); i++) { + char c = timestampStr.charAt(i); + int shift = ((digitCount & 0x01) == 1) ? 4 : 0; + timestamp[(digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); + digitCount++; + } + + if (signMark.equals("-")) { + timestamp[timestamp.length - 1] = (byte) (timestamp[timestamp.length - 1] | 0x08); + } + + return timestamp; + } + + private static byte[] encodeUCS2(String message, byte[] header) + throws UnsupportedEncodingException { + byte[] userData, textPart; + textPart = message.getBytes("utf-16be"); + + if (header != null) { + // Need 1 byte for UDHL + userData = new byte[header.length + textPart.length + 1]; + + userData[0] = (byte)header.length; + System.arraycopy(header, 0, userData, 1, header.length); + System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length); + } + else { + userData = textPart; + } + byte[] ret = new byte[userData.length+1]; + ret[0] = (byte) (userData.length & 0xff ); + System.arraycopy(userData, 0, ret, 1, userData.length); + return ret; + } + + private static int charToBCD(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else { + throw new RuntimeException ("invalid char for BCD " + c); + } + } + + /** + * Returns true if the address passed in is a Browser wap push MMS address. + */ + public static boolean isWapPushNumber(String address) { + if (TextUtils.isEmpty(address)) { + return false; + } else { + return address.contains(WAPPUSH); + } + } + + /** + * Return the sim name of subscription. + */ + public static String getMultiSimName(Context context, int slot) { + if (slot >= TelephonyManager.getDefault().getPhoneCount() || slot < 0) { + return null; + } + //String multiSimName = Settings.System.getString(context.getContentResolver(), + // MULTI_SIM_NAME + (subscription + 1)); + //if (multiSimName == null) { + if (slot == SUB1) { + return context.getString(R.string.slot1); + } else if (slot == SUB2) { + return context.getString(R.string.slot2); + } + //} + return context.getString(R.string.slot1); + } + + /** + * Return the activated card number + */ + public static int getActivatedIccCardCount() { + TelephonyManager tm = TelephonyManager.getDefault(); + int phoneCount = tm.getPhoneCount(); + int count = 0; + for (int i = 0; i < phoneCount; i++) { + if (isIccCardActivated(i)) { + count++; + } + } + return count; + } +} diff --git a/src/com/android/messaging/ui/ManageSimMessages.java b/src/com/android/messaging/ui/ManageSimMessages.java new file mode 100644 index 0000000..f658300 --- /dev/null +++ b/src/com/android/messaging/ui/ManageSimMessages.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * Copyright (C) 2008 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.ui; + +import android.app.AlertDialog; +import android.content.AsyncQueryHandler; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SqliteWrapper; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.telephony.SubscriptionManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.TelephonyIntents; + +import com.android.messaging.R; +import com.android.messaging.datamodel.data.MessagePartData; +import com.android.messaging.datamodel.data.SubscriptionListData; +import com.android.messaging.sms.SimMessagesUtils; +import com.android.messaging.ui.conversation.ConversationMessageAdapter; +import com.android.messaging.ui.conversation.ConversationMessageView; +import com.android.messaging.util.UiUtils; + +/** + * Displays a list of the SMS messages stored on the ICC. + */ +public class ManageSimMessages extends BugleActionBarActivity + implements ConversationMessageView.ConversationMessageViewHost, + View.OnCreateContextMenuListener { + private static final String TAG = ManageSimMessages.class.getSimpleName(); + + private static final int SHOW_LIST = 0; + private static final int SHOW_EMPTY = 1; + private static final int SHOW_BUSY = 2; + private int mState; + private int mSlot; + private int mSubscription; + + private Uri mIccUri; + private ContentResolver mContentResolver; + private Cursor mCursor = null; + private RecyclerView mSimList; + private TextView mMessage; + private ConversationMessageAdapter mListAdapter = null; + private AsyncQueryHandler mQueryHandler = null; + private boolean mIsQuery = false; + private ConversationMessageView mSelectedMessage = null; + public static final int TYPE_INBOX = 1; + + private final ContentObserver simChangeObserver = + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfUpdate) { + refreshMessageList(); + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) { + refreshMessageList(); + } + } + }; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mContentResolver = getContentResolver(); + mQueryHandler = new QueryHandler(mContentResolver, this); + setContentView(R.layout.sim_list); + mSimList = (RecyclerView) findViewById(android.R.id.list); + final LinearLayoutManager manager = new LinearLayoutManager(this); + manager.setStackFromEnd(false); + manager.setReverseLayout(false); + mSimList.setHasFixedSize(true); + mSimList.setLayoutManager(manager); + mMessage = (TextView) findViewById(R.id.empty_message); + IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + registerReceiver(mReceiver, filter); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + init(); + } + + @Override + protected void onNewIntent(Intent intent) { + setIntent(intent); + + init(); + } + + private void init() { + mSlot = getIntent().getIntExtra(PhoneConstants.PHONE_KEY, SimMessagesUtils.SUB_INVALID); + + mSubscription = SimMessagesUtils.SUB_INVALID; + int[] subIds = SubscriptionManager.getSubId(mSlot); + if (subIds != null && subIds.length > 0) { + mSubscription = subIds[0]; + } + + mIccUri = SimMessagesUtils.getIccUriBySlot(mSlot); + updateState(SHOW_BUSY); + startQuery(); + } + + @Override + public boolean onAttachmentClick(ConversationMessageView view, MessagePartData attachment, + Rect imageBounds, boolean longPress) { + return false; + } + + @Override + public SubscriptionListData.SubscriptionListEntry getSubscriptionEntryForSelfParticipant( + String selfParticipantId, boolean excludeDefault) { + return null; + } + + private class QueryHandler extends AsyncQueryHandler { + + public QueryHandler( + ContentResolver contentResolver, ManageSimMessages parent) { + super(contentResolver); + } + + @Override + protected void onQueryComplete( + int token, Object cookie, Cursor cursor) { + if (mCursor != null) { + stopManagingCursor(mCursor); + } + mCursor = cursor; + if (mCursor != null) { + if (!mCursor.moveToFirst()) { + // Let user know the SIM is empty + updateState(SHOW_EMPTY); + } else if (mListAdapter == null) { + mListAdapter = new ConversationMessageAdapter( + ManageSimMessages.this, mCursor, ManageSimMessages.this, null, + onMessageListItemClick, onMessageListItemLongClick, true); + mSimList.setAdapter(mListAdapter); + updateState(SHOW_LIST); + } else { + mListAdapter.changeCursor(mCursor); + updateState(SHOW_LIST); + } + startManagingCursor(mCursor); + } else { + // Let user know the SIM is empty + updateState(SHOW_EMPTY); + } + mIsQuery = false; + } + } + + private void startQuery() { + try { + if (mIsQuery) { + return; + } + mIsQuery = true; + mQueryHandler.startQuery(0, null, mIccUri, null, null, null, null); + } catch (SQLiteException e) { + SqliteWrapper.checkSQLiteException(this, e); + } + } + + private void refreshMessageList() { + updateState(SHOW_BUSY); + startQuery(); + } + + + @Override + public void onResume() { + super.onResume(); + registerSimChangeObserver(); + } + + @Override + public void onPause() { + super.onPause(); + mContentResolver.unregisterContentObserver(simChangeObserver); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + if (mSelectedMessage == null) { + finish(); + } else { + dismissActionMode(); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + unregisterReceiver(mReceiver); + super.onDestroy(); + } + + @Override + protected void updateActionBar(ActionBar actionBar) { + super.updateActionBar(actionBar); + //The Action Mode menu changes the action bar completely. Need to reset it! + updateActionAndStatusBarColor(getSupportActionBar()); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeAsUpIndicator(0); + actionBar.setDisplayShowTitleEnabled(true); + } + + private void updateActionAndStatusBarColor(final ActionBar actionBar) { + final int themeColor = ConversationDrawables.get().getConversationThemeColor(); + actionBar.setBackgroundDrawable(new ColorDrawable(themeColor)); + UiUtils.setStatusBarColor(this, themeColor); + } + + private void registerSimChangeObserver() { + mContentResolver.registerContentObserver( + mIccUri, true, simChangeObserver); + } + + private void updateState(int state) { + if (mState == state) { + return; + } + + mState = state; + switch (state) { + case SHOW_LIST: + mSimList.setVisibility(View.VISIBLE); + mMessage.setVisibility(View.GONE); + setTitle(getString(R.string.sim_manage_messages_title)); + setProgressBarIndeterminateVisibility(false); + mSimList.requestFocus(); + break; + case SHOW_EMPTY: + mSimList.setVisibility(View.GONE); + mMessage.setVisibility(View.VISIBLE); + setTitle(getString(R.string.sim_manage_messages_title)); + setProgressBarIndeterminateVisibility(false); + break; + case SHOW_BUSY: + mSimList.setVisibility(View.GONE); + mMessage.setVisibility(View.GONE); + setTitle(getString(R.string.refreshing)); + setProgressBarIndeterminateVisibility(true); + break; + default: + Log.e(TAG, "Invalid State"); + } + } + + public Context getContext() { + return ManageSimMessages.this; + } + + private void selectMessage(final ConversationMessageView messageView) { + mSelectedMessage = messageView; + if (mSelectedMessage == null) { + mListAdapter.setSelectedMessage(null); + dismissActionMode(); + return; + } + mListAdapter.setSelectedMessage(messageView.getData().getMessageId()); + startActionMode(mActionModeCallback); + } + + private void confirmDeleteDialog(DialogInterface.OnClickListener listener) { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.confirm_dialog_title); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setCancelable(true); + builder.setPositiveButton(R.string.yes, listener); + builder.setNegativeButton(R.string.no, listener); + builder.setMessage(R.string.confirm_delete_selected_messages); + builder.show(); + } + + private void deleteMessageFromSim() { + if (mSelectedMessage == null) { + return; + } + String messageIndexString = + mSelectedMessage.getData().getMessageId(); + if (TextUtils.isEmpty(messageIndexString)) { + return; + } + Uri simUri = mIccUri.buildUpon().appendPath(messageIndexString).build(); + SqliteWrapper.delete(this, mContentResolver, simUri, null, null); + } + + View.OnClickListener onMessageListItemClick = new View.OnClickListener() { + @Override + public void onClick(View view) { + //Do Nothing + } + }; + + View.OnLongClickListener onMessageListItemLongClick = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + selectMessage((ConversationMessageView) view); + return true; + } + }; + + private class DeleteConfirmListener implements DialogInterface.OnClickListener { + public void onClick(DialogInterface dialog, int whichButton) { + switch (whichButton) { + case DialogInterface.BUTTON_POSITIVE: + deleteMessageFromSim(); + break; + } + dismissActionMode(); + } + } + + private final ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (mSelectedMessage != null) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.sim_msg_multi_select_menu, menu); + return true; + } + return false; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.delete: + confirmDeleteDialog(new DeleteConfirmListener()); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + selectMessage(null); + } + }; +} diff --git a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java index b91cc6c..ae5bbf3 100644 --- a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java +++ b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java @@ -37,10 +37,13 @@ import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; +import com.android.internal.telephony.PhoneConstants; import com.android.messaging.R; import com.android.messaging.sms.MmsConfig; +import com.android.messaging.sms.SimMessagesUtils; import com.android.messaging.ui.BugleActionBarActivity; import com.android.messaging.ui.LicenseActivity; +import com.android.messaging.ui.ManageSimMessages; import com.android.messaging.ui.NumberPickerDialog; import com.android.messaging.ui.UIIntents; import com.android.messaging.util.BuglePrefs; @@ -109,6 +112,9 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity { private ListPreference mSmsValidityCard2Pref; private Preference mSmsLimitPref; private Preference mMmsLimitPref; + private Preference mManageSimPref; + private Preference mManageSim1Pref; + private Preference mManageSim2Pref; public ApplicationSettingsFragment() { @@ -139,6 +145,9 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity { mSmsValidityCard2Pref = (ListPreference) findPreference("pref_key_sms_validity_period_slot2"); mSmsLimitPref = findPreference("sms_delete_limit_pref_key"); mMmsLimitPref = findPreference("mms_delete_limit_pref_key"); + mManageSimPref = findPreference("pref_key_manage_sim_messages"); + mManageSim1Pref = findPreference("pref_key_manage_sim_messages_slot1"); + mManageSim2Pref = findPreference("pref_key_manage_sim_messages_slot2"); if (getResources().getBoolean(R.bool.config_sms_validity)) { if (PhoneUtils.getDefault().isMultiSimEnabledMms()) { @@ -181,6 +190,7 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity { } setSmsDisplayLimit(); setMmsDisplayLimit(); + updateSIMSMSPref(); } @Override @@ -189,28 +199,38 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity { if (preference.getKey() == mSmsDisabledPrefKey || preference.getKey() == mSmsEnabledPrefKey) { mIsSmsPreferenceClicked = true; - } else if (getActivity() != null && - preference.getKey().equals(mSmsLimitPref.getKey())) { + } else if (getActivity() != null) { + if (preference.getKey().equals(mSmsLimitPref.getKey())) { new NumberPickerDialog(getActivity(), - mSmsLimitListener, - PrefsUtils.getSMSMessagesPerThreadLimit(), - MmsConfig.getMinMessageCountPerThread(), - MmsConfig.getMaxMessageCountPerThread(), - R.string.sms_delete_pref_title, - R.string.pref_messages_to_save).show(); + mSmsLimitListener, + PrefsUtils.getSMSMessagesPerThreadLimit(), + MmsConfig.getMinMessageCountPerThread(), + MmsConfig.getMaxMessageCountPerThread(), + R.string.sms_delete_pref_title, + R.string.pref_messages_to_save).show(); - } else if(getActivity() != null && - preference.getKey().equals(mMmsLimitPref.getKey())) { + } else if (preference.getKey().equals(mMmsLimitPref.getKey())) { new NumberPickerDialog(getActivity(), - mMmsLimitListener, - PrefsUtils.getMMSMessagesPerThreadLimit(), - MmsConfig.getMinMessageCountPerThread(), - MmsConfig.getMaxMessageCountPerThread(), - R.string.mms_delete_pref_title, - R.string.pref_messages_to_save).show(); - + mMmsLimitListener, + PrefsUtils.getMMSMessagesPerThreadLimit(), + MmsConfig.getMinMessageCountPerThread(), + MmsConfig.getMaxMessageCountPerThread(), + R.string.mms_delete_pref_title, + R.string.pref_messages_to_save).show(); + + } else if (preference.getKey().equals(mManageSimPref.getKey())) { + startActivity(new Intent(getActivity(), ManageSimMessages.class)); + } else if (preference.getKey().equals(mManageSim1Pref.getKey())) { + Intent intent = new Intent(getActivity(), ManageSimMessages.class); + intent.putExtra(PhoneConstants.PHONE_KEY, SimMessagesUtils.SUB1); + startActivity(intent); + } else if (preference.getKey().equals(mManageSim2Pref.getKey())) { + Intent intent = new Intent(getActivity(), ManageSimMessages.class); + intent.putExtra(PhoneConstants.PHONE_KEY, SimMessagesUtils.SUB2); + startActivity(intent); + } } return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -330,6 +350,24 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity { PrefsUtils.getMMSMessagesPerThreadLimit())); } + private void updateSIMSMSPref() { + if (SimMessagesUtils.isMultiSimEnabledMms()) { + if (!SimMessagesUtils.isIccCardActivated(SimMessagesUtils.SUB1)) { + mManageSim1Pref.setEnabled(false); + } + if (!SimMessagesUtils.isIccCardActivated(SimMessagesUtils.SUB2)) { + mManageSim2Pref.setEnabled(false); + } + getPreferenceScreen().removePreference(mManageSimPref); + } else { + if (!SimMessagesUtils.hasIccCard()) { + mManageSimPref.setEnabled(false); + } + getPreferenceScreen().removePreference(mManageSim1Pref); + getPreferenceScreen().removePreference(mManageSim2Pref); + } + } + NumberPickerDialog.OnNumberSetListener mSmsLimitListener = new NumberPickerDialog.OnNumberSetListener() { public void onNumberSet(int limit) { diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java index 4020eed..0bfd644 100644 --- a/src/com/android/messaging/ui/conversation/ConversationFragment.java +++ b/src/com/android/messaging/ui/conversation/ConversationFragment.java @@ -43,6 +43,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Parcelable; +import android.provider.Telephony; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.text.BidiFormatter; import android.support.v4.text.TextDirectionHeuristicsCompat; @@ -51,7 +52,12 @@ import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; +import android.telephony.SmsManager; +import android.telephony.SmsMessage; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.Log; import android.view.ActionMode; import android.view.Display; import android.view.LayoutInflater; @@ -63,6 +69,7 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; import com.android.messaging.BugleApplication; import com.android.messaging.R; import com.android.messaging.datamodel.DataModel; @@ -81,6 +88,7 @@ import com.android.messaging.datamodel.data.MessageData; import com.android.messaging.datamodel.data.MessagePartData; import com.android.messaging.datamodel.data.ParticipantData; import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry; +import com.android.messaging.sms.SimMessagesUtils; import com.android.messaging.ui.AttachmentPreview; import com.android.messaging.ui.BugleActionBarActivity; import com.android.messaging.ui.ConversationDrawables; @@ -373,6 +381,16 @@ public class ConversationFragment extends Fragment implements ConversationDataLi UIIntents.get().launchForwardMessageActivity(getActivity(), message); mHost.dismissActionMode(); return true; + case R.id.copy_to_sim: + if (data != null && mBinding.getData().getParticipants() != null ) { + if(SimMessagesUtils.getActivatedIccCardCount() > 1) { + showSimSelectDialog(data); + } else { + copyToSim(data, SubscriptionManager.getDefaultSmsSubId()); + } + } + mHost.dismissActionMode(); + return true; } return false; } @@ -1434,6 +1452,51 @@ public class ConversationFragment extends Fragment implements ConversationDataLi ((BugleActionBarActivity) activity).supportInvalidateOptionsMenu(); } + private void copyToSim(ConversationMessageData data, int subId) { + boolean success = SimMessagesUtils + .copyToSim(data, mBinding.getData().getParticipants(), subId); + CharSequence copyToSimStatus = success ? + getResources().getText(R.string.copy_to_phone_success) : + getResources().getText(R.string.copy_to_sim_fail); + Toast.makeText(getActivity(), copyToSimStatus.toString(), + Toast.LENGTH_SHORT).show(); + } + + private void showSimSelectDialog(ConversationMessageData data) { + String[] items = new String[TelephonyManager.getDefault() + .getPhoneCount()]; + for (int i = 0; i < items.length; i++) { + items[i] = SimMessagesUtils.getMultiSimName( + getActivity(), i); + } + CopyToSimSelectListener listener = new CopyToSimSelectListener( + data); + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.copy_to_sim) + .setPositiveButton(android.R.string.ok, listener) + .setSingleChoiceItems(items, 0, listener) + .setCancelable(true).show(); + } + + private class CopyToSimSelectListener implements DialogInterface.OnClickListener { + private ConversationMessageData messageData; + private int slot; + + public CopyToSimSelectListener(ConversationMessageData messageData) { + super(); + this.messageData = messageData; + } + + public void onClick(DialogInterface dialog, int which) { + if (which >= 0) { + slot = which; + } else if (which == DialogInterface.BUTTON_POSITIVE) { + int[] subId = SubscriptionManager.getSubId(slot); + copyToSim(messageData, subId[0]); + } + } + } + @Override public void setOptionsMenuVisibility(final boolean visible) { setHasOptionsMenu(visible); diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java index 2748fff..3d50252 100644 --- a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java +++ b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java @@ -45,6 +45,7 @@ public class ConversationMessageAdapter extends private final View.OnLongClickListener mViewLongClickListener; private boolean mOneOnOne; private String mSelectedMessageId; + private boolean isSmsMessage; public ConversationMessageAdapter(final Context context, final Cursor cursor, final ConversationMessageViewHost host, @@ -59,13 +60,28 @@ public class ConversationMessageAdapter extends setHasStableIds(true); } + public ConversationMessageAdapter(final Context context, final Cursor cursor, + final ConversationMessageViewHost host, + final AsyncImageViewDelayLoader imageViewDelayLoader, + final View.OnClickListener viewClickListener, + final View.OnLongClickListener longClickListener, boolean isSmsMessage) { + this(context, cursor, host, imageViewDelayLoader, viewClickListener, longClickListener); + this.isSmsMessage = isSmsMessage; + } + + + @Override public void bindViewHolder(final ConversationMessageViewHolder holder, final Context context, final Cursor cursor) { Assert.isTrue(holder.mView instanceof ConversationMessageView); final ConversationMessageView conversationMessageView = (ConversationMessageView) holder.mView; - conversationMessageView.bind(cursor, mOneOnOne, mSelectedMessageId); + if(isSmsMessage) { + conversationMessageView.bindToSimMessages(cursor, mSelectedMessageId); + } else { + conversationMessageView.bind(cursor, mOneOnOne, mSelectedMessageId); + } } @Override diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java index d68cbde..395936d 100644 --- a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java +++ b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java @@ -95,11 +95,15 @@ public class ConversationMessageBubbleView extends LinearLayout { } } - public void bind(QuickMessage quickMessage) { + public void bind() { mShouldAnimateWidthChange = false; mMorphedWidth = 0; } + public void bind(QuickMessage quickMessage) { + bind(); + } + public void kickOffMorphAnimation(final int oldWidth, final int newWidth) { if (mAnimator != null) { mAnimator.setIntValues(mRunningStartWidth, newWidth); diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java index 4b61cdc..de50184 100644 --- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java +++ b/src/com/android/messaging/ui/conversation/ConversationMessageView.java @@ -232,6 +232,25 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick contentTop + contentHeight); } + public void bindToSimMessages(final Cursor cursor, final String selectedMessageId) { + mData.bindToSimMessages(cursor); + setSelected(TextUtils.equals(mData.getMessageId(), selectedMessageId)); + // Update text and image content for the view. + updateViewContent(); + + // Update colors and layout parameters for the view. + updateViewAppearance(); + + updateContentDescription(); + + //Necessary to remove bubble width animation + mMessageBubble.bind(); + //SIM Messages don't save timestamp for outgoing messages + if(!mData.getIsIncoming()) { + mStatusTextView.setVisibility(View.GONE); + } + } + /** * Fills in the data associated with this view. * |