493 lines
20 KiB
Java
493 lines
20 KiB
Java
/*
|
|
* 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.internal.telephony;
|
|
|
|
import static android.os.PowerWhitelistManager.REASON_EVENT_MMS;
|
|
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
|
|
|
|
import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.app.Activity;
|
|
import android.app.AppOpsManager;
|
|
import android.app.BroadcastOptions;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.PowerWhitelistManager;
|
|
import android.os.RemoteException;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.provider.Telephony.Sms.Intents;
|
|
import android.telephony.SmsManager;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.text.TextUtils;
|
|
|
|
import com.android.internal.telephony.uicc.IccUtils;
|
|
import com.android.internal.telephony.util.TelephonyUtils;
|
|
import com.android.telephony.Rlog;
|
|
|
|
import com.google.android.mms.pdu.GenericPdu;
|
|
import com.google.android.mms.pdu.NotificationInd;
|
|
import com.google.android.mms.pdu.PduParser;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* WAP push handler class.
|
|
*
|
|
* @hide
|
|
*/
|
|
public class WapPushOverSms implements ServiceConnection {
|
|
private static final String TAG = "WAP PUSH";
|
|
private static final boolean DBG = false;
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
private final Context mContext;
|
|
PowerWhitelistManager mPowerWhitelistManager;
|
|
|
|
private String mWapPushManagerPackage;
|
|
|
|
/** Assigned from ServiceConnection callback on main threaad. */
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
private volatile IWapPushManager mWapPushManager;
|
|
|
|
private void bindWapPushManagerService(Context context) {
|
|
Intent intent = new Intent(IWapPushManager.class.getName());
|
|
ComponentName comp = resolveSystemService(context.getPackageManager(), intent);
|
|
intent.setComponent(comp);
|
|
if (comp == null || !context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
|
|
Rlog.e(TAG, "bindService() for wappush manager failed");
|
|
} else {
|
|
synchronized (this) {
|
|
mWapPushManagerPackage = comp.getPackageName();
|
|
}
|
|
if (DBG) Rlog.v(TAG, "bindService() for wappush manager succeeded");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Special function for use by the system to resolve service
|
|
* intents to system apps. Throws an exception if there are
|
|
* multiple potential matches to the Intent. Returns null if
|
|
* there are no matches.
|
|
*/
|
|
private static @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
|
|
@NonNull Intent intent) {
|
|
List<ResolveInfo> results = pm.queryIntentServices(
|
|
intent, PackageManager.MATCH_SYSTEM_ONLY);
|
|
if (results == null) {
|
|
return null;
|
|
}
|
|
ComponentName comp = null;
|
|
for (int i = 0; i < results.size(); i++) {
|
|
ResolveInfo ri = results.get(i);
|
|
ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
|
|
ri.serviceInfo.name);
|
|
if (comp != null) {
|
|
throw new IllegalStateException("Multiple system services handle " + intent
|
|
+ ": " + comp + ", " + foundComp);
|
|
}
|
|
comp = foundComp;
|
|
}
|
|
return comp;
|
|
}
|
|
|
|
@Override
|
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
mWapPushManager = IWapPushManager.Stub.asInterface(service);
|
|
if (DBG) Rlog.v(TAG, "wappush manager connected to " + hashCode());
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDisconnected(ComponentName name) {
|
|
mWapPushManager = null;
|
|
if (DBG) Rlog.v(TAG, "wappush manager disconnected.");
|
|
}
|
|
|
|
public WapPushOverSms(Context context) {
|
|
mContext = context;
|
|
mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
|
|
|
|
UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
|
|
|
|
bindWapPushManagerService(mContext);
|
|
}
|
|
|
|
public void dispose() {
|
|
if (mWapPushManager != null) {
|
|
if (DBG) Rlog.v(TAG, "dispose: unbind wappush manager");
|
|
mContext.unbindService(this);
|
|
} else {
|
|
Rlog.e(TAG, "dispose: not bound to a wappush manager");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decodes the wap push pdu. The decoded result is wrapped inside the {@link DecodedResult}
|
|
* object. The caller of this method should check {@link DecodedResult#statusCode} for the
|
|
* decoding status. It can have the following values.
|
|
*
|
|
* Activity.RESULT_OK - the wap push pdu is successfully decoded and should be further processed
|
|
* Intents.RESULT_SMS_HANDLED - the wap push pdu should be ignored.
|
|
* Intents.RESULT_SMS_GENERIC_ERROR - the pdu is invalid.
|
|
*/
|
|
private DecodedResult decodeWapPdu(byte[] pdu, InboundSmsHandler handler) {
|
|
DecodedResult result = new DecodedResult();
|
|
if (DBG) Rlog.d(TAG, "Rx: " + IccUtils.bytesToHexString(pdu));
|
|
|
|
try {
|
|
int index = 0;
|
|
int transactionId = pdu[index++] & 0xFF;
|
|
int pduType = pdu[index++] & 0xFF;
|
|
|
|
// Should we "abort" if no subId for now just no supplying extra param below
|
|
int phoneId = handler.getPhone().getPhoneId();
|
|
|
|
if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
|
|
(pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
|
|
index = mContext.getResources().getInteger(
|
|
com.android.internal.R.integer.config_valid_wappush_index);
|
|
if (index != -1) {
|
|
transactionId = pdu[index++] & 0xff;
|
|
pduType = pdu[index++] & 0xff;
|
|
if (DBG)
|
|
Rlog.d(TAG, "index = " + index + " PDU Type = " + pduType +
|
|
" transactionID = " + transactionId);
|
|
|
|
// recheck wap push pduType
|
|
if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH)
|
|
&& (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
|
|
if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
|
|
result.statusCode = Intents.RESULT_SMS_HANDLED;
|
|
return result;
|
|
}
|
|
} else {
|
|
if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
|
|
result.statusCode = Intents.RESULT_SMS_HANDLED;
|
|
return result;
|
|
}
|
|
}
|
|
WspTypeDecoder pduDecoder =
|
|
TelephonyComponentFactory.getInstance().inject(WspTypeDecoder.class.getName())
|
|
.makeWspTypeDecoder(pdu);
|
|
|
|
/**
|
|
* Parse HeaderLen(unsigned integer).
|
|
* From wap-230-wsp-20010705-a section 8.1.2
|
|
* The maximum size of a uintvar is 32 bits.
|
|
* So it will be encoded in no more than 5 octets.
|
|
*/
|
|
if (pduDecoder.decodeUintvarInteger(index) == false) {
|
|
if (DBG) Rlog.w(TAG, "Received PDU. Header Length error.");
|
|
result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
|
|
return result;
|
|
}
|
|
int headerLength = (int) pduDecoder.getValue32();
|
|
index += pduDecoder.getDecodedDataLength();
|
|
|
|
int headerStartIndex = index;
|
|
|
|
/**
|
|
* Parse Content-Type.
|
|
* From wap-230-wsp-20010705-a section 8.4.2.24
|
|
*
|
|
* Content-type-value = Constrained-media | Content-general-form
|
|
* Content-general-form = Value-length Media-type
|
|
* Media-type = (Well-known-media | Extension-Media) *(Parameter)
|
|
* Value-length = Short-length | (Length-quote Length)
|
|
* Short-length = <Any octet 0-30> (octet <= WAP_PDU_SHORT_LENGTH_MAX)
|
|
* Length-quote = <Octet 31> (WAP_PDU_LENGTH_QUOTE)
|
|
* Length = Uintvar-integer
|
|
*/
|
|
if (pduDecoder.decodeContentType(index) == false) {
|
|
if (DBG) Rlog.w(TAG, "Received PDU. Header Content-Type error.");
|
|
result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
|
|
return result;
|
|
}
|
|
|
|
String mimeType = pduDecoder.getValueString();
|
|
long binaryContentType = pduDecoder.getValue32();
|
|
index += pduDecoder.getDecodedDataLength();
|
|
|
|
byte[] header = new byte[headerLength];
|
|
System.arraycopy(pdu, headerStartIndex, header, 0, header.length);
|
|
|
|
byte[] intentData;
|
|
|
|
if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) {
|
|
intentData = pdu;
|
|
} else {
|
|
int dataIndex = headerStartIndex + headerLength;
|
|
intentData = new byte[pdu.length - dataIndex];
|
|
System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
|
|
}
|
|
|
|
int subId = SubscriptionManager.getSubscriptionId(phoneId);
|
|
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
|
|
subId = SmsManager.getDefaultSmsSubscriptionId();
|
|
}
|
|
|
|
// Continue if PDU parsing fails: the default messaging app may successfully parse the
|
|
// same PDU.
|
|
GenericPdu parsedPdu = null;
|
|
try {
|
|
parsedPdu = new PduParser(intentData, shouldParseContentDisposition(subId)).parse();
|
|
} catch (Exception e) {
|
|
Rlog.e(TAG, "Unable to parse PDU: " + e.toString());
|
|
}
|
|
|
|
if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
|
|
final NotificationInd nInd = (NotificationInd) parsedPdu;
|
|
// save the WAP push message size so that if a download request is made for it
|
|
// while on a satellite connection we can check if the size is under the threshold
|
|
WapPushCache.putWapMessageSize(
|
|
nInd.getContentLocation(),
|
|
nInd.getTransactionId(),
|
|
nInd.getMessageSize()
|
|
);
|
|
if (nInd.getFrom() != null
|
|
&& BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) {
|
|
result.statusCode = Intents.RESULT_SMS_HANDLED;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Seek for application ID field in WSP header.
|
|
* If application ID is found, WapPushManager substitute the message
|
|
* processing. Since WapPushManager is optional module, if WapPushManager
|
|
* is not found, legacy message processing will be continued.
|
|
*/
|
|
if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
|
|
index = (int) pduDecoder.getValue32();
|
|
pduDecoder.decodeXWapApplicationId(index);
|
|
String wapAppId = pduDecoder.getValueString();
|
|
if (wapAppId == null) {
|
|
wapAppId = Integer.toString((int) pduDecoder.getValue32());
|
|
}
|
|
result.wapAppId = wapAppId;
|
|
String contentType = ((mimeType == null) ?
|
|
Long.toString(binaryContentType) : mimeType);
|
|
result.contentType = contentType;
|
|
if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType);
|
|
}
|
|
|
|
result.subId = subId;
|
|
result.phoneId = phoneId;
|
|
result.parsedPdu = parsedPdu;
|
|
result.mimeType = mimeType;
|
|
result.transactionId = transactionId;
|
|
result.pduType = pduType;
|
|
result.header = header;
|
|
result.intentData = intentData;
|
|
result.contentTypeParameters = pduDecoder.getContentParameters();
|
|
result.statusCode = Activity.RESULT_OK;
|
|
} catch (ArrayIndexOutOfBoundsException aie) {
|
|
// 0-byte WAP PDU or other unexpected WAP PDU contents can easily throw this;
|
|
// log exception string without stack trace and return false.
|
|
Rlog.e(TAG, "ignoring dispatchWapPdu() array index exception: " + aie);
|
|
result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Dispatches inbound messages that are in the WAP PDU format. See
|
|
* wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
|
|
*
|
|
* @param pdu The WAP PDU, made up of one or more SMS PDUs
|
|
* @param address The originating address
|
|
* @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
|
|
* {@link Activity#RESULT_OK} if the message has been broadcast
|
|
* to applications
|
|
*/
|
|
public int dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver,
|
|
InboundSmsHandler handler, String address, int subId, long messageId) {
|
|
DecodedResult result = decodeWapPdu(pdu, handler);
|
|
if (result.statusCode != Activity.RESULT_OK) {
|
|
return result.statusCode;
|
|
}
|
|
|
|
/**
|
|
* If the pdu has application ID, WapPushManager substitute the message
|
|
* processing. Since WapPushManager is optional module, if WapPushManager
|
|
* is not found, legacy message processing will be continued.
|
|
*/
|
|
if (result.wapAppId != null) {
|
|
try {
|
|
boolean processFurther = true;
|
|
IWapPushManager wapPushMan = mWapPushManager;
|
|
|
|
if (wapPushMan == null) {
|
|
if (DBG) Rlog.w(TAG, "wap push manager not found!");
|
|
} else {
|
|
synchronized (this) {
|
|
mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
|
|
mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS,
|
|
REASON_EVENT_MMS, "mms-mgr");
|
|
}
|
|
|
|
Intent intent = new Intent();
|
|
intent.putExtra("transactionId", result.transactionId);
|
|
intent.putExtra("pduType", result.pduType);
|
|
intent.putExtra("header", result.header);
|
|
intent.putExtra("data", result.intentData);
|
|
intent.putExtra("contentTypeParameters", result.contentTypeParameters);
|
|
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
|
|
if (!TextUtils.isEmpty(address)) {
|
|
intent.putExtra("address", address);
|
|
}
|
|
|
|
int procRet = wapPushMan.processMessage(
|
|
result.wapAppId, result.contentType, intent);
|
|
if (DBG) Rlog.v(TAG, "procRet:" + procRet);
|
|
if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
|
|
&& (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
|
|
processFurther = false;
|
|
}
|
|
}
|
|
if (!processFurther) {
|
|
return Intents.RESULT_SMS_HANDLED;
|
|
}
|
|
} catch (RemoteException e) {
|
|
if (DBG) Rlog.w(TAG, "remote func failed...");
|
|
}
|
|
}
|
|
if (DBG) Rlog.v(TAG, "fall back to existing handler");
|
|
|
|
if (result.mimeType == null) {
|
|
if (DBG) Rlog.w(TAG, "Header Content-Type error.");
|
|
return Intents.RESULT_SMS_GENERIC_ERROR;
|
|
}
|
|
|
|
Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
|
|
intent.setType(result.mimeType);
|
|
intent.putExtra("transactionId", result.transactionId);
|
|
intent.putExtra("pduType", result.pduType);
|
|
intent.putExtra("header", result.header);
|
|
intent.putExtra("data", result.intentData);
|
|
intent.putExtra("contentTypeParameters", result.contentTypeParameters);
|
|
if (!TextUtils.isEmpty(address)) {
|
|
intent.putExtra("address", address);
|
|
}
|
|
if (messageId != 0L) {
|
|
intent.putExtra("messageId", messageId);
|
|
}
|
|
|
|
// Direct the intent to only the default MMS app. If we can't find a default MMS app
|
|
// then sent it to all broadcast receivers.
|
|
UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
|
|
ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
|
|
true, userHandle);
|
|
|
|
Bundle options = null;
|
|
if (componentName != null) {
|
|
// Deliver MMS message only to this receiver
|
|
intent.setComponent(componentName);
|
|
if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
|
|
" " + componentName.getClassName());
|
|
long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
|
|
componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS,
|
|
REASON_EVENT_MMS, "mms-app");
|
|
BroadcastOptions bopts = BroadcastOptions.makeBasic();
|
|
bopts.setTemporaryAppAllowlist(duration,
|
|
TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
|
|
REASON_EVENT_MMS,
|
|
"");
|
|
options = bopts.toBundle();
|
|
}
|
|
|
|
if (userHandle == null) {
|
|
userHandle = UserHandle.SYSTEM;
|
|
}
|
|
handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
|
|
getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
|
|
userHandle, subId);
|
|
return Activity.RESULT_OK;
|
|
}
|
|
|
|
/**
|
|
* Check whether the pdu is a MMS WAP push pdu that should be dispatched to the SMS app.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public boolean isWapPushForMms(byte[] pdu, InboundSmsHandler handler) {
|
|
DecodedResult result = decodeWapPdu(pdu, handler);
|
|
return result.statusCode == Activity.RESULT_OK
|
|
&& WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(result.mimeType);
|
|
}
|
|
|
|
private static boolean shouldParseContentDisposition(int subId) {
|
|
return SmsManager
|
|
.getSmsManagerForSubscriptionId(subId)
|
|
.getCarrierConfigValues()
|
|
.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
|
|
}
|
|
|
|
public static String getPermissionForType(String mimeType) {
|
|
String permission;
|
|
if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
|
|
permission = android.Manifest.permission.RECEIVE_MMS;
|
|
} else {
|
|
permission = android.Manifest.permission.RECEIVE_WAP_PUSH;
|
|
}
|
|
return permission;
|
|
}
|
|
|
|
/**
|
|
* Return a appOps String for the given MIME type.
|
|
* @param mimeType MIME type of the Intent
|
|
* @return The appOps String
|
|
*/
|
|
public static String getAppOpsStringPermissionForIntent(String mimeType) {
|
|
String appOp;
|
|
if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
|
|
appOp = AppOpsManager.OPSTR_RECEIVE_MMS;
|
|
} else {
|
|
appOp = AppOpsManager.OPSTR_RECEIVE_WAP_PUSH;
|
|
}
|
|
return appOp;
|
|
}
|
|
|
|
/**
|
|
* Place holder for decoded Wap pdu data.
|
|
*/
|
|
private final class DecodedResult {
|
|
String mimeType;
|
|
String contentType;
|
|
int transactionId;
|
|
int pduType;
|
|
int phoneId;
|
|
int subId;
|
|
byte[] header;
|
|
String wapAppId;
|
|
byte[] intentData;
|
|
HashMap<String, String> contentTypeParameters;
|
|
GenericPdu parsedPdu;
|
|
int statusCode;
|
|
}
|
|
}
|