forked from upstream-mirrors/quik
266 lines
11 KiB
Java
Executable File
266 lines
11 KiB
Java
Executable File
/*
|
|
* Copyright 2014 Jacob Klinker
|
|
*
|
|
* 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 android.content.BroadcastReceiver;
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.database.Cursor;
|
|
import android.database.DatabaseUtils;
|
|
import android.database.sqlite.SqliteWrapper;
|
|
import android.net.Uri;
|
|
import android.os.AsyncTask;
|
|
import android.provider.Telephony.Mms;
|
|
import android.provider.Telephony.Mms.Inbox;
|
|
import com.android.mms.MmsConfig;
|
|
import com.google.android.mms.ContentType;
|
|
import com.google.android.mms.MmsException;
|
|
import com.google.android.mms.pdu_alt.DeliveryInd;
|
|
import com.google.android.mms.pdu_alt.GenericPdu;
|
|
import com.google.android.mms.pdu_alt.NotificationInd;
|
|
import com.google.android.mms.pdu_alt.PduHeaders;
|
|
import com.google.android.mms.pdu_alt.PduParser;
|
|
import com.google.android.mms.pdu_alt.PduPersister;
|
|
import com.google.android.mms.pdu_alt.ReadOrigInd;
|
|
import timber.log.Timber;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
|
|
import static android.provider.Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION;
|
|
import static android.provider.Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION;
|
|
import static com.google.android.mms.pdu_alt.PduHeaders.*;
|
|
|
|
/**
|
|
* Receives Intent.WAP_PUSH_RECEIVED_ACTION intents and starts the
|
|
* TransactionService by passing the push-data to it.
|
|
*/
|
|
public class PushReceiver extends BroadcastReceiver {
|
|
|
|
static final String[] PROJECTION = new String[]{Mms.CONTENT_LOCATION, Mms.LOCKED};
|
|
|
|
static final int COLUMN_CONTENT_LOCATION = 0;
|
|
|
|
private static Set<String> downloadedUrls = new HashSet<String>();
|
|
private static final ExecutorService PUSH_RECEIVER_EXECUTOR = Executors.newSingleThreadExecutor();
|
|
|
|
private class ReceivePushTask extends AsyncTask<Intent, Void, Void> {
|
|
private Context mContext;
|
|
private PendingResult pendingResult;
|
|
|
|
private ReceivePushTask(Context context, PendingResult pendingResult) {
|
|
mContext = context;
|
|
this.pendingResult = pendingResult;
|
|
}
|
|
|
|
@Override
|
|
protected Void doInBackground(Intent... intents) {
|
|
Timber.v("receiving a new mms message");
|
|
Intent intent = intents[0];
|
|
|
|
// Get raw PDU push-data from the message and parse it
|
|
byte[] pushData = intent.getByteArrayExtra("data");
|
|
PduParser parser = new PduParser(pushData);
|
|
GenericPdu pdu = parser.parse();
|
|
|
|
if (pdu == null) {
|
|
Timber.e("Invalid PUSH data");
|
|
return null;
|
|
}
|
|
|
|
PduPersister p = PduPersister.getPduPersister(mContext);
|
|
ContentResolver cr = mContext.getContentResolver();
|
|
int type = pdu.getMessageType();
|
|
long threadId;
|
|
|
|
try {
|
|
switch (type) {
|
|
case MESSAGE_TYPE_DELIVERY_IND:
|
|
case MESSAGE_TYPE_READ_ORIG_IND: {
|
|
threadId = findThreadId(mContext, pdu, type);
|
|
if (threadId == -1) {
|
|
// The associated SendReq isn't found, therefore skip
|
|
// processing this PDU.
|
|
break;
|
|
}
|
|
|
|
Uri uri = p.persist(pdu, Uri.parse("content://mms/inbox"), true,
|
|
true, null);
|
|
// Update thread ID for ReadOrigInd & DeliveryInd.
|
|
ContentValues values = new ContentValues(1);
|
|
values.put(Mms.THREAD_ID, threadId);
|
|
SqliteWrapper.update(mContext, cr, uri, values, null, null);
|
|
break;
|
|
}
|
|
case MESSAGE_TYPE_NOTIFICATION_IND: {
|
|
NotificationInd nInd = (NotificationInd) pdu;
|
|
|
|
if (MmsConfig.getTransIdEnabled()) {
|
|
byte[] contentLocation = nInd.getContentLocation();
|
|
if ('=' == contentLocation[contentLocation.length - 1]) {
|
|
byte[] transactionId = nInd.getTransactionId();
|
|
byte[] contentLocationWithId = new byte[contentLocation.length
|
|
+ transactionId.length];
|
|
System.arraycopy(contentLocation, 0, contentLocationWithId,
|
|
0, contentLocation.length);
|
|
System.arraycopy(transactionId, 0, contentLocationWithId,
|
|
contentLocation.length, transactionId.length);
|
|
nInd.setContentLocation(contentLocationWithId);
|
|
}
|
|
}
|
|
|
|
if (!isDuplicateNotification(mContext, nInd)) {
|
|
// Save the pdu. If we can start downloading the real pdu immediately,
|
|
// don't allow persist() to create a thread for the notificationInd
|
|
// because it causes UI jank.
|
|
Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, true, null);
|
|
|
|
String location = getContentLocation(mContext, uri);
|
|
if (downloadedUrls.contains(location)) {
|
|
Timber.v("already added this download, don't download again");
|
|
return null;
|
|
} else {
|
|
downloadedUrls.add(location);
|
|
}
|
|
|
|
DownloadManager.getInstance().downloadMultimediaMessage(mContext, location, uri, true);
|
|
} else {
|
|
Timber.v("Skip downloading duplicate message: " + new String(nInd.getContentLocation()));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
Timber.e("Received unrecognized PDU.");
|
|
}
|
|
} catch (MmsException e) {
|
|
Timber.e(e, "Failed to save the data from PUSH: type=" + type);
|
|
} catch (RuntimeException e) {
|
|
Timber.e(e, "Unexpected RuntimeException.");
|
|
}
|
|
|
|
Timber.v("PUSH Intent processed.");
|
|
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Void aVoid) {
|
|
pendingResult.finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
Timber.v(intent.getAction() + " " + intent.getType());
|
|
if ((intent.getAction().equals(WAP_PUSH_DELIVER_ACTION) || intent.getAction().equals(WAP_PUSH_RECEIVED_ACTION))
|
|
&& ContentType.MMS_MESSAGE.equals(intent.getType())) {
|
|
Timber.v("Received PUSH Intent: " + intent);
|
|
|
|
MmsConfig.init(context);
|
|
new ReceivePushTask(context, goAsync()).executeOnExecutor(PUSH_RECEIVER_EXECUTOR, intent);
|
|
|
|
Timber.v(context.getPackageName() + " received and aborted");
|
|
}
|
|
}
|
|
|
|
public static String getContentLocation(Context context, Uri uri) throws MmsException {
|
|
Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
|
|
uri, PROJECTION, null, null, null);
|
|
|
|
if (cursor != null) {
|
|
try {
|
|
if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
|
|
String location = cursor.getString(COLUMN_CONTENT_LOCATION);
|
|
cursor.close();
|
|
return location;
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
throw new MmsException("Cannot get X-Mms-Content-Location from: " + uri);
|
|
}
|
|
|
|
private static long findThreadId(Context context, GenericPdu pdu, int type) {
|
|
String messageId;
|
|
|
|
if (type == MESSAGE_TYPE_DELIVERY_IND) {
|
|
messageId = new String(((DeliveryInd) pdu).getMessageId());
|
|
} else {
|
|
messageId = new String(((ReadOrigInd) pdu).getMessageId());
|
|
}
|
|
|
|
StringBuilder sb = new StringBuilder('(');
|
|
sb.append(Mms.MESSAGE_ID);
|
|
sb.append('=');
|
|
sb.append(DatabaseUtils.sqlEscapeString(messageId));
|
|
sb.append(" AND ");
|
|
sb.append(Mms.MESSAGE_TYPE);
|
|
sb.append('=');
|
|
sb.append(PduHeaders.MESSAGE_TYPE_SEND_REQ);
|
|
// TODO ContentResolver.query() appends closing ')' to the selection argument
|
|
// sb.append(')');
|
|
|
|
Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
|
|
Mms.CONTENT_URI, new String[]{Mms.THREAD_ID},
|
|
sb.toString(), null, null);
|
|
if (cursor != null) {
|
|
try {
|
|
if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
|
|
long id = cursor.getLong(0);
|
|
cursor.close();
|
|
return id;
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private static boolean isDuplicateNotification(Context context, NotificationInd nInd) {
|
|
byte[] rawLocation = nInd.getContentLocation();
|
|
if (rawLocation != null) {
|
|
String location = new String(rawLocation);
|
|
String selection = Mms.CONTENT_LOCATION + " = ?";
|
|
String[] selectionArgs = new String[]{location};
|
|
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 notification before.
|
|
cursor.close();
|
|
//return true;
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|