Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e5019ead authored by Brad Ebinger's avatar Brad Ebinger Committed by Gerrit Code Review
Browse files

Merge changes from topics "ims_sms_sms_ready", "ims_sms_rm_internal_sms",...

Merge changes from topics "ims_sms_sms_ready", "ims_sms_rm_internal_sms", "ims_sms_reg", "ims_sms_disp"

* changes:
  Add and trigger onReady API for SMS over IMS
  Fix accidental use of internal version of SMS APIs.
  Integrate IMS registration with ImsSmsDispatcher.
  Fix ImsSmsDispatcherTest
  Implement ImsSmsDispatcher
  Refactor SMSDispatcher and friends.
parents d76d0e11 8c1fcae0
Loading
Loading
Loading
Loading
+26 −15
Original line number Original line Diff line number Diff line
@@ -83,7 +83,7 @@ public class IccSmsInterfaceManager {
    final protected Context mContext;
    final protected Context mContext;
    final protected AppOpsManager mAppOps;
    final protected AppOpsManager mAppOps;
    final private UserManager mUserManager;
    final private UserManager mUserManager;
    protected SMSDispatcher mDispatcher;
    protected SmsDispatchersController mDispatchersController;


    protected Handler mHandler = new Handler() {
    protected Handler mHandler = new Handler() {
        @Override
        @Override
@@ -131,7 +131,7 @@ public class IccSmsInterfaceManager {
        mContext = phone.getContext();
        mContext = phone.getContext();
        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        mDispatcher = new ImsSMSDispatcher(phone,
        mDispatchersController = new SmsDispatchersController(phone,
                phone.mSmsStorageMonitor, phone.mSmsUsageMonitor);
                phone.mSmsStorageMonitor, phone.mSmsUsageMonitor);
    }
    }


@@ -170,7 +170,7 @@ public class IccSmsInterfaceManager {


    protected void updatePhoneObject(Phone phone) {
    protected void updatePhoneObject(Phone phone) {
        mPhone = phone;
        mPhone = phone;
        mDispatcher.updatePhoneObject(phone);
        mDispatchersController.updatePhoneObject(phone);
    }
    }


    protected void enforceReceiveAndSend(String message) {
    protected void enforceReceiveAndSend(String message) {
@@ -379,7 +379,8 @@ public class IccSmsInterfaceManager {
            return;
            return;
        }
        }
        destAddr = filterDestAddress(destAddr);
        destAddr = filterDestAddress(destAddr);
        mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
        mDispatchersController.sendData(destAddr, scAddr, destPort, data, sentIntent,
                deliveryIntent);
    }
    }


    /**
    /**
@@ -451,7 +452,7 @@ public class IccSmsInterfaceManager {
            enforcePrivilegedAppPermissions();
            enforcePrivilegedAppPermissions();
        }
        }
        destAddr = filterDestAddress(destAddr);
        destAddr = filterDestAddress(destAddr);
        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
        mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
    }
    }


@@ -472,7 +473,17 @@ public class IccSmsInterfaceManager {
                "\n format=" + format +
                "\n format=" + format +
                "\n receivedIntent=" + receivedIntent);
                "\n receivedIntent=" + receivedIntent);
        }
        }
        mDispatcher.injectSmsPdu(pdu, format, receivedIntent);
        mDispatchersController.injectSmsPdu(pdu, format,
                result -> {
                    if (receivedIntent != null) {
                        try {
                            receivedIntent.send(result);
                        } catch (PendingIntent.CanceledException e) {
                            Rlog.d(LOG_TAG, "receivedIntent cancelled.");
                        }
                    }
                }
        );
    }
    }


    /**
    /**
@@ -546,7 +557,7 @@ public class IccSmsInterfaceManager {
                    singleDeliveryIntent = deliveryIntents.get(i);
                    singleDeliveryIntent = deliveryIntents.get(i);
                }
                }


                mDispatcher.sendText(destAddr, scAddr, singlePart,
                mDispatchersController.sendText(destAddr, scAddr, singlePart,
                        singleSentIntent, singleDeliveryIntent,
                        singleSentIntent, singleDeliveryIntent,
                        null/*messageUri*/, callingPackage,
                        null/*messageUri*/, callingPackage,
                        persistMessageForNonDefaultSmsApp);
                        persistMessageForNonDefaultSmsApp);
