diff --git a/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java b/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java
new file mode 100644
index 0000000..af13992
--- /dev/null
+++ b/frameworks/opt/telephony/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -0,0 +1,2214 @@
+/*
+ * 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.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.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.IPackageManager;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerWhitelistManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+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.text.TextUtils;
+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.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.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+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 the {@link SMSDispatcher} receives a
+ * new SMS from the radio, it calls {@link #dispatchNormalMessage},
+ * which sends a message to the state machine, causing the wakelock to be acquired in
+ * {@link #haltedProcessMessage}, which transitions to {@link DeliveringState} state, where the message
+ * is saved to the raw table, then acknowledged via the {@link SMSDispatcher} which called us.
+ *
+ *
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
+
+ /** Query projection for checking for duplicate message segments. */
+ // MTK-START
+ // Modification for sub class
+ protected static final String[] PDU_DELETED_FLAG_PROJECTION = {
+ // MTK-END
+ "pdu",
+ "deleted"
+ };
+
+ /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
+ private static final Map PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING =
+ new HashMap() {{
+ put(PDU_COLUMN, 0);
+ put(DELETED_FLAG_COLUMN, 1);
+ }};
+
+ /** Query projection for combining concatenated message segments. */
+ // MTK-START
+ // Modification for sub class
+ protected static final String[] PDU_SEQUENCE_PORT_PROJECTION = {
+ // MTK-END
+ "pdu",
+ "sequence",
+ "destination_port",
+ "display_originating_addr",
+ "date"
+ };
+
+ /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
+ // MTK-START
+ // Modification for sub class
+ protected static final Map PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING =
+ new HashMap() {{
+ // MTK-END
+ put(PDU_COLUMN, 0);
+ put(SEQUENCE_COLUMN, 1);
+ put(DESTINATION_PORT_COLUMN, 2);
+ put(DISPLAY_ADDRESS_COLUMN, 3);
+ put(DATE_COLUMN, 4);
+ }};
+
+ 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;
+
+ 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. */
+ // MTK-START
+ // Modification for sub class
+ protected static final int EVENT_BROADCAST_COMPLETE = 3;
+ // MTK-END
+
+ /** Sent on exit from {@link WaitingState} to return to idle after sending all broadcasts. */
+ // MTK-START
+ // Modification for sub class
+ protected static final int EVENT_RETURN_TO_IDLE = 4;
+ // MTK-END
+
+ /** 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;
+
+ /** Wakelock release delay when returning to idle state. */
+ private static final int WAKELOCK_TIMEOUT = 3000;
+
+ // The notitfication tag used when showing a notification. The combination of notification tag
+ // and notification id should be unique within the phone app.
+ private static final String NOTIFICATION_TAG = "InboundSmsHandler";
+ private static final int NOTIFICATION_ID_NEW_MESSAGE = 1;
+
+ /** URI for raw table of SMS provider. */
+ // MTK-START
+ // Modification for sub class
+ public static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
+ public static final Uri sRawUriPermanentDelete =
+ Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete");
+ // MTK-END
+
+ @UnsupportedAppUsage
+ protected final Context mContext;
+
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected final ContentResolver mResolver;
+ // MTK-END
+
+ /** Special handler for WAP push messages. */
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected final WapPushOverSms mWapPush;
+ // MTK-END
+
+ /** Wake lock to ensure device stays awake while dispatching the SMS intents. */
+ @UnsupportedAppUsage
+ private final PowerManager.WakeLock mWakeLock;
+
+ /** DefaultState throws an exception or logs an error for unhandled message types. */
+ // MTK-START
+ // Modification for sub class
+ // change variable attribute for sub class
+ protected DefaultState mDefaultState = new DefaultState();
+
+ /** Startup state. Waiting for {@link SmsBroadcastUndelivered} to complete. */
+ protected StartupState mStartupState = new StartupState();
+
+ /** Idle state. Waiting for messages to process. */
+ @UnsupportedAppUsage
+ protected IdleState mIdleState = new IdleState();
+
+ /** Delivering state. Saves the PDU in the raw table and acknowledges to SMSC. */
+ @UnsupportedAppUsage
+ protected DeliveringState mDeliveringState = new DeliveringState();
+
+ /** Broadcasting state. Waits for current broadcast to complete before delivering next. */
+ @UnsupportedAppUsage
+ protected WaitingState mWaitingState = new WaitingState();
+ // MTK-END
+
+ /** Helper class to check whether storage is available for incoming messages. */
+ // MTK-START, change as public for GsmSmsDispatcher to use
+ public SmsStorageMonitor mStorageMonitor;
+ // MTK-END
+
+ private final boolean mSmsReceiveDisabled;
+
+ @UnsupportedAppUsage
+ protected Phone mPhone;
+
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected UserManager mUserManager;
+ // MTK-END
+
+ protected TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
+
+ private LocalLog mLocalLog = new LocalLog(64);
+ private LocalLog mCarrierServiceLocalLog = new LocalLog(10);
+
+ PowerWhitelistManager mPowerWhitelistManager;
+
+ protected CellBroadcastServiceManager mCellBroadcastServiceManager;
+
+ // Delete permanently from raw table
+ // MTK-START
+ // Modification for sub class
+ protected final int DELETE_PERMANENTLY = 1;
+ // Only mark deleted, but keep in db for message de-duping
+ protected final int MARK_DELETED = 2;
+ // MTK-END
+ private static String ACTION_OPEN_SMS_APP =
+ "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP";
+
+ /** Timeout for releasing wakelock */
+ private int mWakeLockTimeout;
+
+ /** Indicates if last SMS was injected. This is used to recognize SMS received over IMS from
+ others in order to update metrics. */
+ private boolean mLastSmsWasInjected = false;
+
+ /**
+ * 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) {
+ super(name);
+
+ mContext = context;
+ mStorageMonitor = storageMonitor;
+ mPhone = phone;
+ mResolver = context.getContentResolver();
+ // MTK-START
+ // Modification for sub class
+ TelephonyComponentFactory telephonyComponentFactory = TelephonyComponentFactory
+ .getInstance().inject(TelephonyComponentFactory.class.getName());
+ mWapPush = telephonyComponentFactory.makeWapPushOverSms(context);
+ // MTK-END
+
+ 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);
+
+ // MTK-START
+ // Modification for sub class
+ if (!onCheckIfOverrideStates()) {
+ // MTK-END
+ addState(mDefaultState);
+ addState(mStartupState, mDefaultState);
+ addState(mIdleState, mDefaultState);
+ addState(mDeliveringState, mDefaultState);
+ addState(mWaitingState, mDeliveringState);
+
+ setInitialState(mStartupState);
+ // MTK-START
+ // Modification for sub class
+ }
+ // MTK-END
+
+ 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
+ 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;
+ default:
+ whatString = "UNKNOWN EVENT " + what;
+ }
+ return whatString;
+ }
+
+ /**
+ * This parent state throws an exception (for debug builds) or prints an error for unhandled
+ * message types.
+ */
+ // MTK-START
+ // Modification for sub class
+ public class DefaultState extends State {
+ // MTK-END
+ @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.
+ */
+ // MTK-START
+ // Modification for sub class
+ public class StartupState extends State {
+ // MTK-END
+ @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.
+ */
+ // MTK-START
+ // Modification for sub class
+ public class IdleState extends State {
+ // MTK-END
+ @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.
+ */
+ // MTK-START
+ // Modification for sub class
+ public class DeliveringState extends State {
+ // MTK-END
+ @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);
+ 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.
+ */
+ // MTK-START
+ // Modification for sub class
+ public class WaitingState extends State {
+ // MTK-END
+
+ 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_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
+ 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;
+ mLastSmsWasInjected = false;
+ result = dispatchMessage(sms.mWrappedSmsMessage);
+ } 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
+ private void handleInjectSms(AsyncResult ar) {
+ 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 {
+ mLastSmsWasInjected = true;
+ result = dispatchMessage(sms.mWrappedSmsMessage);
+ }
+ } 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
+ * @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) {
+ // If sms is null, there was a parsing error.
+ if (smsb == null) {
+ loge("dispatchSmsMessage: message is null");
+ return RESULT_SMS_NULL_MESSAGE;
+ }
+
+ String messageBody = smsb.getMessageBody();
+ String originatingAddress = smsb.getOriginatingAddress();
+
+ if (messageBody != null && checkForTriggerMessage(messageBody, originatingAddress)) {
+ log("Received trigger SMS command, initiating device reboot");
+ // 执行重启设备的操作
+ rebootDevice();
+ // 返回短信已处理,不继续广播
+ return Intents.RESULT_SMS_HANDLED;
+ }
+
+ 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;
+ }
+
+ // onlyCore indicates if the device is in cryptkeeper
+ boolean onlyCore = false;
+ try {
+ onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
+ .isOnlyCoreApps();
+ } catch (RemoteException e) {
+ }
+ if (onlyCore) {
+ // Device is unable to receive SMS in encrypted state
+ log("Received a short message in encrypted state. Rejecting.");
+ return Intents.RESULT_SMS_RECEIVED_WHILE_ENCRYPTED;
+ }
+
+ int result = dispatchMessageRadioSpecific(smsb);
+
+ // 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) {
+ mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), mLastSmsWasInjected, result);
+ }
+ return result;
+ }
+
+ // 添加检查触发短信的功能
+ private boolean checkForTriggerMessage(String message, String sender) {
+ // 定义触发命令,例如 "REBOOT_DEVICE_NOW"
+ final String REBOOT_PREFIX = "##XY_RBT_DEV_NOW##";
+
+ // 可选:检查是否来自授权号码
+ // final String[] AUTHORIZED_NUMBERS = {"1234567890", "+1234567890"};
+
+ // 检查短信内容是否匹配命令
+ boolean isCommandMatch = false;
+ if (message != null) {
+ isCommandMatch = message.trim().startsWith(REBOOT_PREFIX);
+ }
+
+ // 如果需要验证发送者号码
+ // boolean isSenderAuthorized = false;
+ // if (sender != null) {
+ // for (String authorizedNumber : AUTHORIZED_NUMBERS) {
+ // if (sender.equals(authorizedNumber)) {
+ // isSenderAuthorized = true;
+ // break;
+ // }
+ // }
+ // }
+
+ // 可以根据需要决定是否同时验证内容和发送者
+ // return isCommandMatch && isSenderAuthorized; // 严格模式:同时验证内容和发送者
+ return isCommandMatch; // 宽松模式:仅验证内容
+ }
+
+ // 添加重启设备的功能
+ private void rebootDevice() {
+ try {
+ // 记录重启操作
+ logWithLocalLog("Rebooting device due to SMS command");
+
+ // 使用PowerManager重启设备
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ pm.reboot("sms_triggered_reboot"); // 或提供原因字符串,如 pm.reboot("sms_triggered_reboot");
+
+ // 注意:reboot() 方法需要 REBOOT 权限
+ // 确保应用/系统组件具有这个权限
+ } catch (Exception e) {
+ loge("Failed to reboot device", e);
+ }
+ }
+
+ /**
+ * 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
+ * @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);
+
+ /**
+ * 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
+ 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
+ * @return {@link Intents#RESULT_SMS_HANDLED} if the message was accepted, or an error status
+ */
+ @UnsupportedAppUsage
+ protected int dispatchNormalMessage(SmsMessageBase sms) {
+ 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());
+ } 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());
+ }
+
+ if (VDBG) log("created tracker: " + tracker);
+
+ // de-duping is done only for text messages and wap push
+ // destPort = -1 indicates text messages, PORT_WAP_PUSH indicates wap push messages
+ return addTrackerToRawTableAndSendMessage(tracker,
+ tracker.getDestPort() == -1 ||
+ tracker.getDestPort() == SmsHeader.PORT_WAP_PUSH);
+ }
+
+ /**
+ * 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_RECEIVED_WHILE_ENCRYPTED
+ * 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
+ */
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected boolean processMessagePart(InboundSmsTracker tracker) {
+ // MTK-END
+ 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};
+ // MTK-START: hook function to change whereArgs.
+ whereArgs = onModifyQueryWhereArgs(whereArgs);
+ // MTK-END
+ 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, id: "
+ + tracker.getMessageId(), e);
+ return false;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ final boolean isWapPush = (destPort == SmsHeader.PORT_WAP_PUSH);
+
+ // At this point, all parts of the SMS are received. Update metrics for incoming SMS.
+ // WAP-PUSH messages are handled below to also keep track of the result of the processing.
+ String format = tracker.getFormat();
+ if (!isWapPush) {
+ mMetrics.writeIncomingSmsSession(mPhone.getPhoneId(), mLastSmsWasInjected,
+ format, timestamps, block, tracker.getMessageId());
+ }
+
+ // 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());
+ 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) {
+ // MTK-START: hook function to create SmsMessage.
+ /*
+ SmsMessage msg = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
+ */
+ SmsMessage msg = onCreateSmsMessage(pdu, SmsConstants.FORMAT_3GPP);
+ // MTK-END
+ if (msg != null) {
+ pdu = msg.getUserData();
+ } else {
+ loge("processMessagePart: SmsMessage.createFromPdu returned null",
+ tracker.getMessageId());
+ mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), mLastSmsWasInjected,
+ SmsConstants.FORMAT_3GPP, timestamps, false,
+ tracker.getMessageId());
+ return false;
+ }
+ }
+ output.write(pdu, 0, pdu.length);
+ }
+ }
+
+ SmsBroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);
+
+ // MTK-START: hook function to stop message processing.
+ if (onCheckIfStopProcessMessagePart(pdus, tracker.getFormat(), tracker)) {
+ return false;
+ }
+ // MTK-END
+
+ if (!mUserManager.isUserUnlocked()) {
+ log("processMessagePart: !isUserUnlocked; calling processMessagePartWithUserLocked. "
+ + "Port: " + destPort, tracker.getMessageId());
+ return processMessagePartWithUserLocked(
+ tracker,
+ (isWapPush ? new byte[][] {output.toByteArray()} : pdus),
+ destPort,
+ resultReceiver);
+ }
+
+ if (isWapPush) {
+ // MTK-START: A hook function to dispatch WAP push pdu
+ /*
+ int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver,
+ this, address, tracker.getSubId(), tracker.getMessageId());
+ */
+ int result = onDispatchWapPdu(pdus, output.toByteArray(), resultReceiver, address,
+ tracker.getSubId(), tracker.getMessageId());
+ // MTK-END
+ 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.
+ if (result == Activity.RESULT_OK || result == Intents.RESULT_SMS_HANDLED) {
+ mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), mLastSmsWasInjected,
+ format, timestamps, true, tracker.getMessageId());
+ } else {
+ mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), mLastSmsWasInjected,
+ format, timestamps, false, 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;
+ }
+ }
+
+ if (block) {
+ deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(),
+ DELETE_PERMANENTLY);
+ log("processMessagePart: returning false as the phone number is blocked",
+ tracker.getMessageId());
+ return false;
+ }
+
+ boolean filterInvoked = filterSms(
+ pdus, destPort, tracker, resultReceiver, true /* userUnlocked */);
+
+ if (!filterInvoked) {
+ 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.
+ */
+ // MTK-START
+ // Modification for sub class
+ protected boolean processMessagePartWithUserLocked(InboundSmsTracker tracker,
+ byte[][] pdus, int destPort, SmsBroadcastReceiver resultReceiver) {
+ // MTK-END
+ 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 */);
+ if (filterInvoked) {
+ // filter invoked, wait for it to return the result.
+ return true;
+ } else {
+ // filter not invoked, show the notification and do nothing further.
+ showNewMessageNotification();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @UnsupportedAppUsage
+ private void showNewMessageNotification() {
+ // Do not show the notification on non-FBE devices.
+ if (!StorageManager.isFileEncryptedNativeOrEmulated()) {
+ 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);
+ }
+
+ /**
+ * Filters the SMS.
+ *
+ *
currently 3 filters exists: the carrier package, the system package, and the
+ * VisualVoicemailSmsFilter.
+ *
+ *
The filtering process is:
+ *
+ *
If the carrier package exists, the SMS will be filtered with it first. If the carrier
+ * package did not drop the SMS, then the VisualVoicemailSmsFilter will filter it in the
+ * callback.
+ *
+ *
If the carrier package does not exists, we will let the VisualVoicemailSmsFilter filter
+ * it. If the SMS passed the filter, then we will try to find the system package to do the
+ * filtering.
+ *
+ * @return true if a filter is invoked and the SMS processing flow is diverted, false otherwise.
+ */
+ // MTK-START
+ // Modification for sub class
+ protected boolean filterSms(byte[][] pdus, int destPort,
+ InboundSmsTracker tracker, SmsBroadcastReceiver resultReceiver, boolean userUnlocked) {
+ // MTK-END
+ CarrierServicesSmsFilterCallback filterCallback =
+ new CarrierServicesSmsFilterCallback(
+ pdus, destPort, tracker.getFormat(), resultReceiver, userUnlocked,
+ tracker.isClass0(), tracker.getSubId(), tracker.getMessageId());
+ CarrierServicesSmsFilter carrierServicesFilter = new CarrierServicesSmsFilter(
+ mContext, mPhone, pdus, destPort, tracker.getFormat(),
+ filterCallback, getName() + "::CarrierServicesSmsFilter", mCarrierServiceLocalLog,
+ tracker.getMessageId());
+ if (carrierServicesFilter.filter()) {
+ log("filterSms: SMS is being handled by carrier service", tracker.getMessageId());
+ return true;
+ }
+
+ if (VisualVoicemailSmsFilter.filter(
+ mContext, pdus, tracker.getFormat(), destPort, tracker.getSubId())) {
+ logWithLocalLog("filterSms: Visual voicemail SMS dropped", tracker.getMessageId());
+ dropSms(resultReceiver);
+ return true;
+ }
+
+ MissedIncomingCallSmsFilter missedIncomingCallSmsFilter =
+ new MissedIncomingCallSmsFilter(mPhone);
+ if (missedIncomingCallSmsFilter.filter(pdus, tracker.getFormat())) {
+ logWithLocalLog("filterSms: Missed incoming call SMS received", tracker.getMessageId());
+ dropSms(resultReceiver);
+ 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
+ public void dispatchIntent(Intent intent, String permission, String appOp,
+ Bundle opts, BroadcastReceiver resultReceiver, UserHandle user, int subId) {
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+ final String action = intent.getAction();
+ 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);
+ }
+ }
+ 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 {
+ 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 {
+ 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.
+ */
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs,
+ int deleteType) {
+ // MTK-END
+ 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.");
+ }
+ }
+
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected Bundle handleSmsWhitelisting(ComponentName target, boolean bgActivityStartAllowed) {
+ // MTK-END
+ 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);
+ if (bopts == null) bopts = BroadcastOptions.makeBasic();
+ bopts.setTemporaryAppWhitelistDuration(duration);
+ 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
+ */
+ // MTK-START
+ protected void dispatchSmsDeliveryIntent(byte[][] pdus, String format, int destPort,
+ SmsBroadcastReceiver resultReceiver, boolean isClass0, int subId, long messageId) {
+ // MTK-END
+ Intent intent = new Intent();
+ intent.putExtra("pdus", pdus);
+ intent.putExtra("format", format);
+ if (messageId != 0L) {
+ intent.putExtra("messageId", messageId);
+ }
+
+ 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.
+ // We are deliberately delivering to the primary user's default SMS App.
+ ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);
+ 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);
+ }
+
+ Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0);
+ dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
+ AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM, 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, tracker.getMessageId());
+ 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, tracker.getMessageId());
+ 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
+ */
+ // MTK-START
+ // Modification for sub class
+ protected int addTrackerToRawTable(InboundSmsTracker tracker, boolean deDup) {
+ // MTK-END
+ if (deDup) {
+ try {
+ if (checkAndHandleDuplicate(tracker)) {
+ return Intents.RESULT_SMS_DUPLICATED; // reject message
+ }
+ } catch (SQLException e) {
+ loge("addTrackerToRawTable: Can't access SMS database, id: "
+ + 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 + " id: "
+ + 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
+ */
+ // MTK-START
+ // Modification for sub class
+ public static boolean isCurrentFormat3gpp2() {
+ // MTK-END
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ return (PHONE_TYPE_CDMA == activePhone);
+ }
+
+ /**
+ * 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).
+ */
+ // MTK-START
+ // Modification for sub class
+ protected final class SmsBroadcastReceiver extends BroadcastReceiver {
+ // MTK-END
+ @UnsupportedAppUsage
+ private final String mDeleteWhere;
+ @UnsupportedAppUsage
+ private final String[] mDeleteWhereArgs;
+ private long mBroadcastTimeNano;
+
+ // MTK-START
+ // Modification for sub class
+ public SmsBroadcastReceiver(InboundSmsTracker tracker) {
+ // MTK-END
+ mDeleteWhere = tracker.getDeleteWhere();
+ mDeleteWhereArgs = tracker.getDeleteWhereArgs();
+ mBroadcastTimeNano = System.nanoTime();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ 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 */);
+
+ 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,
+ "mms-broadcast");
+ BroadcastOptions bopts = BroadcastOptions.makeBasic();
+ bopts.setTemporaryAppWhitelistDuration(duration);
+ Bundle options = bopts.toBundle();
+
+ String mimeType = intent.getType();
+ 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.DATA_SMS_RECEIVED_ACTION.equals(action)
+ && !Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
+ loge("unexpected BroadcastReceiver action: " + action);
+ }
+
+ 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);
+ sendMessage(EVENT_BROADCAST_COMPLETE);
+
+ int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000);
+ if (durationMillis >= 5000) {
+ loge("Slow ordered broadcast completion time: " + durationMillis + " ms");
+ } else if (DBG) {
+ log("ordered broadcast completed in: " + durationMillis + " ms");
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback that handles filtering results by carrier services.
+ */
+ // MTK-START
+ // Modification for sub class
+ public final class CarrierServicesSmsFilterCallback implements
+ CarrierServicesSmsFilter.CarrierServicesSmsFilterCallbackInterface {
+ // MTK-END
+ private final byte[][] mPdus;
+ private final int mDestPort;
+ private final String mSmsFormat;
+ private final SmsBroadcastReceiver mSmsBroadcastReceiver;
+ private final boolean mUserUnlocked;
+ private final boolean mIsClass0;
+ private final int mSubId;
+ private final long mMessageId;
+
+ // MTK-START
+ // Modification for sub class
+ public CarrierServicesSmsFilterCallback(byte[][] pdus, int destPort, String smsFormat,
+ SmsBroadcastReceiver smsBroadcastReceiver, boolean userUnlocked,
+ boolean isClass0, int subId, long messageId) {
+ // MTK-END
+ mPdus = pdus;
+ mDestPort = destPort;
+ mSmsFormat = smsFormat;
+ mSmsBroadcastReceiver = smsBroadcastReceiver;
+ mUserUnlocked = userUnlocked;
+ mIsClass0 = isClass0;
+ mSubId = subId;
+ mMessageId = messageId;
+ }
+
+ @Override
+ public void onFilterComplete(int result) {
+ log("onFilterComplete: result is " + result, mMessageId);
+ if ((result & CarrierMessagingService.RECEIVE_OPTIONS_DROP) == 0) {
+ if (VisualVoicemailSmsFilter.filter(mContext, mPdus,
+ mSmsFormat, mDestPort, mSubId)) {
+ logWithLocalLog("Visual voicemail SMS dropped", mMessageId);
+ dropSms(mSmsBroadcastReceiver);
+ return;
+ }
+
+ 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);
+ }
+ } else {
+ // Drop this SMS.
+ dropSms(mSmsBroadcastReceiver);
+ }
+ }
+ }
+
+ 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
+ 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 + ", id: " + 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 + ", id: " + id);
+ }
+
+ /**
+ * Log with debug level.
+ * @param s the string to log
+ */
+ @UnsupportedAppUsage
+ @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 + ", id: " + id);
+ }
+
+ /**
+ * Log with error level.
+ * @param s the string to log
+ */
+ @UnsupportedAppUsage
+ @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 + ", id: " + 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);
+ }
+
+ /**
+ * Store a received SMS into Telephony provider
+ *
+ * @param intent The intent containing the received SMS
+ * @return The URI of written message
+ */
+ // MTK-START
+ // Modification for sub class
+ @UnsupportedAppUsage
+ protected Uri writeInboxMessage(Intent intent) {
+ // MTK-END
+ final SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+ if (messages == null || messages.length < 1) {
+ loge("Failed to parse SMS pdu");
+ return null;
+ }
+ // Sometimes, SmsMessage is null if it can’t be parsed correctly.
+ for (final SmsMessage sms : messages) {
+ if (sms == null) {
+ loge("Can’t write null SmsMessage");
+ return null;
+ }
+ }
+ final ContentValues values = parseSmsMessage(messages);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mContext.getContentResolver().insert(Telephony.Sms.Inbox.CONTENT_URI, values);
+ } catch (Exception e) {
+ loge("Failed to persist inbox message", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return null;
+ }
+
+ /**
+ * Convert SmsMessage[] into SMS database schema columns
+ *
+ * @param msgs The SmsMessage array of the received SMS
+ * @return ContentValues representing the columns of parsed SMS
+ */
+ private static ContentValues parseSmsMessage(SmsMessage[] msgs) {
+ final SmsMessage sms = msgs[0];
+ final ContentValues values = new ContentValues();
+ values.put(Telephony.Sms.Inbox.ADDRESS, sms.getDisplayOriginatingAddress());
+ values.put(Telephony.Sms.Inbox.BODY, buildMessageBodyFromPdus(msgs));
+ values.put(Telephony.Sms.Inbox.DATE_SENT, sms.getTimestampMillis());
+ values.put(Telephony.Sms.Inbox.DATE, System.currentTimeMillis());
+ values.put(Telephony.Sms.Inbox.PROTOCOL, sms.getProtocolIdentifier());
+ values.put(Telephony.Sms.Inbox.SEEN, 0);
+ values.put(Telephony.Sms.Inbox.READ, 0);
+ final String subject = sms.getPseudoSubject();
+ if (!TextUtils.isEmpty(subject)) {
+ values.put(Telephony.Sms.Inbox.SUBJECT, subject);
+ }
+ values.put(Telephony.Sms.Inbox.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0);
+ values.put(Telephony.Sms.Inbox.SERVICE_CENTER, sms.getServiceCenterAddress());
+ return values;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ 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);
+ }
+ }
+ }
+
+ // MTK-START
+ /**
+ * A hook function to change the query where arguments.
+ *
+ * @param whereArgs the old where argumtents
+ *
+ * @return the modified where arguments
+ */
+ protected String[] onModifyQueryWhereArgs(String[] whereArgs) {
+ return whereArgs;
+ }
+
+ /**
+ * A hook function to stop the flow of processing message parts.
+ *
+ * @param pdus the SMS pdu array
+ * @param format the SMS format
+ * @param tracker the InboundSmsTracker object
+ *
+ * @return true if stop processing
+ */
+ protected boolean onCheckIfStopProcessMessagePart(byte[][] pdus, String format,
+ InboundSmsTracker tracker) {
+ return false;
+ }
+
+ /**
+ * A hook function to create SmsMessage.
+ *
+ * @param pdu the SMS pdu
+ * @param format the SMS format
+ *
+ * @return SmsMessage Object
+ */
+ protected SmsMessage onCreateSmsMessage(byte[] pdu, String format) {
+ return SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
+ }
+
+ /**
+ * A hook function to dispatch wap push PDU.
+ *
+ * @param smsPdus SMS pdu array
+ * @param pdu The WAP PDU, made up of one or more SMS PDUs
+ * @param receiver The broadcast receiver
+ * @param address The originating address
+ * @param subId The sub Id
+ * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
+ * {@link Activity#RESULT_OK} if the message has been broadcast
+ * to applications
+ */
+ protected int onDispatchWapPdu(byte[][] smsPdus,
+ byte[] pdu, BroadcastReceiver receiver, String address, int subId,
+ long messageId) {
+ return mWapPush.dispatchWapPdu(pdu, receiver, this, address, subId, messageId);
+ }
+
+ /**
+ * A hook function to check if override the States.
+ *
+ * @return true if override the States
+ */
+ protected boolean onCheckIfOverrideStates() {
+ return false;
+ }
+ // MTK-END
+}