/*
* Copyright (C) 2007-2008 Esmertec AG.
* Copyright (C) 2007-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.mms.transaction;
import com.android.internal.util.HexDump;
import com.android.mms.MmsConfig;
import com.android.mms.ui.MessageUtils;
import com.android.mms.util.DownloadManager;
import com.android.mms.util.Recycler;
import com.android.mms.ui.MessageFolderActivity;
import com.google.android.mms.MmsException;
import com.google.android.mms.pdu.AcknowledgeInd;
import com.google.android.mms.pdu.NotificationInd;
import com.google.android.mms.pdu.PduComposer;
import com.google.android.mms.pdu.PduHeaders;
import com.google.android.mms.pdu.PduParser;
import com.google.android.mms.pdu.PduPersister;
import com.google.android.mms.pdu.RetrieveConf;
import com.google.android.mms.pdu.EncodedStringValue;
import android.database.sqlite.SqliteWrapper;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Mms.Inbox;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import android.content.Intent;
import java.io.IOException;
/**
* The RetrieveTransaction is responsible for retrieving multimedia
* messages (M-Retrieve.conf) from the MMSC server. It:
*
* <ul>
* <li>Sends a GET request to the MMSC server.
* <li>Retrieves the binary M-Retrieve.conf data and parses it.
* <li>Persists the retrieve multimedia message.
* <li>Determines whether an acknowledgement is required.
* <li>Creates appropriate M-Acknowledge.ind and sends it to MMSC server.
* <li>Notifies the TransactionService about succesful completion.
* </ul>
*/
public class RetrieveTransaction extends Transaction implements Runnable {
private static final String TAG = "RetrieveTransaction";
private static final boolean DEBUG = false;
private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
private final Uri mUri;
private boolean mLocked;
static final String[] PROJECTION = new String[] {
Mms.CONTENT_LOCATION,
Mms.LOCKED,
Mms.MESSAGE_SIZE,
Mms.TRANSACTION_ID
};
// The indexes of the columns which must be consistent with above PROJECTION.
static final int COLUMN_CONTENT_LOCATION = 0;
static final int COLUMN_LOCKED = 1;
static final int MESSAGE_SIZE = 2;
static final int TRANSACTION_ID = 3;
public RetrieveTransaction(Context context, int serviceId,
TransactionSettings connectionSettings, String uri, int phoneId)
throws MmsException {
super(context, serviceId, connectionSettings, phoneId);
if (uri.startsWith("content://")) {
mUri = Uri.parse(uri); // The Uri of the M-Notification.ind
mContentLocation = getContentLocation(context, mUri);
if (LOCAL_LOGV) {
Log.v(TAG, "X-Mms-Content-Location: " + mContentLocation);
Log.v(TAG, "X-Mms-Transaction-Id: " + mId);
}
} else {
throw new IllegalArgumentException(
"Initializing from X-Mms-Content-Location is abandoned!");
}
// Attach the transaction to the instance of RetryScheduler.
attach(RetrySchedulerProxy.getInstance(context, phoneId));
}
private String getContentLocation(Context context, Uri uri)
throws MmsException {
Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
uri, PROJECTION, null, null, null);
mLocked = false;
if (cursor != null) {
try {
if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
// Get the locked flag from the M-Notification.ind so it can be transferred
// to the real message after the download.
mLocked = cursor.getInt(COLUMN_LOCKED) == 1;
mId = cursor.getString(TRANSACTION_ID);
return cursor.getString(COLUMN_CONTENT_LOCATION);
}
} finally {
cursor.close();
}
}
throw new MmsException("Cannot get X-Mms-Content-Location from: " + uri);
}
private long getMmsMessageSize(){
long messagesize = 0;
Cursor cursor = SqliteWrapper.query(mContext, mContext.getContentResolver(), mUri, PROJECTION, null, null, null);
if (cursor != null) {
try {
if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
// Get the message size.
messagesize = cursor.getLong(MESSAGE_SIZE);
}
} finally {
cursor.close();
}
}
return messagesize;
}
/*
* (non-Javadoc)
* @see com.android.mms.transaction.Transaction#process()
*/
@Override
public void process() {
new Thread(this).start();
}
public void run() {
// ===== fixed CR<NEWMS00118858> by luning at 11-10-17 begin=====
boolean lowMemory = isLowMemory();
// ===== fixed CR<NEWMS00118858> by luning at 11-10-17 end=====
try {
// ===== fixed CR<NEWMS00118858> by luning at 11-10-17 begin=====
if (lowMemory) {
DownloadManager.getInstance().markState(mUri,
DownloadManager.STATE_LOW_MEMORY,mPhoneId);
return;
}
// ===== fixed CR<NEWMS00118858> by luning at 11-10-17 end=====
boolean isBeyondLimit = DownloadManager.getInstance().checkPduTotalSizeLimit(getMmsMessageSize());
if (isBeyondLimit) {
DownloadManager.getInstance().markState(mUri, DownloadManager.STATE_UNSTARTED,mPhoneId);
return;
}
// Change the downloading state of the M-Notification.ind.
DownloadManager.getInstance().markState(
mUri, DownloadManager.STATE_DOWNLOADING, mPhoneId);
NotificationInd nInd = (NotificationInd) PduPersister.getPduPersister(mContext).load(mUri);
int phoneId = nInd.getPhoneId();
// TODO: ensure phoneId == mPhoneId, thus remove unnecessary code
// Send GET request to MMSC and retrieve the response data.
byte[] resp = getPdu(mContentLocation);
if(resp != null){
String hexstr = HexDump.toHexString(resp);
Log.d(TAG, "RetrieveTransaction download from URL:"+mContentLocation+", result pdu is :"+hexstr);
}else{
Log.e(TAG, "RetrieveTransaction download from URL:"+mContentLocation+", result pdu is null!!!");
}
// Parse M-Retrieve.conf
RetrieveConf retrieveConf = (RetrieveConf) new PduParser(resp).parse(phoneId);
if (null == retrieveConf) {
throw new MmsException("Invalid M-Retrieve.conf PDU.");
}
Uri msgUri = null;
if (isDuplicateMessage(mContext, retrieveConf)) {
// Mark this transaction as failed to prevent duplicate
// notification to user.
mTransactionState.setState(TransactionState.FAILED);
mTransactionState.setContentUri(mUri);
} else {
// Store M-Retrieve.conf into Inbox
PduPersister persister = PduPersister.getPduPersister(mContext);
msgUri = persister.persist(retrieveConf, Inbox.CONTENT_URI, phoneId);
// Use local time instead of PDU time
ContentValues values = new ContentValues(1);
values.put(Mms.DATE, System.currentTimeMillis() / 1000L);
SqliteWrapper.update(mContext, mContext.getContentResolver(),
msgUri, values, null, null);
// The M-Retrieve.conf has been successfully downloaded.
mTransactionState.setState(TransactionState.SUCCESS);
mTransactionState.setContentUri(msgUri);
Intent receiveMms = new Intent(MessageFolderActivity.mMmsReceiveAction);
mContext.sendBroadcast(receiveMms);
// Remember the location the message was downloaded from.
// Since it's not critical, it won't fail the transaction.
// Copy over the locked flag from the M-Notification.ind in case
// the user locked the message before activating the download.
updateContentLocation(mContext, msgUri, mContentLocation, mLocked);
}
// Delete the corresponding M-Notification.ind.
SqliteWrapper.delete(mContext, mContext.getContentResolver(),
mUri, null, null);
if (msgUri != null) {
// Have to delete messages over limit *after* the delete above. Otherwise,
// it would be counted as part of the total.
Recycler.getMmsRecycler().deleteOldMessagesInSameThreadAsMessage(mContext, msgUri);
}
// Send ACK to the Proxy-Relay to indicate we have fetched the
// MM successfully.
// Don't mark the transaction as failed if we failed to send it.
sendAcknowledgeInd(retrieveConf);
} catch (Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
} finally {
// ===== fixed CR<NEWMS00118858> by luning at 11-10-17 begin=====
if(lowMemory){
mTransactionState.setState(TransactionState.SUCCESS);
mTransactionState.setContentUri(mUri);
Log.e(TAG, "Retrieval lowMemory.");
}
// ===== fixed CR<NEWMS00118858> by luning at 11-10-17 end=====
if (mTransactionState.getState() != TransactionState.SUCCESS) {
mTransactionState.setState(TransactionState.FAILED);
mTransactionState.setContentUri(mUri);
Log.e(TAG, "Retrieval failed.");
}
notifyObservers();
}
}
private static boolean isDuplicateMessage(Context context, RetrieveConf rc) {
byte[] rawMessageId = rc.getMessageId();
if (rawMessageId != null) {
String messageId = new String(rawMessageId);
String selection = "(" + Mms.MESSAGE_ID + " = ? AND "
+ Mms.MESSAGE_TYPE + " = ?)";
String[] selectionArgs = new String[] { messageId,
String.valueOf(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) };
Cursor cursor = SqliteWrapper.query(
context, context.getContentResolver(),
Mms.CONTENT_URI, new String[] { Mms._ID },
selection, selectionArgs, null);
if (cursor != null) {
try {
if (cursor.getCount() > 0) {
// We already received the same message before.
return true;
}
} finally {
cursor.close();
}
}
}
return false;
}
private void sendAcknowledgeInd(RetrieveConf rc) throws MmsException, IOException {
// Send M-Acknowledge.ind to MMSC if required.
// If the Transaction-ID isn't set in the M-Retrieve.conf, it means
// the MMS proxy-relay doesn't require an ACK.
byte[] tranId = rc.getTransactionId();
if (tranId != null) {
// Create M-Acknowledge.ind
AcknowledgeInd acknowledgeInd = new AcknowledgeInd(
PduHeaders.CURRENT_MMS_VERSION, tranId, mPhoneId);
// insert the 'from' address per spec
//lino modify for NEWMS127046 begin
// String lineNumber = MessageUtils.getLocalNumber();
PduHeaders pduHeaders = rc.getPduHeaders();
EncodedStringValue encodedStringValue = null;
if(pduHeaders != null){
EncodedStringValue[] encodedStringValues = pduHeaders.getEncodedStringValues(PduHeaders.TO);
if(encodedStringValues != null && encodedStringValues.length >= 1){
encodedStringValue = encodedStringValues[0];
}
}
// acknowledgeInd.setFrom(new EncodedStringValue(lineNumber));
acknowledgeInd.setFrom(encodedStringValue);
//lino modify for NEWMS127046 end
// Pack M-Acknowledge.ind and send it
if(MmsConfig.getNotifyWapMMSC()) {
sendPdu(new PduComposer(mContext, acknowledgeInd).make(), mContentLocation);
} else {
sendPdu(new PduComposer(mContext, acknowledgeInd).make());
}
}
}
private static void updateContentLocation(Context context, Uri uri,
String contentLocation,
boolean locked) {
ContentValues values = new ContentValues(2);
values.put(Mms.CONTENT_LOCATION, contentLocation);
values.put(Mms.LOCKED, locked); // preserve the state of the M-Notification.ind lock.
SqliteWrapper.update(context, context.getContentResolver(),
uri, values, null, null);
}
@Override
public int getType() {
return RETRIEVE_TRANSACTION;
}
@Override
public void makeFailure() {
makeFailure(mUri);
}
@Override
public boolean isEquivalent(Transaction transaction) {
if ((transaction.getType() == NOTIFICATION_TRANSACTION) ||
(transaction.getType() == RETRIEVE_TRANSACTION)) {
if(mContentLocation.equals(transaction.mContentLocation)) return true;
return false;
}
return super.isEquivalent(transaction);
}
}