@@ -554,19 +565,19 @@ public class IccSmsInterfaceManager {
            return;
            return;
        }
        }


        mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
        mDispatchersController.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents,
                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents,
                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
    }
    }




    public int getPremiumSmsPermission(String packageName) {
    public int getPremiumSmsPermission(String packageName) {
        return mDispatcher.getPremiumSmsPermission(packageName);
        return mDispatchersController.getPremiumSmsPermission(packageName);
    }
    }




    public void setPremiumSmsPermission(String packageName, int permission) {
    public void setPremiumSmsPermission(String packageName, int permission) {
        mDispatcher.setPremiumSmsPermission(packageName, permission);
        mDispatchersController.setPremiumSmsPermission(packageName, permission);
    }
    }


    /**
    /**
@@ -919,11 +930,11 @@ public class IccSmsInterfaceManager {
    }
    }


    public boolean isImsSmsSupported() {
    public boolean isImsSmsSupported() {
        return mDispatcher.isIms();
        return mDispatchersController.isIms();
    }
    }


    public String getImsSmsFormat() {
    public String getImsSmsFormat() {
        return mDispatcher.getImsSmsFormat();
        return mDispatchersController.getImsSmsFormat();
    }
    }


    public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
    public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
@@ -951,7 +962,7 @@ public class IccSmsInterfaceManager {
            return;
            return;
        }
        }
        textAndAddress[1] = filterDestAddress(textAndAddress[1]);
        textAndAddress[1] = filterDestAddress(textAndAddress[1]);
        mDispatcher.sendText(textAndAddress[1], scAddress, textAndAddress[0],
        mDispatchersController.sendText(textAndAddress[1], scAddress, textAndAddress[0],
                sentIntent, deliveryIntent, messageUri, callingPkg,
                sentIntent, deliveryIntent, messageUri, callingPkg,
                true /* persistMessageForNonDefaultSmsApp */);
                true /* persistMessageForNonDefaultSmsApp */);
    }
    }
