/* * Copyright (C) 2013 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.REASON_EVENT_SMS; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.provider.Telephony.Sms.Intents.RESULT_SMS_DATABASE_ERROR; import static android.provider.Telephony.Sms.Intents.RESULT_SMS_DISPATCH_FAILURE; import static android.provider.Telephony.Sms.Intents.RESULT_SMS_INVALID_URI; import static android.provider.Telephony.Sms.Intents.RESULT_SMS_NULL_MESSAGE; import static android.provider.Telephony.Sms.Intents.RESULT_SMS_NULL_PDU; import static android.service.carrier.CarrierMessagingService.RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.SQLException; import android.net.Uri; import android.os.AsyncResult; import android.os.Build; import android.os.Bundle; import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.PowerWhitelistManager; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.service.carrier.CarrierMessagingService; import android.telephony.SmsMessage; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.LocalLog; import android.util.Pair; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.SmsConstants.MessageClass; import com.android.internal.telephony.analytics.TelephonyAnalytics; import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.telephony.util.NotificationChannelController; import com.android.internal.telephony.util.TelephonyUtils; import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.telephony.Rlog; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.ListIterator; import java.util.Map; /** * This class broadcasts incoming SMS messages to interested apps after storing them in the * SmsProvider "raw" table and ACKing them to the SMSC. After each message has been broadcast, its * parts are removed from the raw table. If the device crashes after ACKing but before the broadcast * completes, the pending messages will be rebroadcast on the next boot. * *

The state machine starts in {@link IdleState} state. When we receive a new SMS from the radio, * the wakelock is acquired, then transition to {@link DeliveringState} state, where the message is * saved to the raw table, then acknowledged to the modem which in turn acknowledges it to the SMSC. * *

After saving the SMS, if the message is complete (either single-part or the final segment of a * multi-part SMS), we broadcast the completed PDUs as an ordered broadcast, then transition to * {@link WaitingState} state to wait for the broadcast to complete. When the local * {@link BroadcastReceiver} is called with the result, it sends {@link #EVENT_BROADCAST_COMPLETE} * to the state machine, causing us to either broadcast the next pending message (if one has arrived * while waiting for the broadcast to complete), or to transition back to the halted state after all * messages are processed. Then the wakelock is released and we wait for the next SMS. */ public abstract class InboundSmsHandler extends StateMachine { protected static final boolean DBG = true; protected static final boolean VDBG = false; // STOPSHIP if true, logs user data public static final int PDU_COLUMN = 0; public static final int SEQUENCE_COLUMN = 1; public static final int DESTINATION_PORT_COLUMN = 2; public static final int DATE_COLUMN = 3; public static final int REFERENCE_NUMBER_COLUMN = 4; public static final int COUNT_COLUMN = 5; public static final int ADDRESS_COLUMN = 6; public static final int ID_COLUMN = 7; public static final int MESSAGE_BODY_COLUMN = 8; public static final int DISPLAY_ADDRESS_COLUMN = 9; public static final int DELETED_FLAG_COLUMN = 10; public static final int SUBID_COLUMN = 11; /** Query projection for checking for duplicate message segments. */ private static final String[] PDU_DELETED_FLAG_PROJECTION = { "pdu", "deleted" }; /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */ private static final Map PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING = Map.of( PDU_COLUMN, 0, DELETED_FLAG_COLUMN, 1); /** Query projection for combining concatenated message segments. */ private static final String[] PDU_SEQUENCE_PORT_PROJECTION = { "pdu", "sequence", "destination_port", "display_originating_addr", "date" }; /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */ private static final Map PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING = Map.of( PDU_COLUMN, 0, SEQUENCE_COLUMN, 1, DESTINATION_PORT_COLUMN, 2, DISPLAY_ADDRESS_COLUMN, 3, DATE_COLUMN, 4); public static final String SELECT_BY_ID = "_id=?"; /** New SMS received as an AsyncResult. */ public static final int EVENT_NEW_SMS = 1; /** Message type containing a {@link InboundSmsTracker} ready to broadcast to listeners. */ public static final int EVENT_BROADCAST_SMS = 2; /** Message from resultReceiver notifying {@link WaitingState} of a completed broadcast. */ public static final int EVENT_BROADCAST_COMPLETE = 3; /** Sent on exit from {@link WaitingState} to return to idle after sending all broadcasts. */ private static final int EVENT_RETURN_TO_IDLE = 4; /** Release wakelock after {@link #mWakeLockTimeout} when returning to idle state. */ private static final int EVENT_RELEASE_WAKELOCK = 5; /** Sent by {@link SmsBroadcastUndelivered} after cleaning the raw table. */ public static final int EVENT_START_ACCEPTING_SMS = 6; /** New SMS received as an AsyncResult. */ public static final int EVENT_INJECT_SMS = 7; /** Update the sms tracker */ public static final int EVENT_UPDATE_TRACKER = 8; /** BroadcastReceiver timed out waiting for an intent */ public static final int EVENT_RECEIVER_TIMEOUT = 9; /** Wakelock release delay when returning to idle state. */ private static final int WAKELOCK_TIMEOUT = 3000; /** Received SMS was not injected. */ public static final int SOURCE_NOT_INJECTED = 0; /** Received SMS was received over IMS and injected. */ public static final int SOURCE_INJECTED_FROM_IMS = 1; /** Received SMS was injected from source different than IMS. */ public static final int SOURCE_INJECTED_FROM_UNKNOWN = 2; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"SOURCE_"}, value = { SOURCE_NOT_INJECTED, SOURCE_INJECTED_FROM_IMS, SOURCE_INJECTED_FROM_UNKNOWN }) public @interface SmsSource {} // The notitfication tag used when showing a notification. The combination of notification tag // and notification id should be unique within the phone app. @VisibleForTesting public static final String NOTIFICATION_TAG = "InboundSmsHandler"; @VisibleForTesting public static final int NOTIFICATION_ID_NEW_MESSAGE = 1; /** URI for raw table of SMS provider. */ protected static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); protected static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected final Context mContext; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final ContentResolver mResolver; /** Special handler for WAP push messages. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final WapPushOverSms mWapPush; /** Wake lock to ensure device stays awake while dispatching the SMS intents. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final PowerManager.WakeLock mWakeLock; /** DefaultState throws an exception or logs an error for unhandled message types. */ private final DefaultState mDefaultState = new DefaultState(); /** Startup state. Waiting for {@link SmsBroadcastUndelivered} to complete. */ private final StartupState mStartupState = new StartupState(); /** Idle state. Waiting for messages to process. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final IdleState mIdleState = new IdleState(); /** Delivering state. Saves the PDU in the raw table and acknowledges to SMSC. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final DeliveringState mDeliveringState = new DeliveringState(); /** Broadcasting state. Waits for current broadcast to complete before delivering next. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final WaitingState mWaitingState = new WaitingState(); /** Helper class to check whether storage is available for incoming messages. */ protected SmsStorageMonitor mStorageMonitor; private final boolean mSmsReceiveDisabled; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected Phone mPhone; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private UserManager mUserManager; protected TelephonyMetrics mMetrics = TelephonyMetrics.getInstance(); private LocalLog mLocalLog = new LocalLog(64); private LocalLog mCarrierServiceLocalLog = new LocalLog(8); PowerWhitelistManager mPowerWhitelistManager; protected CellBroadcastServiceManager mCellBroadcastServiceManager; // Delete permanently from raw table private final int DELETE_PERMANENTLY = 1; // Only mark deleted, but keep in db for message de-duping private final int MARK_DELETED = 2; private static String ACTION_OPEN_SMS_APP = "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP"; /** Timeout for releasing wakelock */ private int mWakeLockTimeout; private List mSmsFilters; /** * Create a new SMS broadcast helper. * @param name the class name for logging * @param context the context of the phone app * @param storageMonitor the SmsStorageMonitor to check for storage availability */ protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor, Phone phone, Looper looper) { super(name, looper); mContext = context; mStorageMonitor = storageMonitor; mPhone = phone; mResolver = context.getContentResolver(); mWapPush = new WapPushOverSms(context); boolean smsCapable = mContext.getResources().getBoolean( com.android.internal.R.bool.config_sms_capable); mSmsReceiveDisabled = !TelephonyManager.from(mContext).getSmsReceiveCapableForPhone( mPhone.getPhoneId(), smsCapable); PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); mWakeLock.acquire(); // wake lock released after we enter idle state mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mPowerWhitelistManager = (PowerWhitelistManager) mContext.getSystemService(Context.POWER_WHITELIST_MANAGER); mCellBroadcastServiceManager = new CellBroadcastServiceManager(context, phone); mSmsFilters = createDefaultSmsFilters(); addState(mDefaultState); addState(mStartupState, mDefaultState); addState(mIdleState, mDefaultState); addState(mDeliveringState, mDefaultState); addState(mWaitingState, mDeliveringState); setInitialState(mStartupState); if (DBG) log("created InboundSmsHandler"); } /** * Tell the state machine to quit after processing all messages. */ public void dispose() { quit(); } /** * Dispose of the WAP push object and release the wakelock. */ @Override protected void onQuitting() { mWapPush.dispose(); mCellBroadcastServiceManager.disable(); while (mWakeLock.isHeld()) { mWakeLock.release(); } } // CAF_MSIM Is this used anywhere ? if not remove it @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Phone getPhone() { return mPhone; } @Override protected String getWhatToString(int what) { String whatString; switch (what) { case EVENT_NEW_SMS: whatString = "EVENT_NEW_SMS"; break; case EVENT_BROADCAST_SMS: whatString = "EVENT_BROADCAST_SMS"; break; case EVENT_BROADCAST_COMPLETE: whatString = "EVENT_BROADCAST_COMPLETE"; break; case EVENT_RETURN_TO_IDLE: whatString = "EVENT_RETURN_TO_IDLE"; break; case EVENT_RELEASE_WAKELOCK: whatString = "EVENT_RELEASE_WAKELOCK"; break; case EVENT_START_ACCEPTING_SMS: whatString = "EVENT_START_ACCEPTING_SMS"; break; case EVENT_INJECT_SMS: whatString = "EVENT_INJECT_SMS"; break; case EVENT_UPDATE_TRACKER: whatString = "EVENT_UPDATE_TRACKER"; break; case EVENT_RECEIVER_TIMEOUT: whatString = "EVENT_RECEIVER_TIMEOUT"; break; default: whatString = "UNKNOWN EVENT " + what; } return whatString; } /** * This parent state throws an exception (for debug builds) or prints an error for unhandled * message types. */ private class DefaultState extends State { @Override public boolean processMessage(Message msg) { switch (msg.what) { default: { String errorText = "processMessage: unhandled message type " + getWhatToString(msg.what) + " currState=" + getCurrentState().getName(); if (TelephonyUtils.IS_DEBUGGABLE) { loge("---- Dumping InboundSmsHandler ----"); loge("Total records=" + getLogRecCount()); for (int i = Math.max(getLogRecSize() - 20, 0); i < getLogRecSize(); i++) { // getLogRec(i).toString() will call the overridden getWhatToString // method which has more information loge("Rec[%d]: %s\n" + i + getLogRec(i).toString()); } loge("---- Dumped InboundSmsHandler ----"); throw new RuntimeException(errorText); } else { loge(errorText); } break; } } return HANDLED; } } /** * The Startup state waits for {@link SmsBroadcastUndelivered} to process the raw table and * notify the state machine to broadcast any complete PDUs that might not have been broadcast. */ private class StartupState extends State { @Override public void enter() { if (DBG) log("StartupState.enter: entering StartupState"); // Set wakelock timeout to 0 during startup, this will ensure that the wakelock is not // held if there are no pending messages to be handled. setWakeLockTimeout(0); } @Override public boolean processMessage(Message msg) { log("StartupState.processMessage: processing " + getWhatToString(msg.what)); switch (msg.what) { case EVENT_NEW_SMS: case EVENT_INJECT_SMS: case EVENT_BROADCAST_SMS: deferMessage(msg); return HANDLED; case EVENT_START_ACCEPTING_SMS: transitionTo(mIdleState); return HANDLED; case EVENT_BROADCAST_COMPLETE: case EVENT_RETURN_TO_IDLE: case EVENT_RELEASE_WAKELOCK: default: // let DefaultState handle these unexpected message types return NOT_HANDLED; } } } /** * In the idle state the wakelock is released until a new SM arrives, then we transition * to Delivering mode to handle it, acquiring the wakelock on exit. */ private class IdleState extends State { @Override public void enter() { if (DBG) log("IdleState.enter: entering IdleState"); sendMessageDelayed(EVENT_RELEASE_WAKELOCK, getWakeLockTimeout()); } @Override public void exit() { mWakeLock.acquire(); if (DBG) log("IdleState.exit: acquired wakelock, leaving IdleState"); } @Override public boolean processMessage(Message msg) { if (DBG) log("IdleState.processMessage: processing " + getWhatToString(msg.what)); switch (msg.what) { case EVENT_NEW_SMS: case EVENT_INJECT_SMS: case EVENT_BROADCAST_SMS: deferMessage(msg); transitionTo(mDeliveringState); return HANDLED; case EVENT_RELEASE_WAKELOCK: mWakeLock.release(); if (DBG) { if (mWakeLock.isHeld()) { // this is okay as long as we call release() for every acquire() log("IdleState.processMessage: EVENT_RELEASE_WAKELOCK: mWakeLock is " + "still held after release"); } else { log("IdleState.processMessage: EVENT_RELEASE_WAKELOCK: mWakeLock " + "released"); } } return HANDLED; case EVENT_RETURN_TO_IDLE: // already in idle state; ignore return HANDLED; case EVENT_BROADCAST_COMPLETE: case EVENT_START_ACCEPTING_SMS: default: // let DefaultState handle these unexpected message types return NOT_HANDLED; } } } /** * In the delivering state, the inbound SMS is processed and stored in the raw table. * The message is acknowledged before we exit this state. If there is a message to broadcast, * transition to {@link WaitingState} state to send the ordered broadcast and wait for the * results. When all messages have been processed, the halting state will release the wakelock. */ private class DeliveringState extends State { @Override public void enter() { if (DBG) log("DeliveringState.enter: entering DeliveringState"); } @Override public void exit() { if (DBG) log("DeliveringState.exit: leaving DeliveringState"); } @Override public boolean processMessage(Message msg) { if (DBG) log("DeliveringState.processMessage: processing " + getWhatToString(msg.what)); switch (msg.what) { case EVENT_NEW_SMS: // handle new SMS from RIL handleNewSms((AsyncResult) msg.obj); sendMessage(EVENT_RETURN_TO_IDLE); return HANDLED; case EVENT_INJECT_SMS: // handle new injected SMS handleInjectSms((AsyncResult) msg.obj, msg.arg1 == 1 /* isOverIms */, msg.arg2 /* token */); sendMessage(EVENT_RETURN_TO_IDLE); return HANDLED; case EVENT_BROADCAST_SMS: // if any broadcasts were sent, transition to waiting state InboundSmsTracker inboundSmsTracker = (InboundSmsTracker) msg.obj; if (processMessagePart(inboundSmsTracker)) { sendMessage(obtainMessage(EVENT_UPDATE_TRACKER, msg.obj)); transitionTo(mWaitingState); } else { // if event is sent from SmsBroadcastUndelivered.broadcastSms(), and // processMessagePart() returns false, the state machine will be stuck in // DeliveringState until next message is received. Send message to // transition to idle to avoid that so that wakelock can be released log("DeliveringState.processMessage: EVENT_BROADCAST_SMS: No broadcast " + "sent. Return to IdleState"); sendMessage(EVENT_RETURN_TO_IDLE); } return HANDLED; case EVENT_RETURN_TO_IDLE: // return to idle after processing all other messages transitionTo(mIdleState); return HANDLED; case EVENT_RELEASE_WAKELOCK: mWakeLock.release(); // decrement wakelock from previous entry to Idle if (!mWakeLock.isHeld()) { // wakelock should still be held until 3 seconds after we enter Idle loge("mWakeLock released while delivering/broadcasting!"); } return HANDLED; case EVENT_UPDATE_TRACKER: logd("process tracker message in DeliveringState " + msg.arg1); return HANDLED; // we shouldn't get this message type in this state, log error and halt. case EVENT_BROADCAST_COMPLETE: case EVENT_START_ACCEPTING_SMS: default: logeWithLocalLog("Unhandled msg in delivering state, msg.what = " + getWhatToString(msg.what)); // let DefaultState handle these unexpected message types return NOT_HANDLED; } } } /** * The waiting state delegates handling of new SMS to parent {@link DeliveringState}, but * defers handling of the {@link #EVENT_BROADCAST_SMS} phase until after the current * result receiver sends {@link #EVENT_BROADCAST_COMPLETE}. Before transitioning to * {@link DeliveringState}, {@link #EVENT_RETURN_TO_IDLE} is sent to transition to * {@link IdleState} after any deferred {@link #EVENT_BROADCAST_SMS} messages are handled. */ private class WaitingState extends State { private InboundSmsTracker mLastDeliveredSmsTracker; @Override public void enter() { if (DBG) log("WaitingState.enter: entering WaitingState"); } @Override public void exit() { if (DBG) log("WaitingState.exit: leaving WaitingState"); // Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds // to give any receivers time to take their own wake locks setWakeLockTimeout(WAKELOCK_TIMEOUT); mPhone.getIccSmsInterfaceManager().mDispatchersController.sendEmptyMessage( SmsDispatchersController.EVENT_SMS_HANDLER_EXITING_WAITING_STATE); } @Override public boolean processMessage(Message msg) { if (DBG) log("WaitingState.processMessage: processing " + getWhatToString(msg.what)); switch (msg.what) { case EVENT_BROADCAST_SMS: // defer until the current broadcast completes if (mLastDeliveredSmsTracker != null) { String str = "Defer sms broadcast due to undelivered sms, " + " messageCount = " + mLastDeliveredSmsTracker.getMessageCount() + " destPort = " + mLastDeliveredSmsTracker.getDestPort() + " timestamp = " + mLastDeliveredSmsTracker.getTimestamp() + " currentTimestamp = " + System.currentTimeMillis(); logWithLocalLog(str, mLastDeliveredSmsTracker.getMessageId()); } deferMessage(msg); return HANDLED; case EVENT_RECEIVER_TIMEOUT: logeWithLocalLog("WaitingState.processMessage: received " + "EVENT_RECEIVER_TIMEOUT"); if (mLastDeliveredSmsTracker != null) { mLastDeliveredSmsTracker.getSmsBroadcastReceiver(InboundSmsHandler.this) .fakeNextAction(); } return HANDLED; case EVENT_BROADCAST_COMPLETE: mLastDeliveredSmsTracker = null; // return to idle after handling all deferred messages sendMessage(EVENT_RETURN_TO_IDLE); transitionTo(mDeliveringState); return HANDLED; case EVENT_RETURN_TO_IDLE: // not ready to return to idle; ignore return HANDLED; case EVENT_UPDATE_TRACKER: mLastDeliveredSmsTracker = (InboundSmsTracker) msg.obj; return HANDLED; default: // parent state handles the other message types return NOT_HANDLED; } } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void handleNewSms(AsyncResult ar) { if (ar.exception != null) { loge("Exception processing incoming SMS: " + ar.exception); return; } int result; try { SmsMessage sms = (SmsMessage) ar.result; result = dispatchMessage(sms.mWrappedSmsMessage, SOURCE_NOT_INJECTED, 0 /*unused*/); } catch (RuntimeException ex) { loge("Exception dispatching message", ex); result = RESULT_SMS_DISPATCH_FAILURE; } // RESULT_OK means that the SMS will be acknowledged by special handling, // e.g. for SMS-PP data download. Any other result, we should ack here. if (result != Activity.RESULT_OK) { boolean handled = (result == Intents.RESULT_SMS_HANDLED); notifyAndAcknowledgeLastIncomingSms(handled, result, null); } } /** * This method is called when a new SMS PDU is injected into application framework. * @param ar is the AsyncResult that has the SMS PDU to be injected. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void handleInjectSms(AsyncResult ar, boolean isOverIms, int token) { int result; SmsDispatchersController.SmsInjectionCallback callback = null; try { callback = (SmsDispatchersController.SmsInjectionCallback) ar.userObj; SmsMessage sms = (SmsMessage) ar.result; if (sms == null) { loge("Null injected sms"); result = RESULT_SMS_NULL_PDU; } else { @SmsSource int smsSource = isOverIms ? SOURCE_INJECTED_FROM_IMS : SOURCE_INJECTED_FROM_UNKNOWN; result = dispatchMessage(sms.mWrappedSmsMessage, smsSource, token); } } catch (RuntimeException ex) { loge("Exception dispatching message", ex); result = RESULT_SMS_DISPATCH_FAILURE; } if (callback != null) { callback.onSmsInjectedResult(result); } } /** * Process an SMS message from the RIL, calling subclass methods to handle 3GPP and * 3GPP2-specific message types. * * @param smsb the SmsMessageBase object from the RIL * @param smsSource the source of the SMS message * @return a result code from {@link android.provider.Telephony.Sms.Intents}, * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC */ private int dispatchMessage(SmsMessageBase smsb, @SmsSource int smsSource, int token) { // If sms is null, there was a parsing error. if (smsb == null) { loge("dispatchSmsMessage: message is null"); return RESULT_SMS_NULL_MESSAGE; } if (mSmsReceiveDisabled) { // Device doesn't support receiving SMS, log("Received short message on device which doesn't support " + "receiving SMS. Ignored."); return Intents.RESULT_SMS_HANDLED; } int result = dispatchMessageRadioSpecific(smsb, smsSource, token); // In case of error, add to metrics. This is not required in case of success, as the // data will be tracked when the message is processed (processMessagePart). if (result != Intents.RESULT_SMS_HANDLED && result != Activity.RESULT_OK) { mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), is3gpp2(), smsSource, result); mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result); if (mPhone != null) { TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics(); if (telephonyAnalytics != null) { SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics(); if (smsMmsAnalytics != null) { smsMmsAnalytics.onIncomingSmsError(smsSource, result); } } } } return result; } /** * Process voicemail notification, SMS-PP data download, CDMA CMAS, CDMA WAP push, and other * 3GPP/3GPP2-specific messages. Regular SMS messages are handled by calling the shared * {@link #dispatchNormalMessage} from this class. * * @param smsb the SmsMessageBase object from the RIL * @param smsSource the source of the SMS message * @return a result code from {@link android.provider.Telephony.Sms.Intents}, * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC */ protected abstract int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource, int token); /** * Send an acknowledge message to the SMSC. * @param success indicates that last message was successfully received. * @param result result code indicating any error * @param response callback message sent when operation completes. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected abstract void acknowledgeLastIncomingSms(boolean success, int result, Message response); /** * Notify interested apps if the framework has rejected an incoming SMS, * and send an acknowledge message to the network. * @param success indicates that last message was successfully received. * @param result result code indicating any error * @param response callback message sent when operation completes. */ private void notifyAndAcknowledgeLastIncomingSms(boolean success, int result, Message response) { if (!success) { // broadcast SMS_REJECTED_ACTION intent Intent intent = new Intent(Intents.SMS_REJECTED_ACTION); intent.putExtra("result", result); intent.putExtra("subId", mPhone.getSubId()); mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS); } acknowledgeLastIncomingSms(success, result, response); } /** * Return true if this handler is for 3GPP2 messages; false for 3GPP format. * @return true for the 3GPP2 handler; false for the 3GPP handler */ protected abstract boolean is3gpp2(); /** * Dispatch a normal incoming SMS. This is called from {@link #dispatchMessageRadioSpecific} * if no format-specific handling was required. Saves the PDU to the SMS provider raw table, * creates an {@link InboundSmsTracker}, then sends it to the state machine as an * {@link #EVENT_BROADCAST_SMS}. Returns {@link Intents#RESULT_SMS_HANDLED} or an error value. * * @param sms the message to dispatch * @param smsSource the source of the SMS message * @return {@link Intents#RESULT_SMS_HANDLED} if the message was accepted, or an error status */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected int dispatchNormalMessage(SmsMessageBase sms, @SmsSource int smsSource) { SmsHeader smsHeader = sms.getUserDataHeader(); InboundSmsTracker tracker; if ((smsHeader == null) || (smsHeader.concatRef == null)) { // Message is not concatenated. int destPort = -1; if (smsHeader != null && smsHeader.portAddrs != null) { // The message was sent to a port. destPort = smsHeader.portAddrs.destPort; if (DBG) log("destination port: " + destPort); } tracker = TelephonyComponentFactory.getInstance() .inject(InboundSmsTracker.class.getName()) .makeInboundSmsTracker(mContext, sms.getPdu(), sms.getTimestampMillis(), destPort, is3gpp2(), false, sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(), sms.getMessageBody(), sms.getMessageClass() == MessageClass.CLASS_0, mPhone.getSubId(), smsSource); } else { // Create a tracker for this message segment. SmsHeader.ConcatRef concatRef = smsHeader.concatRef; SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs; int destPort = (portAddrs != null ? portAddrs.destPort : -1); tracker = TelephonyComponentFactory.getInstance() .inject(InboundSmsTracker.class.getName()) .makeInboundSmsTracker(mContext, sms.getPdu(), sms.getTimestampMillis(), destPort, is3gpp2(), sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(), concatRef.refNumber, concatRef.seqNumber, concatRef.msgCount, false, sms.getMessageBody(), sms.getMessageClass() == MessageClass.CLASS_0, mPhone.getSubId(), smsSource); } if (VDBG) log("created tracker: " + tracker); // de-duping is done only for text messages // destPort = -1 indicates text messages, otherwise it's a data sms return addTrackerToRawTableAndSendMessage(tracker, tracker.getDestPort() == -1 /* de-dup if text message */); } /** * Helper to add the tracker to the raw table and then send a message to broadcast it, if * successful. Returns the SMS intent status to return to the SMSC. * @param tracker the tracker to save to the raw table and then deliver * @return {@link Intents#RESULT_SMS_HANDLED} or one of these errors:
* RESULT_SMS_UNSUPPORTED
* RESULT_SMS_DUPLICATED
* RESULT_SMS_DISPATCH_FAILURE
* RESULT_SMS_NULL_PDU
* RESULT_SMS_NULL_MESSAGE
* RESULT_SMS_DATABASE_ERROR
* RESULT_SMS_INVALID_URI
*/ protected int addTrackerToRawTableAndSendMessage(InboundSmsTracker tracker, boolean deDup) { int result = addTrackerToRawTable(tracker, deDup); switch(result) { case Intents.RESULT_SMS_HANDLED: sendMessage(EVENT_BROADCAST_SMS, tracker); return Intents.RESULT_SMS_HANDLED; case Intents.RESULT_SMS_DUPLICATED: return Intents.RESULT_SMS_HANDLED; default: return result; } } /** * Process the inbound SMS segment. If the message is complete, send it as an ordered * broadcast to interested receivers and return true. If the message is a segment of an * incomplete multi-part SMS, return false. * @param tracker the tracker containing the message segment to process * @return true if an ordered broadcast was sent; false if waiting for more message segments */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private boolean processMessagePart(InboundSmsTracker tracker) { int messageCount = tracker.getMessageCount(); byte[][] pdus; long[] timestamps; int destPort = tracker.getDestPort(); boolean block = false; String address = tracker.getAddress(); // Do not process when the message count is invalid. if (messageCount <= 0) { loge("processMessagePart: returning false due to invalid message count " + messageCount, tracker.getMessageId()); return false; } if (messageCount == 1) { // single-part message pdus = new byte[][]{tracker.getPdu()}; timestamps = new long[]{tracker.getTimestamp()}; block = BlockChecker.isBlocked(mContext, tracker.getDisplayAddress(), null); } else { // multi-part message Cursor cursor = null; try { // used by several query selection arguments String refNumber = Integer.toString(tracker.getReferenceNumber()); String count = Integer.toString(tracker.getMessageCount()); // query for all segments and broadcast message if we have all the parts String[] whereArgs = {address, refNumber, count}; cursor = mResolver.query(sRawUri, PDU_SEQUENCE_PORT_PROJECTION, tracker.getQueryForSegments(), whereArgs, null); int cursorCount = cursor.getCount(); if (cursorCount < messageCount) { // Wait for the other message parts to arrive. It's also possible for the last // segment to arrive before processing the EVENT_BROADCAST_SMS for one of the // earlier segments. In that case, the broadcast will be sent as soon as all // segments are in the table, and any later EVENT_BROADCAST_SMS messages will // get a row count of 0 and return. log("processMessagePart: returning false. Only " + cursorCount + " of " + messageCount + " segments " + " have arrived. refNumber: " + refNumber, tracker.getMessageId()); return false; } // All the parts are in place, deal with them pdus = new byte[messageCount][]; timestamps = new long[messageCount]; while (cursor.moveToNext()) { // subtract offset to convert sequence to 0-based array index int index = cursor.getInt(PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING .get(SEQUENCE_COLUMN)) - tracker.getIndexOffset(); // The invalid PDUs can be received and stored in the raw table. The range // check ensures the process not crash even if the seqNumber in the // UserDataHeader is invalid. if (index >= pdus.length || index < 0) { loge(String.format( "processMessagePart: invalid seqNumber = %d, messageCount = %d", index + tracker.getIndexOffset(), messageCount), tracker.getMessageId()); continue; } pdus[index] = HexDump.hexStringToByteArray(cursor.getString( PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING.get(PDU_COLUMN))); // Read the destination port from the first segment (needed for CDMA WAP PDU). // It's not a bad idea to prefer the port from the first segment in other cases. if (index == 0 && !cursor.isNull(PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING .get(DESTINATION_PORT_COLUMN))) { int port = cursor.getInt(PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING .get(DESTINATION_PORT_COLUMN)); // strip format flags and convert to real port number, or -1 port = InboundSmsTracker.getRealDestPort(port); if (port != -1) { destPort = port; } } timestamps[index] = cursor.getLong( PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING.get(DATE_COLUMN)); // check if display address should be blocked or not if (!block) { // Depending on the nature of the gateway, the display origination address // is either derived from the content of the SMS TP-OA field, or the TP-OA // field contains a generic gateway address and the from address is added // at the beginning in the message body. In that case only the first SMS // (part of Multi-SMS) comes with the display originating address which // could be used for block checking purpose. block = BlockChecker.isBlocked(mContext, cursor.getString(PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING .get(DISPLAY_ADDRESS_COLUMN)), null); } } log("processMessagePart: all " + messageCount + " segments " + " received. refNumber: " + refNumber, tracker.getMessageId()); } catch (SQLException e) { loge("processMessagePart: Can't access multipart SMS database, " + SmsController.formatCrossStackMessageId(tracker.getMessageId()), e); return false; } finally { if (cursor != null) { cursor.close(); } } } final boolean isWapPush = (destPort == SmsHeader.PORT_WAP_PUSH); String format = tracker.getFormat(); // Do not process null pdu(s). Check for that and return false in that case. List pduList = Arrays.asList(pdus); if (pduList.size() == 0 || pduList.contains(null)) { String errorMsg = "processMessagePart: returning false due to " + (pduList.size() == 0 ? "pduList.size() == 0" : "pduList.contains(null)"); logeWithLocalLog(errorMsg, tracker.getMessageId()); mPhone.getSmsStats().onIncomingSmsError( is3gpp2(), tracker.getSource(), RESULT_SMS_NULL_PDU); if (mPhone != null) { TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics(); if (telephonyAnalytics != null) { SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics(); if (smsMmsAnalytics != null) { smsMmsAnalytics.onIncomingSmsError( tracker.getSource(), RESULT_SMS_NULL_PDU); } } } return false; } ByteArrayOutputStream output = new ByteArrayOutputStream(); if (isWapPush) { for (byte[] pdu : pdus) { // 3GPP needs to extract the User Data from the PDU; 3GPP2 has already done this if (format == SmsConstants.FORMAT_3GPP) { SmsMessage msg = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP); if (msg != null) { pdu = msg.getUserData(); } else { loge("processMessagePart: SmsMessage.createFromPdu returned null", tracker.getMessageId()); mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), tracker.getSource(), SmsConstants.FORMAT_3GPP, timestamps, false, tracker.getMessageId()); mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(), messageCount, RESULT_SMS_NULL_MESSAGE, tracker.getMessageId()); return false; } } output.write(pdu, 0, pdu.length); } } SmsBroadcastReceiver resultReceiver = tracker.getSmsBroadcastReceiver(this); if (!mUserManager.isUserUnlocked()) { log("processMessagePart: !isUserUnlocked; calling processMessagePartWithUserLocked. " + "Port: " + destPort, tracker.getMessageId()); return processMessagePartWithUserLocked( tracker, (isWapPush ? new byte[][] {output.toByteArray()} : pdus), destPort, resultReceiver, block); } if (isWapPush) { int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver, this, address, tracker.getSubId(), tracker.getMessageId()); if (DBG) { log("processMessagePart: dispatchWapPdu() returned " + result, tracker.getMessageId()); } // Add result of WAP-PUSH into metrics. RESULT_SMS_HANDLED indicates that the WAP-PUSH // needs to be ignored, so treating it as a success case. boolean wapPushResult = result == Activity.RESULT_OK || result == Intents.RESULT_SMS_HANDLED; mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), tracker.getSource(), format, timestamps, wapPushResult, tracker.getMessageId()); mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(), messageCount, result, tracker.getMessageId()); // result is Activity.RESULT_OK if an ordered broadcast was sent if (result == Activity.RESULT_OK) { return true; } else { deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), MARK_DELETED); loge("processMessagePart: returning false as the ordered broadcast for WAP push " + "was not sent", tracker.getMessageId()); return false; } } // All parts of SMS are received. Update metrics for incoming SMS. // The metrics are generated before SMS filters are invoked. // For messages composed by multiple parts, the metrics are generated considering the // characteristics of the last one. mMetrics.writeIncomingSmsSession(mPhone.getPhoneId(), tracker.getSource(), format, timestamps, block, tracker.getMessageId()); mPhone.getSmsStats().onIncomingSmsSuccess(is3gpp2(), tracker.getSource(), messageCount, block, tracker.getMessageId()); if (mPhone != null) { TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics(); if (telephonyAnalytics != null) { SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics(); if (smsMmsAnalytics != null) { smsMmsAnalytics.onIncomingSmsSuccess(tracker.getSource()); } } } // Always invoke SMS filters, even if the number ends up being blocked, to prevent // surprising bugs due to blocking numbers that happen to be used for visual voicemail SMS // or other carrier system messages. boolean filterInvoked = filterSms( pdus, destPort, tracker, resultReceiver, true /* userUnlocked */, block); if (!filterInvoked) { // Block now if the filter wasn't invoked. Otherwise, it will be the responsibility of // the filter to delete the SMS once processing completes. if (block) { deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), DELETE_PERMANENTLY); log("processMessagePart: returning false as the phone number is blocked", tracker.getMessageId()); return false; } dispatchSmsDeliveryIntent(pdus, format, destPort, resultReceiver, tracker.isClass0(), tracker.getSubId(), tracker.getMessageId()); } return true; } /** * Processes the message part while the credential-encrypted storage is still locked. * *

If the message is a regular MMS, show a new message notification. If the message is a * SMS, ask the carrier app to filter it and show the new message notification if the carrier * app asks to keep the message. * * @return true if an ordered broadcast was sent to the carrier app; false otherwise. */ private boolean processMessagePartWithUserLocked(InboundSmsTracker tracker, byte[][] pdus, int destPort, SmsBroadcastReceiver resultReceiver, boolean block) { if (destPort == SmsHeader.PORT_WAP_PUSH && mWapPush.isWapPushForMms(pdus[0], this)) { showNewMessageNotification(); return false; } if (destPort == -1) { // This is a regular SMS - hand it to the carrier or system app for filtering. boolean filterInvoked = filterSms( pdus, destPort, tracker, resultReceiver, false /* userUnlocked */, block); if (filterInvoked) { // filter invoked, wait for it to return the result. return true; } else if (!block) { // filter not invoked and message not blocked, show the notification and do nothing // further. Even if the message is blocked, we keep it in the database so it can be // reprocessed by filters once credential-encrypted storage is available. showNewMessageNotification(); } } return false; } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void showNewMessageNotification() { // Do not show the notification on non-FBE devices. if (!StorageManager.isFileEncrypted()) { return; } log("Show new message notification."); PendingIntent intent = PendingIntent.getBroadcast( mContext, 0, new Intent(ACTION_OPEN_SMS_APP), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); Notification.Builder mBuilder = new Notification.Builder(mContext) .setSmallIcon(com.android.internal.R.drawable.sym_action_chat) .setAutoCancel(true) .setVisibility(Notification.VISIBILITY_PUBLIC) .setDefaults(Notification.DEFAULT_ALL) .setContentTitle(mContext.getString(R.string.new_sms_notification_title)) .setContentText(mContext.getString(R.string.new_sms_notification_content)) .setContentIntent(intent) .setChannelId(NotificationChannelController.CHANNEL_ID_SMS); NotificationManager mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.notify( NOTIFICATION_TAG, NOTIFICATION_ID_NEW_MESSAGE, mBuilder.build()); } static void cancelNewMessageNotification(Context context) { NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.cancel(InboundSmsHandler.NOTIFICATION_TAG, InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE); } /** * Creates the default filters used to filter SMS messages. * *

Currently 3 filters exist: the carrier package, the VisualVoicemailSmsFilter, and the * missed incoming call SMS filter. * *

Since the carrier filter is asynchronous, if a message passes through the carrier filter, * the remaining filters will be applied in the callback. */ private List createDefaultSmsFilters() { List smsFilters = new ArrayList<>(3); smsFilters.add( (pdus, destPort, tracker, resultReceiver, userUnlocked, block, remainingFilters) -> { CarrierServicesSmsFilterCallback filterCallback = new CarrierServicesSmsFilterCallback( pdus, destPort, tracker, tracker.getFormat(), resultReceiver, userUnlocked, tracker.isClass0(), tracker.getSubId(), tracker.getMessageId(), block, remainingFilters); CarrierServicesSmsFilter carrierServicesFilter = new CarrierServicesSmsFilter( mContext, mPhone, pdus, destPort, tracker.getFormat(), filterCallback, getName() + "::CarrierServicesSmsFilter", mCarrierServiceLocalLog, tracker.getMessageId()); if (carrierServicesFilter.filter()) { log("SMS is being handled by carrier service", tracker.getMessageId()); return true; } else { return false; } }); smsFilters.add( (pdus, destPort, tracker, resultReceiver, userUnlocked, block, remainingFilters) -> { if (VisualVoicemailSmsFilter.filter( mContext, pdus, tracker.getFormat(), destPort, tracker.getSubId())) { logWithLocalLog("Visual voicemail SMS dropped", tracker.getMessageId()); dropFilteredSms(tracker, resultReceiver, block); return true; } return false; }); smsFilters.add( (pdus, destPort, tracker, resultReceiver, userUnlocked, block, remainingFilters) -> { MissedIncomingCallSmsFilter missedIncomingCallSmsFilter = new MissedIncomingCallSmsFilter(mPhone); if (missedIncomingCallSmsFilter.filter(pdus, tracker.getFormat())) { logWithLocalLog("Missed incoming call SMS received", tracker.getMessageId()); dropFilteredSms(tracker, resultReceiver, block); return true; } return false; }); return smsFilters; } private void dropFilteredSms( InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean block) { if (block) { deleteFromRawTable( tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), DELETE_PERMANENTLY); sendMessage(EVENT_BROADCAST_COMPLETE); } else { dropSms(resultReceiver); } } /** * Filters the SMS. * *

Each filter in {@link #mSmsFilters} is invoked sequentially. If any filter returns true, * this method returns true and subsequent filters are ignored. * * @return true if a filter is invoked and the SMS processing flow is diverted, false otherwise. */ private boolean filterSms(byte[][] pdus, int destPort, InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked, boolean block) { return filterSms(pdus, destPort, tracker, resultReceiver, userUnlocked, block, mSmsFilters); } private static boolean filterSms(byte[][] pdus, int destPort, InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked, boolean block, List filters) { ListIterator iterator = filters.listIterator(); while (iterator.hasNext()) { SmsFilter smsFilter = iterator.next(); if (smsFilter.filterSms(pdus, destPort, tracker, resultReceiver, userUnlocked, block, filters.subList(iterator.nextIndex(), filters.size()))) { return true; } } return false; } /** * Dispatch the intent with the specified permission, appOp, and result receiver, using * this state machine's handler thread to run the result receiver. * * @param intent the intent to broadcast * @param permission receivers are required to have this permission * @param appOp app op that is being performed when dispatching to a receiver * @param user user to deliver the intent to */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void dispatchIntent(Intent intent, String permission, String appOp, Bundle opts, SmsBroadcastReceiver resultReceiver, UserHandle user, int subId) { intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); final String action = intent.getAction(); if (Intents.SMS_DELIVER_ACTION.equals(action) || Intents.SMS_RECEIVED_ACTION.equals(action) || Intents.WAP_PUSH_DELIVER_ACTION.equals(action) || Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) { // Some intents need to be delivered with high priority: // SMS_DELIVER, SMS_RECEIVED, WAP_PUSH_DELIVER, WAP_PUSH_RECEIVED // In some situations, like after boot up or system under load, normal // intent delivery could take a long time. // This flag should only be set for intents for visible, timely operations // which is true for the intents above. intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); } SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); // override the subId value in the intent with the values from tracker as they can be // different, specifically if the message is coming from SmsBroadcastUndelivered if (SubscriptionManager.isValidSubscriptionId(subId)) { SubscriptionManager.putSubscriptionIdExtra(intent, subId); } if (user.equals(UserHandle.ALL)) { // Get a list of currently started users. int[] users = null; final List userHandles = mUserManager.getUserHandles(false); final List runningUserHandles = new ArrayList(); for (UserHandle handle : userHandles) { if (mUserManager.isUserRunning(handle)) { runningUserHandles.add(handle); } else { if (handle.equals(UserHandle.SYSTEM)) { logeWithLocalLog("dispatchIntent: SYSTEM user is not running", resultReceiver.mInboundSmsTracker.getMessageId()); } } } if (runningUserHandles.isEmpty()) { users = new int[] {user.getIdentifier()}; } else { users = new int[runningUserHandles.size()]; for (int i = 0; i < runningUserHandles.size(); i++) { users[i] = runningUserHandles.get(i).getIdentifier(); } } // Deliver the broadcast only to those running users that are permitted // by user policy. for (int i = users.length - 1; i >= 0; i--) { UserHandle targetUser = UserHandle.of(users[i]); if (users[i] != UserHandle.SYSTEM.getIdentifier()) { // Is the user not allowed to use SMS? if (hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) { continue; } // Skip unknown users and managed profiles as well if (mUserManager.isManagedProfile(users[i])) { continue; } } // Only pass in the resultReceiver when the user SYSTEM is processed. try { if (users[i] == UserHandle.SYSTEM.getIdentifier()) { resultReceiver.setWaitingForIntent(intent); } mContext.createPackageContextAsUser(mContext.getPackageName(), 0, targetUser) .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp, users[i] == UserHandle.SYSTEM.getIdentifier() ? resultReceiver : null, getHandler(), null /* initialData */, null /* initialExtras */, opts); } catch (PackageManager.NameNotFoundException ignored) { } } } else { try { resultReceiver.setWaitingForIntent(intent); mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user) .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp, resultReceiver, getHandler(), null /* initialData */, null /* initialExtras */, opts); } catch (PackageManager.NameNotFoundException ignored) { } } } private boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) { final List sources = mUserManager .getUserRestrictionSources(restrictionKey, userHandle); return (sources != null && !sources.isEmpty()); } /** * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs, int deleteType) { Uri uri = deleteType == DELETE_PERMANENTLY ? sRawUriPermanentDelete : sRawUri; int rows = mResolver.delete(uri, deleteWhere, deleteWhereArgs); if (rows == 0) { loge("No rows were deleted from raw table!"); } else if (DBG) { log("Deleted " + rows + " rows from raw table."); } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private Bundle handleSmsWhitelisting(ComponentName target, boolean bgActivityStartAllowed) { String pkgName; String reason; if (target != null) { pkgName = target.getPackageName(); reason = "sms-app"; } else { pkgName = mContext.getPackageName(); reason = "sms-broadcast"; } BroadcastOptions bopts = null; Bundle bundle = null; if (bgActivityStartAllowed) { bopts = BroadcastOptions.makeBasic(); bopts.setBackgroundActivityStartsAllowed(true); bundle = bopts.toBundle(); } long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent( pkgName, PowerWhitelistManager.EVENT_SMS, REASON_EVENT_SMS, reason); if (bopts == null) bopts = BroadcastOptions.makeBasic(); bopts.setTemporaryAppAllowlist(duration, TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_EVENT_SMS, ""); bundle = bopts.toBundle(); return bundle; } /** * Creates and dispatches the intent to the default SMS app, appropriate port or via the {@link * AppSmsManager}. * * @param pdus message pdus * @param format the message format, typically "3gpp" or "3gpp2" * @param destPort the destination port * @param resultReceiver the receiver handling the delivery result */ private void dispatchSmsDeliveryIntent(byte[][] pdus, String format, int destPort, SmsBroadcastReceiver resultReceiver, boolean isClass0, int subId, long messageId) { Intent intent = new Intent(); intent.putExtra("pdus", pdus); intent.putExtra("format", format); if (messageId != 0L) { intent.putExtra("messageId", messageId); } UserHandle userHandle = null; if (destPort == -1) { intent.setAction(Intents.SMS_DELIVER_ACTION); // Direct the intent to only the default SMS app. If we can't find a default SMS app // then sent it to all broadcast receivers. userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId); ComponentName componentName = SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, userHandle); if (componentName != null) { // Deliver SMS message only to this receiver. intent.setComponent(componentName); logWithLocalLog("Delivering SMS to: " + componentName.getPackageName() + " " + componentName.getClassName(), messageId); } else { intent.setComponent(null); } // Handle app specific sms messages. AppSmsManager appManager = mPhone.getAppSmsManager(); if (appManager.handleSmsReceivedIntent(intent)) { // The AppSmsManager handled this intent, we're done. dropSms(resultReceiver); return; } } else { intent.setAction(Intents.DATA_SMS_RECEIVED_ACTION); Uri uri = Uri.parse("sms://localhost:" + destPort); intent.setData(uri); intent.setComponent(null); } if (userHandle == null) { userHandle = UserHandle.SYSTEM; } Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0); dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS, AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, userHandle, subId); } /** * Function to detect and handle duplicate messages. If the received message should replace an * existing message in the raw db, this function deletes the existing message. If an existing * message takes priority (for eg, existing message has already been broadcast), then this new * message should be dropped. * @return true if the message represented by the passed in tracker should be dropped, * false otherwise */ private boolean checkAndHandleDuplicate(InboundSmsTracker tracker) throws SQLException { Pair exactMatchQuery = tracker.getExactMatchDupDetectQuery(); Cursor cursor = null; try { // Check for duplicate message segments cursor = mResolver.query(sRawUri, PDU_DELETED_FLAG_PROJECTION, exactMatchQuery.first, exactMatchQuery.second, null); // moveToNext() returns false if no duplicates were found if (cursor != null && cursor.moveToNext()) { if (cursor.getCount() != 1) { logeWithLocalLog("checkAndHandleDuplicate: Exact match query returned " + cursor.getCount() + " rows", tracker.getMessageId()); } // if the exact matching row is marked deleted, that means this message has already // been received and processed, and can be discarded as dup if (cursor.getInt( PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(DELETED_FLAG_COLUMN)) == 1) { logWithLocalLog("checkAndHandleDuplicate: Discarding duplicate " + "message/segment: " + tracker); logDupPduMismatch(cursor, tracker); return true; // reject message } else { // exact match duplicate is not marked deleted. If it is a multi-part segment, // the code below for inexact match will take care of it. If it is a single // part message, handle it here. if (tracker.getMessageCount() == 1) { // delete the old message segment permanently deleteFromRawTable(exactMatchQuery.first, exactMatchQuery.second, DELETE_PERMANENTLY); logWithLocalLog("checkAndHandleDuplicate: Replacing duplicate message: " + tracker); logDupPduMismatch(cursor, tracker); } } } } finally { if (cursor != null) { cursor.close(); } } // The code above does an exact match. Multi-part message segments need an additional check // on top of that: if there is a message segment that conflicts this new one (may not be an // exact match), replace the old message segment with this one. if (tracker.getMessageCount() > 1) { Pair inexactMatchQuery = tracker.getInexactMatchDupDetectQuery(); cursor = null; try { // Check for duplicate message segments cursor = mResolver.query(sRawUri, PDU_DELETED_FLAG_PROJECTION, inexactMatchQuery.first, inexactMatchQuery.second, null); // moveToNext() returns false if no duplicates were found if (cursor != null && cursor.moveToNext()) { if (cursor.getCount() != 1) { logeWithLocalLog("checkAndHandleDuplicate: Inexact match query returned " + cursor.getCount() + " rows", tracker.getMessageId()); } // delete the old message segment permanently deleteFromRawTable(inexactMatchQuery.first, inexactMatchQuery.second, DELETE_PERMANENTLY); logWithLocalLog("checkAndHandleDuplicate: Replacing duplicate message segment: " + tracker); logDupPduMismatch(cursor, tracker); } } finally { if (cursor != null) { cursor.close(); } } } return false; } private void logDupPduMismatch(Cursor cursor, InboundSmsTracker tracker) { String oldPduString = cursor.getString( PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(PDU_COLUMN)); byte[] pdu = tracker.getPdu(); byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString); if (!Arrays.equals(oldPdu, tracker.getPdu())) { logeWithLocalLog("Warning: dup message PDU of length " + pdu.length + " is different from existing PDU of length " + oldPdu.length, tracker.getMessageId()); } } /** * Insert a message PDU into the raw table so we can acknowledge it immediately. * If the device crashes before the broadcast to listeners completes, it will be delivered * from the raw table on the next device boot. For single-part messages, the deleteWhere * and deleteWhereArgs fields of the tracker will be set to delete the correct row after * the ordered broadcast completes. * * @param tracker the tracker to add to the raw table * @return true on success; false on failure to write to database */ private int addTrackerToRawTable(InboundSmsTracker tracker, boolean deDup) { if (deDup) { try { if (checkAndHandleDuplicate(tracker)) { return Intents.RESULT_SMS_DUPLICATED; // reject message } } catch (SQLException e) { loge("addTrackerToRawTable: Can't access SMS database, " + SmsController.formatCrossStackMessageId(tracker.getMessageId()), e); return RESULT_SMS_DATABASE_ERROR; // reject message } } else { log("addTrackerToRawTable: Skipped message de-duping logic", tracker.getMessageId()); } String address = tracker.getAddress(); String refNumber = Integer.toString(tracker.getReferenceNumber()); String count = Integer.toString(tracker.getMessageCount()); ContentValues values = tracker.getContentValues(); if (VDBG) { log("addTrackerToRawTable: adding content values to raw table: " + values.toString(), tracker.getMessageId()); } Uri newUri = mResolver.insert(sRawUri, values); if (DBG) log("addTrackerToRawTable: URI of new row: " + newUri, tracker.getMessageId()); try { long rowId = ContentUris.parseId(newUri); if (tracker.getMessageCount() == 1) { // set the delete selection args for single-part message tracker.setDeleteWhere(SELECT_BY_ID, new String[]{Long.toString(rowId)}); } else { // set the delete selection args for multi-part message String[] deleteWhereArgs = {address, refNumber, count}; tracker.setDeleteWhere(tracker.getQueryForSegments(), deleteWhereArgs); } return Intents.RESULT_SMS_HANDLED; } catch (Exception e) { loge("addTrackerToRawTable: error parsing URI for new row: " + newUri + " " + SmsController.formatCrossStackMessageId(tracker.getMessageId()), e); return RESULT_SMS_INVALID_URI; } } /** * Returns whether the default message format for the current radio technology is 3GPP2. * @return true if the radio technology uses 3GPP2 format by default, false for 3GPP format */ static boolean isCurrentFormat3gpp2() { int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); return (PHONE_TYPE_CDMA == activePhone); } @VisibleForTesting public static int sTimeoutDurationMillis = 10 * 60 * 1000; // 10 minutes /** * Handler for an {@link InboundSmsTracker} broadcast. Deletes PDUs from the raw table and * logs the broadcast duration (as an error if the other receivers were especially slow). */ public final class SmsBroadcastReceiver extends BroadcastReceiver { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final String mDeleteWhere; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final String[] mDeleteWhereArgs; private long mBroadcastTimeMillis; public Intent mWaitingForIntent; private final InboundSmsTracker mInboundSmsTracker; /** * This method must be called anytime an ordered broadcast is sent that is expected to be * received by this receiver. */ public synchronized void setWaitingForIntent(Intent intent) { mWaitingForIntent = intent; mBroadcastTimeMillis = System.currentTimeMillis(); removeMessages(EVENT_RECEIVER_TIMEOUT); sendMessageDelayed(EVENT_RECEIVER_TIMEOUT, sTimeoutDurationMillis); } public SmsBroadcastReceiver(InboundSmsTracker tracker) { mDeleteWhere = tracker.getDeleteWhere(); mDeleteWhereArgs = tracker.getDeleteWhereArgs(); mInboundSmsTracker = tracker; } /** * This method is called if the expected intent (mWaitingForIntent) is not received and * the timer for it expires. It fakes the receipt of the intent to unblock the state * machine. */ public void fakeNextAction() { if (mWaitingForIntent != null) { logeWithLocalLog("fakeNextAction: " + mWaitingForIntent.getAction(), mInboundSmsTracker.getMessageId()); handleAction(mWaitingForIntent, false); } else { logeWithLocalLog("fakeNextAction: mWaitingForIntent is null", mInboundSmsTracker.getMessageId()); } } @Override public void onReceive(Context context, Intent intent) { if (intent == null) { logeWithLocalLog("onReceive: received null intent, faking " + mWaitingForIntent, mInboundSmsTracker.getMessageId()); return; } handleAction(intent, true); } private synchronized void handleAction(@NonNull Intent intent, boolean onReceive) { String action = intent.getAction(); if (mWaitingForIntent == null || !mWaitingForIntent.getAction().equals(action)) { logeWithLocalLog("handleAction: Received " + action + " when expecting " + mWaitingForIntent == null ? "none" : mWaitingForIntent.getAction(), mInboundSmsTracker.getMessageId()); return; } if (onReceive) { int durationMillis = (int) (System.currentTimeMillis() - mBroadcastTimeMillis); if (durationMillis >= 5000) { loge("Slow ordered broadcast completion time for " + action + ": " + durationMillis + " ms"); } else if (DBG) { log("Ordered broadcast completed for " + action + " in: " + durationMillis + " ms"); } } int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); if (action.equals(Intents.SMS_DELIVER_ACTION)) { // Now dispatch the notification only intent intent.setAction(Intents.SMS_RECEIVED_ACTION); // Allow registered broadcast receivers to get this intent even // when they are in the background. intent.setComponent(null); // All running users will be notified of the received sms. Bundle options = handleSmsWhitelisting(null, false /* bgActivityStartAllowed */); setWaitingForIntent(intent); dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS, AppOpsManager.OPSTR_RECEIVE_SMS, options, this, UserHandle.ALL, subId); } else if (action.equals(Intents.WAP_PUSH_DELIVER_ACTION)) { // Now dispatch the notification only intent intent.setAction(Intents.WAP_PUSH_RECEIVED_ACTION); intent.setComponent(null); // Only the primary user will receive notification of incoming mms. // That app will do the actual downloading of the mms. long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent( mContext.getPackageName(), PowerWhitelistManager.EVENT_MMS, REASON_EVENT_MMS, "mms-broadcast"); BroadcastOptions bopts = BroadcastOptions.makeBasic(); bopts.setTemporaryAppAllowlist(duration, TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_EVENT_MMS, ""); Bundle options = bopts.toBundle(); String mimeType = intent.getType(); setWaitingForIntent(intent); dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType), WapPushOverSms.getAppOpsStringPermissionForIntent(mimeType), options, this, UserHandle.SYSTEM, subId); } else { // Now that the intents have been deleted we can clean up the PDU data. if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action) && !Intents.SMS_RECEIVED_ACTION.equals(action) && !Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) { loge("unexpected BroadcastReceiver action: " + action); } if (onReceive) { int rc = getResultCode(); if ((rc != Activity.RESULT_OK) && (rc != Intents.RESULT_SMS_HANDLED)) { loge("a broadcast receiver set the result code to " + rc + ", deleting from raw table anyway!"); } else if (DBG) { log("successful broadcast, deleting from raw table."); } } deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs, MARK_DELETED); mWaitingForIntent = null; removeMessages(EVENT_RECEIVER_TIMEOUT); sendMessage(EVENT_BROADCAST_COMPLETE); } } } /** * Callback that handles filtering results by carrier services. */ private final class CarrierServicesSmsFilterCallback implements CarrierServicesSmsFilter.CarrierServicesSmsFilterCallbackInterface { private final byte[][] mPdus; private final int mDestPort; private final InboundSmsTracker mTracker; private final String mSmsFormat; private final SmsBroadcastReceiver mSmsBroadcastReceiver; private final boolean mUserUnlocked; private final boolean mIsClass0; private final int mSubId; private final long mMessageId; private final boolean mBlock; private final List mRemainingFilters; CarrierServicesSmsFilterCallback(byte[][] pdus, int destPort, InboundSmsTracker tracker, String smsFormat, SmsBroadcastReceiver smsBroadcastReceiver, boolean userUnlocked, boolean isClass0, int subId, long messageId, boolean block, List remainingFilters) { mPdus = pdus; mDestPort = destPort; mTracker = tracker; mSmsFormat = smsFormat; mSmsBroadcastReceiver = smsBroadcastReceiver; mUserUnlocked = userUnlocked; mIsClass0 = isClass0; mSubId = subId; mMessageId = messageId; mBlock = block; mRemainingFilters = remainingFilters; } @Override public void onFilterComplete(int result) { log("onFilterComplete: result is " + result, mTracker.getMessageId()); boolean carrierRequestedDrop = (result & CarrierMessagingService.RECEIVE_OPTIONS_DROP) != 0; if (carrierRequestedDrop) { // Carrier app asked the platform to drop the SMS. Drop it from the database and // complete processing. dropFilteredSms(mTracker, mSmsBroadcastReceiver, mBlock); return; } boolean filterInvoked = filterSms(mPdus, mDestPort, mTracker, mSmsBroadcastReceiver, mUserUnlocked, mBlock, mRemainingFilters); if (filterInvoked) { // A remaining filter has assumed responsibility for further message processing. return; } // Now that all filters have been invoked, drop the message if it is blocked. if (mBlock) { // Only delete the message if the user is unlocked. Otherwise, we should reprocess // the message after unlock so the filter has a chance to run while credential- // encrypted storage is available. if (mUserUnlocked) { log("onFilterComplete: dropping message as the sender is blocked", mTracker.getMessageId()); dropFilteredSms(mTracker, mSmsBroadcastReceiver, mBlock); } else { // Just complete handling of the message without dropping it. sendMessage(EVENT_BROADCAST_COMPLETE); } return; } // Message matched no filters and is not blocked, so complete processing. if (mUserUnlocked) { dispatchSmsDeliveryIntent( mPdus, mSmsFormat, mDestPort, mSmsBroadcastReceiver, mIsClass0, mSubId, mMessageId); } else { // Don't do anything further, leave the message in the raw table if the // credential-encrypted storage is still locked and show the new message // notification if the message is visible to the user. if (!isSkipNotifyFlagSet(result)) { showNewMessageNotification(); } sendMessage(EVENT_BROADCAST_COMPLETE); } } } private void dropSms(SmsBroadcastReceiver receiver) { // Needs phone package permissions. deleteFromRawTable(receiver.mDeleteWhere, receiver.mDeleteWhereArgs, MARK_DELETED); sendMessage(EVENT_BROADCAST_COMPLETE); } /** Checks whether the flag to skip new message notification is set in the bitmask returned * from the carrier app. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private boolean isSkipNotifyFlagSet(int callbackResult) { return (callbackResult & RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE) > 0; } /** * Log with debug level in logcat and LocalLog * @param logMsg msg to log */ protected void logWithLocalLog(String logMsg) { log(logMsg); mLocalLog.log(logMsg); } /** * Log with debug level in logcat and LocalLog * @param logMsg msg to log * @param id unique message id */ protected void logWithLocalLog(String logMsg, long id) { log(logMsg, id); mLocalLog.log(logMsg + ", " + SmsController.formatCrossStackMessageId(id)); } /** * Log with error level in logcat and LocalLog * @param logMsg msg to log */ protected void logeWithLocalLog(String logMsg) { loge(logMsg); mLocalLog.log(logMsg); } /** * Log with error level in logcat and LocalLog * @param logMsg msg to log * @param id unique message id */ protected void logeWithLocalLog(String logMsg, long id) { loge(logMsg, id); mLocalLog.log(logMsg + ", " + SmsController.formatCrossStackMessageId(id)); } /** * Log with debug level. * @param s the string to log */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Override protected void log(String s) { Rlog.d(getName(), s); } /** * Log with debug level. * @param s the string to log * @param id unique message id */ protected void log(String s, long id) { log(s + ", " + SmsController.formatCrossStackMessageId(id)); } /** * Log with error level. * @param s the string to log */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Override protected void loge(String s) { Rlog.e(getName(), s); } /** * Log with error level. * @param s the string to log * @param id unique message id */ protected void loge(String s, long id) { loge(s + ", " + SmsController.formatCrossStackMessageId(id)); } /** * Log with error level. * @param s the string to log * @param e is a Throwable which logs additional information. */ @Override protected void loge(String s, Throwable e) { Rlog.e(getName(), s, e); } /** * Build up the SMS message body from the SmsMessage array of received SMS * * @param msgs The SmsMessage array of the received SMS * @return The text message body */ private static String buildMessageBodyFromPdus(SmsMessage[] msgs) { if (msgs.length == 1) { // There is only one part, so grab the body directly. return replaceFormFeeds(msgs[0].getDisplayMessageBody()); } else { // Build up the body from the parts. StringBuilder body = new StringBuilder(); for (SmsMessage msg: msgs) { // getDisplayMessageBody() can NPE if mWrappedMessage inside is null. body.append(msg.getDisplayMessageBody()); } return replaceFormFeeds(body.toString()); } } @Override public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); pw.println(getName() + " extends StateMachine:"); pw.increaseIndent(); super.dump(fd, pw, args); if (mCellBroadcastServiceManager != null) { mCellBroadcastServiceManager.dump(fd, pw, args); } pw.println("mLocalLog:"); pw.increaseIndent(); mLocalLog.dump(fd, pw, args); pw.decreaseIndent(); pw.println("mCarrierServiceLocalLog:"); pw.increaseIndent(); mCarrierServiceLocalLog.dump(fd, pw, args); pw.decreaseIndent(); pw.decreaseIndent(); } // Some providers send formfeeds in their messages. Convert those formfeeds to newlines. private static String replaceFormFeeds(String s) { return s == null ? "" : s.replace('\f', '\n'); } @VisibleForTesting public PowerManager.WakeLock getWakeLock() { return mWakeLock; } @VisibleForTesting public int getWakeLockTimeout() { return mWakeLockTimeout; } /** * Sets the wakelock timeout to {@link timeOut} milliseconds */ private void setWakeLockTimeout(int timeOut) { mWakeLockTimeout = timeOut; } /** * Set the SMS filters used by {@link #filterSms} for testing purposes. * * @param smsFilters List of SMS filters, or null to restore the default filters. */ @VisibleForTesting public void setSmsFiltersForTesting(@Nullable List smsFilters) { if (smsFilters == null) { mSmsFilters = createDefaultSmsFilters(); } else { mSmsFilters = smsFilters; } } /** * Handler for the broadcast sent when the new message notification is clicked. It launches the * default SMS app. */ private static class NewMessageNotificationActionReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (ACTION_OPEN_SMS_APP.equals(intent.getAction())) { // do nothing if the user had not unlocked the device yet UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); if (userManager.isUserUnlocked()) { context.startActivity(context.getPackageManager().getLaunchIntentForPackage( Telephony.Sms.getDefaultSmsPackage(context))); } } } } protected byte[] decodeHexString(String hexString) { if (hexString == null || hexString.length() % 2 == 1) { return null; } byte[] bytes = new byte[hexString.length() / 2]; for (int i = 0; i < hexString.length(); i += 2) { bytes[i / 2] = hexToByte(hexString.substring(i, i + 2)); } return bytes; } private byte hexToByte(String hexString) { int firstDigit = toDigit(hexString.charAt(0)); int secondDigit = toDigit(hexString.charAt(1)); return (byte) ((firstDigit << 4) + secondDigit); } private int toDigit(char hexChar) { int digit = Character.digit(hexChar, 16); if (digit == -1) { return 0; } return digit; } /** * Registers the broadcast receiver to launch the default SMS app when the user clicks the * new message notification. */ static void registerNewMessageNotificationActionHandler(Context context) { IntentFilter userFilter = new IntentFilter(); userFilter.addAction(ACTION_OPEN_SMS_APP); context.registerReceiver(new NewMessageNotificationActionReceiver(), userFilter, Context.RECEIVER_NOT_EXPORTED); } protected abstract class CbTestBroadcastReceiver extends BroadcastReceiver { protected abstract void handleTestAction(Intent intent); protected final String mTestAction; public CbTestBroadcastReceiver(String testAction) { mTestAction = testAction; } @Override public void onReceive(Context context, Intent intent) { logd("Received test intent action=" + intent.getAction()); if (intent.getAction().equals(mTestAction)) { // Return early if phone_id is explicilty included and does not match mPhone. // If phone_id extra is not included, continue. int phoneId = mPhone.getPhoneId(); if (intent.getIntExtra("phone_id", phoneId) != phoneId) { return; } handleTestAction(intent); } } } /** A filter for incoming messages allowing the normal processing flow to be skipped. */ @VisibleForTesting public interface SmsFilter { /** * Returns true if a filter is invoked and the SMS processing flow should be diverted, false * otherwise. * *

If the filter can immediately determine that the message matches, it must call * {@link #dropFilteredSms} to drop the message from the database once it has been * processed. * *

If the filter must perform some asynchronous work to determine if the message matches, * it should return true to defer processing. Once it has made a determination, if it finds * the message matches, it must call {@link #dropFilteredSms}. If the message does not * match, it must be passed through {@code remainingFilters} and either dropped if the * remaining filters all return false or if {@code block} is true, or else it must be * broadcast. */ boolean filterSms(byte[][] pdus, int destPort, InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked, boolean block, List remainingFilters); } }