@@ -1007,14 +1018,14 @@ public class IccSmsInterfaceManager {
                    singleDeliveryIntent = deliveryIntents.get(i);
                    singleDeliveryIntent = deliveryIntents.get(i);
                }
                }


                mDispatcher.sendText(textAndAddress[1], scAddress, singlePart,
                mDispatchersController.sendText(textAndAddress[1], scAddress, singlePart,
                        singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
                        singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
                        true  /* persistMessageForNonDefaultSmsApp */);
                        true  /* persistMessageForNonDefaultSmsApp */);
            }
            }
            return;
            return;
        }
        }


        mDispatcher.sendMultipartText(
        mDispatchersController.sendMultipartText(
                textAndAddress[1], // destAddress
                textAndAddress[1], // destAddress
                scAddress,
                scAddress,
                parts,
                parts,
+396 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.app.Activity;
import android.os.RemoteException;
import android.os.Message;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.provider.Telephony.Sms;
import android.content.Intent;
import android.telephony.Rlog;

import com.android.ims.ImsException;
import com.android.ims.ImsManager;
import com.android.ims.ImsServiceProxy;
import com.android.ims.internal.IImsSmsListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.util.SMSDispatcherUtil;
import com.android.internal.telephony.gsm.SmsMessage;

import android.telephony.ims.internal.feature.ImsFeature;
import android.telephony.ims.internal.feature.MmTelFeature;
import android.telephony.ims.internal.stub.SmsImplBase;
import android.telephony.ims.internal.stub.SmsImplBase.SendStatusResult;
import android.telephony.ims.internal.stub.SmsImplBase.StatusReportResult;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.provider.Telephony.Sms.Intents;
import android.util.Pair;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Responsible for communications with {@link com.android.ims.ImsManager} to send/receive messages
 * over IMS.
 */
public class ImsSmsDispatcher extends SMSDispatcher {
    // Initial condition for ims connection retry.
    private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms
    // Ceiling bitshift amount for service query timeout, calculated as:
    // 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where
    // mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT].
    private static final int CEILING_SERVICE_RETRY_COUNT = 6;

    @VisibleForTesting
    public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>();
    @VisibleForTesting
    public AtomicInteger mNextToken = new AtomicInteger();
    private final Object mLock = new Object();
    private volatile boolean mIsSmsCapable;
    private volatile boolean mIsImsServiceUp;
    private volatile boolean mIsRegistered;
    private volatile int mImsServiceRetryCount;

    /**
     * Default implementation of interface that calculates the ImsService retry timeout.
     * Override-able for testing.
     */
    private IRetryTimeout mRetryTimeout = () -> {
        int timeout = (1 << mImsServiceRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS;
        if (mImsServiceRetryCount <= CEILING_SERVICE_RETRY_COUNT) {
            mImsServiceRetryCount++;
        }
        return timeout;
    };

    /**
     * Listen to the IMS service state change
     *
     */
    private ImsRegistrationImplBase.Callback mRegistrationCallback =
            new ImsRegistrationImplBase.Callback() {
                @Override
                public void onRegistered(
                        @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
                    Rlog.d(TAG, "onImsConnected imsRadioTech=" + imsRadioTech);
                    synchronized (mLock) {
                        mIsRegistered = true;
                    }
                }

                @Override
                public void onRegistering(
                        @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
                    Rlog.d(TAG, "onImsProgressing imsRadioTech=" + imsRadioTech);
                    synchronized (mLock) {
                        mIsRegistered = false;
                    }
                }

                @Override
                public void onDeregistered(com.android.ims.ImsReasonInfo info) {
                    Rlog.d(TAG, "onImsDisconnected imsReasonInfo=" + info);
                    synchronized (mLock) {
                        mIsRegistered = false;
                    }
                }
            };

    private ImsFeature.CapabilityCallback mCapabilityCallback =
            new ImsFeature.CapabilityCallback() {
                @Override
                public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
                    synchronized (mLock) {
                        mIsSmsCapable = config.isCapable(
                                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS);
                    }
                }
    };

    // Callback fires when ImsManager MMTel Feature changes state
    private ImsServiceProxy.IFeatureUpdate mNotifyStatusChangedCallback =
            new ImsServiceProxy.IFeatureUpdate() {
                @Override
                public void notifyStateChanged() {
                    try {
                        int status = getImsManager().getImsServiceStatus();
                        Rlog.d(TAG, "Status Changed: " + status);
                        switch (status) {
                            case android.telephony.ims.feature.ImsFeature.STATE_READY: {
                                synchronized (mLock) {
                                    setListeners();
                                    mIsImsServiceUp = true;
                                }
                                break;
                            }
                            case android.telephony.ims.feature.ImsFeature.STATE_INITIALIZING:
                                // fall through
                            case android.telephony.ims.feature.ImsFeature.STATE_NOT_AVAILABLE:
                                synchronized (mLock) {
                                    mIsImsServiceUp = false;
                                }
                                break;
                            default: {
                                Rlog.w(TAG, "Unexpected State!");
                            }
                        }
                    } catch (ImsException e) {
                        // Could not get the ImsService, retry!
                        retryGetImsService();
                    }
                }

                @Override
                public void notifyUnavailable() {
                    retryGetImsService();
                }
            };

    private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() {
        @Override
        public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
                int reason) throws RemoteException {
            SmsTracker tracker = mTrackers.get(token);
            if (tracker == null) {
                throw new IllegalArgumentException("Invalid token.");
            }
            switch(reason) {
                case SmsImplBase.SEND_STATUS_OK:
                    tracker.onSent(mContext);
                    break;
                case SmsImplBase.SEND_STATUS_ERROR:
                    tracker.onFailed(mContext, reason, 0 /* errorCode */);
                    mTrackers.remove(token);
                    break;
                case SmsImplBase.SEND_STATUS_ERROR_RETRY:
                    tracker.mRetryCount += 1;
                    sendSms(tracker);
                    break;
                case SmsImplBase.SEND_STATUS_ERROR_FALLBACK:
                    fallbackToPstn(token, tracker);
                    break;
                default:
            }
        }

        @Override
        public void onSmsStatusReportReceived(int token, int messageRef, String format, byte[] pdu)
                throws RemoteException {
            Rlog.d(TAG, "Status report received.");
            SmsTracker tracker = mTrackers.get(token);
            if (tracker == null) {
                throw new RemoteException("Invalid token.");
            }
            Pair<Boolean, Boolean> result = mSmsDispatchersController.handleSmsStatusReport(
                    tracker, format, pdu);
            Rlog.d(TAG, "Status report handle result, success: " + result.first +
                    "complete: " + result.second);
            try {
                getImsManager().acknowledgeSmsReport(
                        token,
                        messageRef,
                        result.first ? SmsImplBase.STATUS_REPORT_STATUS_OK
                                : SmsImplBase.STATUS_REPORT_STATUS_ERROR);
            } catch (ImsException e) {
                Rlog.e(TAG, "Failed to acknowledgeSmsReport(). Error: "
                        + e.getMessage());
            }
            if (result.second) {
                mTrackers.remove(token);
            }
        }

        @Override
        public void onSmsReceived(int token, String format, byte[] pdu)
                throws RemoteException {
            Rlog.d(TAG, "SMS received.");
            mSmsDispatchersController.injectSmsPdu(pdu, format, result -> {
                Rlog.d(TAG, "SMS handled result: " + result);
                try {
                    getImsManager().acknowledgeSms(token,
                            0,
                            result == Intents.RESULT_SMS_HANDLED
                                    ? SmsImplBase.STATUS_REPORT_STATUS_OK
                                    : SmsImplBase.DELIVER_STATUS_ERROR);
                } catch (ImsException e) {
                    Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage());
                }
            });
        }
    };

    public ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController) {
        super(phone, smsDispatchersController);

        mImsServiceRetryCount = 0;
        // Send a message to connect to the Ims Service and open a connection through
        // getImsService().
        sendEmptyMessage(EVENT_GET_IMS_SERVICE);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_GET_IMS_SERVICE:
                try {
                    getImsService();
                } catch (ImsException e) {
                    Rlog.e(TAG, "setListeners: " + e);
                    retryGetImsService();
                }
                break;
            default:
                super.handleMessage(msg);
        }
    }

    private void getImsService() throws ImsException {
        Rlog.d(TAG, "getImsService");
        // Adding to set, will be safe adding multiple times. If the ImsService is not active yet,
        // this method will throw an ImsException.
        getImsManager().addNotifyStatusChangedCallbackIfAvailable(mNotifyStatusChangedCallback);
        // Wait for ImsService.STATE_READY to start listening for SMS.
        // Call the callback right away for compatibility with older devices that do not use states.
        mNotifyStatusChangedCallback.notifyStateChanged();
    }

    private void setListeners() throws ImsException {
        getImsManager().addRegistrationCallback(mRegistrationCallback);
        getImsManager().addCapabilitiesCallback(mCapabilityCallback);
        getImsManager().setSmsListener(mImsSmsListener);
        getImsManager().onSmsReady();
        mImsServiceRetryCount = 0;
    }

    private void retryGetImsService() {
        // The binder connection is already up. Do not try to get it again.
        if (getImsManager().isServiceAvailable()) {
            return;
        }
        // remove callback so we do not receive updates from old ImsServiceProxy when switching
        // between ImsServices.
        getImsManager().removeNotifyStatusChangedCallback(mNotifyStatusChangedCallback);
        // Exponential backoff during retry, limited to 32 seconds.
        Rlog.e(TAG, "getImsService: Retrying getting ImsService...");
        removeMessages(EVENT_GET_IMS_SERVICE);
        sendEmptyMessageDelayed(EVENT_GET_IMS_SERVICE, mRetryTimeout.get());
    }

    public boolean isAvailable() {
        synchronized (mLock) {
            return mIsImsServiceUp && mIsRegistered && mIsSmsCapable;
        }
    }

    @Override
    protected String getFormat() {
        try {
            return getImsManager().getSmsFormat();
        } catch (ImsException e) {
            Rlog.e(TAG, "Failed to get sms format. Error: " + e.getMessage());
            return SmsConstants.FORMAT_UNKNOWN;
        }
    }

    @Override
    protected boolean shouldBlockSms() {
        return SMSDispatcherUtil.shouldBlockSms(isCdmaMo(), mPhone);
    }

    @Override
    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
            String message, boolean statusReportRequested, SmsHeader smsHeader) {
        return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message,
                statusReportRequested, smsHeader);
    }

    @Override
    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
            int destPort, byte[] message, boolean statusReportRequested) {
        return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message,
                statusReportRequested);
    }

    @Override
    protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
        return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly);
    }

    @Override
    public void sendSms(SmsTracker tracker) {
        Rlog.d(TAG, "sendSms: "
                + " mRetryCount=" + tracker.mRetryCount
                + " mMessageRef=" + tracker.mMessageRef
                + " SS=" + mPhone.getServiceState().getState());

        HashMap<String, Object> map = tracker.getData();

        byte[] pdu = (byte[]) map.get(MAP_KEY_PDU);
        byte smsc[] = (byte[]) map.get(MAP_KEY_SMSC);
        boolean isRetry = tracker.mRetryCount > 0;

        if (SmsConstants.FORMAT_3GPP.equals(getFormat()) && tracker.mRetryCount > 0) {
            // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
            //   TP-RD (bit 2) is 1 for retry
            //   and TP-MR is set to previously failed sms TP-MR
            if (((0x01 & pdu[0]) == 0x01)) {
                pdu[0] |= 0x04; // TP-RD
                pdu[1] = (byte) tracker.mMessageRef; // TP-MR
            }
        }

        int token = mNextToken.incrementAndGet();
        mTrackers.put(token, tracker);
        try {
            getImsManager().sendSms(
                    token,
                    tracker.mMessageRef,
                    getFormat(),
                    smsc != null ? new String(smsc) : null,
                    isRetry,
                    pdu);
        } catch (ImsException e) {
            Rlog.e(TAG, "sendSms failed. Falling back to PSTN. Error: " + e.getMessage());
            fallbackToPstn(token, tracker);
        }
    }

    private ImsManager getImsManager() {
        return ImsManager.getInstance(mContext, mPhone.getPhoneId());
    }

    @VisibleForTesting
    public void fallbackToPstn(int token, SmsTracker tracker) {
        mSmsDispatchersController.sendRetrySms(tracker);
        mTrackers.remove(token);
    }

    @Override
    protected boolean isCdmaMo() {
        return mSmsDispatchersController.isCdmaFormat(getFormat());
    }

    @VisibleForTesting
    public interface IRetryTimeout {
        int get();
    }
}
+4 −6
Original line number Original line Diff line number Diff line
@@ -603,9 +603,9 @@ public abstract class InboundSmsHandler extends StateMachine {
     */
     */
    private void handleInjectSms(AsyncResult ar) {
    private void handleInjectSms(AsyncResult ar) {
        int result;
        int result;
        PendingIntent receivedIntent = null;
        SmsDispatchersController.SmsInjectionCallback callback = null;
        try {
        try {
            receivedIntent = (PendingIntent) ar.userObj;
            callback = (SmsDispatchersController.SmsInjectionCallback) ar.userObj;
            SmsMessage sms = (SmsMessage) ar.result;
            SmsMessage sms = (SmsMessage) ar.result;
            if (sms == null) {
            if (sms == null) {
              result = Intents.RESULT_SMS_GENERIC_ERROR;
              result = Intents.RESULT_SMS_GENERIC_ERROR;
@@ -617,10 +617,8 @@ public abstract class InboundSmsHandler extends StateMachine {
            result = Intents.RESULT_SMS_GENERIC_ERROR;
            result = Intents.RESULT_SMS_GENERIC_ERROR;
        }
        }


        if (receivedIntent != null) {
        if (callback != null) {
            try {
            callback.onSmsInjectedResult(result);
                receivedIntent.send(result);
            } catch (CanceledException e) { }
        }
        }
    }
    }


+191 −112

File changed.

Preview size limit exceeded, changes collapsed.

+562 −0

File changed and moved.

Preview size limit exceeded, changes collapsed.

Loading