1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.internal.telephony;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.os.Binder;
22 import android.os.Message;
23 import android.os.PersistableBundle;
24 import android.os.RemoteException;
25 import android.provider.Telephony.Sms.Intents;
26 import android.telephony.CarrierConfigManager;
27 import android.telephony.ServiceState;
28 import android.telephony.SmsManager;
29 import android.telephony.ims.ImsReasonInfo;
30 import android.telephony.ims.RegistrationManager;
31 import android.telephony.ims.aidl.IImsSmsListener;
32 import android.telephony.ims.feature.MmTelFeature;
33 import android.telephony.ims.stub.ImsRegistrationImplBase;
34 import android.telephony.ims.stub.ImsSmsImplBase;
35 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult;
36 
37 import com.android.ims.FeatureConnector;
38 import com.android.ims.ImsException;
39 import com.android.ims.ImsManager;
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
42 import com.android.internal.telephony.analytics.TelephonyAnalytics;
43 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
44 import com.android.internal.telephony.metrics.TelephonyMetrics;
45 import com.android.internal.telephony.uicc.IccUtils;
46 import com.android.internal.telephony.util.SMSDispatcherUtil;
47 import com.android.telephony.Rlog;
48 
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.concurrent.ConcurrentHashMap;
54 import java.util.concurrent.Executor;
55 import java.util.concurrent.atomic.AtomicInteger;
56 
57 /**
58  * Responsible for communications with {@link com.android.ims.ImsManager} to send/receive messages
59  * over IMS.
60  * @hide
61  */
62 public class ImsSmsDispatcher extends SMSDispatcher {
63 
64     private static final String TAG = "ImsSmsDispatcher";
65     private static final int CONNECT_DELAY_MS = 5000; // 5 seconds;
66     public static final int MAX_SEND_RETRIES_OVER_IMS = MAX_SEND_RETRIES;
67 
68     /**
69      * Creates FeatureConnector instances for ImsManager, used during testing to inject mock
70      * connector instances.
71      */
72     @VisibleForTesting
73     public interface FeatureConnectorFactory {
74         /**
75          * Create a new FeatureConnector for ImsManager.
76          */
create(Context context, int phoneId, String logPrefix, FeatureConnector.Listener<ImsManager> listener, Executor executor)77         FeatureConnector<ImsManager> create(Context context, int phoneId, String logPrefix,
78                 FeatureConnector.Listener<ImsManager> listener, Executor executor);
79     }
80 
81     public List<Integer> mMemoryAvailableNotifierList = new ArrayList<Integer>();
82     @VisibleForTesting
83     public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>();
84     @VisibleForTesting
85     public AtomicInteger mNextToken = new AtomicInteger();
86     private final Object mLock = new Object();
87     private volatile boolean mIsSmsCapable;
88     private volatile boolean mIsImsServiceUp;
89     private volatile boolean mIsRegistered;
90     private final FeatureConnector<ImsManager> mImsManagerConnector;
91     /** Telephony metrics instance for logging metrics event */
92     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
93     private ImsManager mImsManager;
94     private FeatureConnectorFactory mConnectorFactory;
95 
96     private Runnable mConnectRunnable = new Runnable() {
97         @Override
98         public void run() {
99             mImsManagerConnector.connect();
100         }
101     };
102 
103     /**
104      * Listen to the IMS service state change
105      *
106      */
107     private RegistrationManager.RegistrationCallback mRegistrationCallback =
108             new RegistrationManager.RegistrationCallback() {
109                 @Override
110                 public void onRegistered(
111                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
112                     logd("onImsConnected imsRadioTech=" + imsRadioTech);
113                     synchronized (mLock) {
114                         mIsRegistered = true;
115                     }
116                 }
117 
118                 @Override
119                 public void onRegistering(
120                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
121                     logd("onImsProgressing imsRadioTech=" + imsRadioTech);
122                     synchronized (mLock) {
123                         mIsRegistered = false;
124                     }
125                 }
126 
127                 @Override
128                 public void onUnregistered(ImsReasonInfo info) {
129                     logd("onImsDisconnected imsReasonInfo=" + info);
130                     synchronized (mLock) {
131                         mIsRegistered = false;
132                     }
133                 }
134             };
135 
136     private android.telephony.ims.ImsMmTelManager.CapabilityCallback mCapabilityCallback =
137             new android.telephony.ims.ImsMmTelManager.CapabilityCallback() {
138                 @Override
139                 public void onCapabilitiesStatusChanged(
140                         MmTelFeature.MmTelCapabilities capabilities) {
141                     synchronized (mLock) {
142                         mIsSmsCapable = capabilities.isCapable(
143                                 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS);
144                     }
145                 }
146     };
147 
148     private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() {
149         @Override
150         public void onMemoryAvailableResult(int token, @SendStatusResult int status,
151                 int networkReasonCode) {
152             final long identity = Binder.clearCallingIdentity();
153             try {
154                 logd("onMemoryAvailableResult token=" + token + " status=" + status
155                         + " networkReasonCode=" + networkReasonCode);
156                 if (!mMemoryAvailableNotifierList.contains(token)) {
157                     loge("onMemoryAvailableResult Invalid token");
158                     return;
159                 }
160                 mMemoryAvailableNotifierList.remove(Integer.valueOf(token));
161 
162                 /**
163                  * The Retrans flag is set and reset As per section 6.3.3.1.2 in TS 124011
164                  * Note: Assuming that SEND_STATUS_ERROR_RETRY is sent in case of temporary failure
165                  */
166                 if (status ==  ImsSmsImplBase.SEND_STATUS_ERROR_RETRY) {
167                     if (!mRPSmmaRetried) {
168                         sendMessageDelayed(obtainMessage(EVENT_RETRY_SMMA), SEND_RETRY_DELAY);
169                         mRPSmmaRetried = true;
170                     } else {
171                         mRPSmmaRetried = false;
172                     }
173                 } else {
174                     mRPSmmaRetried = false;
175                 }
176             } finally {
177                 Binder.restoreCallingIdentity(identity);
178             }
179         }
180         @Override
181         public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
182                 @SmsManager.Result int reason, int networkReasonCode) {
183             final long identity = Binder.clearCallingIdentity();
184             try {
185                 logd("onSendSmsResult token=" + token + " messageRef=" + messageRef
186                         + " status=" + status + " reason=" + reason + " networkReasonCode="
187                         + networkReasonCode);
188                 // TODO integrate networkReasonCode into IMS SMS metrics.
189                 SmsTracker tracker = mTrackers.get(token);
190                 mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason,
191                         (tracker != null ? tracker.mMessageId : 0L));
192                 if (tracker == null) {
193                     throw new IllegalArgumentException("Invalid token.");
194                 }
195                 tracker.mMessageRef = messageRef;
196                 switch(status) {
197                     case ImsSmsImplBase.SEND_STATUS_OK:
198                         if (tracker.mDeliveryIntent != null) {
199                             // Expecting a status report. Put this tracker to the map.
200                             mSmsDispatchersController.putDeliveryPendingTracker(tracker);
201                         }
202                         tracker.onSent(mContext);
203                         mTrackers.remove(token);
204                         mPhone.notifySmsSent(tracker.mDestAddress);
205                         mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
206                                 tracker.mDestAddress, tracker.mMessageId, true,
207                                 tracker.isSinglePartOrLastPart());
208                         break;
209                     case ImsSmsImplBase.SEND_STATUS_ERROR:
210                         tracker.onFailed(mContext, reason, networkReasonCode);
211                         mTrackers.remove(token);
212                         notifySmsSentFailedToEmergencyStateTracker(tracker, true);
213                         break;
214                     case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
215                         int maxRetryCountOverIms = getMaxRetryCountOverIms();
216                         if (tracker.mRetryCount < getMaxSmsRetryCount()) {
217                             if (maxRetryCountOverIms < getMaxSmsRetryCount()
218                                     && tracker.mRetryCount >= maxRetryCountOverIms) {
219                                 tracker.mRetryCount += 1;
220                                 mTrackers.remove(token);
221                                 fallbackToPstn(tracker);
222                                 break;
223                             }
224                             tracker.mRetryCount += 1;
225                             sendMessageDelayed(
226                                     obtainMessage(EVENT_SEND_RETRY, tracker),
227                                     getSmsRetryDelayValue());
228                         } else {
229                             tracker.onFailed(mContext, reason, networkReasonCode);
230                             mTrackers.remove(token);
231                             notifySmsSentFailedToEmergencyStateTracker(tracker, true);
232                         }
233                         break;
234                     case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
235                         // Skip MAX_SEND_RETRIES checking here. It allows CSFB after
236                         // SEND_STATUS_ERROR_RETRY up to MAX_SEND_RETRIES even.
237                         tracker.mRetryCount += 1;
238                         mTrackers.remove(token);
239                         fallbackToPstn(tracker);
240                         break;
241                     default:
242                 }
243                 mPhone.getSmsStats().onOutgoingSms(
244                         true /* isOverIms */,
245                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
246                         status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK,
247                         reason,
248                         networkReasonCode,
249                         tracker.mMessageId,
250                         tracker.isFromDefaultSmsApplication(mContext),
251                         tracker.getInterval(),
252                         mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
253                 if (mPhone != null) {
254                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
255                     if (telephonyAnalytics != null) {
256                         SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
257                         if (smsMmsAnalytics != null) {
258                             smsMmsAnalytics.onOutgoingSms(
259                                     true /* isOverIms */,
260                                     reason);
261                         }
262                     }
263                 }
264 
265             } finally {
266                 Binder.restoreCallingIdentity(identity);
267             }
268         }
269 
270         @Override
271         public void onSmsStatusReportReceived(int token, String format, byte[] pdu)
272                 throws RemoteException {
273             final long identity = Binder.clearCallingIdentity();
274             try {
275                 logd("Status report received.");
276                 android.telephony.SmsMessage message =
277                         android.telephony.SmsMessage.createFromPdu(pdu, format);
278                 if (message == null || message.mWrappedSmsMessage == null) {
279                     throw new RemoteException(
280                             "Status report received with a PDU that could not be parsed.");
281                 }
282                 mSmsDispatchersController.handleSmsStatusReport(format, pdu);
283                 try {
284                     getImsManager().acknowledgeSmsReport(
285                             token,
286                             message.mWrappedSmsMessage.mMessageRef,
287                             ImsSmsImplBase.STATUS_REPORT_STATUS_OK);
288                 } catch (ImsException e) {
289                     loge("Failed to acknowledgeSmsReport(). Error: " + e.getMessage());
290                 }
291             } finally {
292                 Binder.restoreCallingIdentity(identity);
293             }
294         }
295 
296         @Override
297         public void onSmsReceived(int token, String format, byte[] pdu) {
298             final long identity = Binder.clearCallingIdentity();
299             try {
300                 logd("SMS received.");
301                 android.telephony.SmsMessage message =
302                         android.telephony.SmsMessage.createFromPdu(pdu, format);
303                 mSmsDispatchersController.injectSmsPdu(message, format, result -> {
304                     logd("SMS handled result: " + result);
305                     int mappedResult;
306                     switch (result) {
307                         case Intents.RESULT_SMS_HANDLED:
308                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK;
309                             if (message != null) {
310                                 mSmsDispatchersController
311                                         .notifySmsReceivedViaImsToEmergencyStateTracker(
312                                                 message.getOriginatingAddress());
313                             }
314                             break;
315                         case Intents.RESULT_SMS_OUT_OF_MEMORY:
316                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY;
317                             break;
318                         case Intents.RESULT_SMS_UNSUPPORTED:
319                             mappedResult =
320                                     ImsSmsImplBase.DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED;
321                             break;
322                         case Activity.RESULT_OK:
323                             // class2 message saving to SIM operation is in progress, defer ack
324                             // until saving to SIM is success/failure
325                             return;
326                         default:
327                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC;
328                             break;
329                     }
330                     try {
331                         if (message != null && message.mWrappedSmsMessage != null) {
332                             getImsManager().acknowledgeSms(token,
333                                     message.mWrappedSmsMessage.mMessageRef, mappedResult);
334                         } else {
335                             logw("SMS Received with a PDU that could not be parsed.");
336                             getImsManager().acknowledgeSms(token, 0, mappedResult);
337                         }
338                     } catch (ImsException e) {
339                         loge("Failed to acknowledgeSms(). Error: " + e.getMessage());
340                     }
341                 }, true /* ignoreClass */, true /* isOverIms */, token);
342             } finally {
343                 Binder.restoreCallingIdentity(identity);
344             }
345         }
346     };
347 
348     @Override
handleMessage(Message msg)349     public void handleMessage(Message msg) {
350         switch (msg.what) {
351             case EVENT_SEND_RETRY:
352                 logd("SMS retry..");
353                 sendSms((SmsTracker) msg.obj);
354                 break;
355             case EVENT_RETRY_SMMA:
356                 logd("SMMA Notification retry..");
357                 onMemoryAvailable();
358                 break;
359             default:
360                 super.handleMessage(msg);
361         }
362     }
363 
ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, FeatureConnectorFactory factory)364     public ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController,
365             FeatureConnectorFactory factory) {
366         super(phone, smsDispatchersController);
367         mConnectorFactory = factory;
368 
369         mImsManagerConnector = mConnectorFactory.create(mContext, mPhone.getPhoneId(), TAG,
370                 new FeatureConnector.Listener<ImsManager>() {
371                     public void connectionReady(ImsManager manager, int subId) throws ImsException {
372                         logd("ImsManager: connection ready.");
373                         synchronized (mLock) {
374                             mImsManager = manager;
375                             setListeners();
376                             mIsImsServiceUp = true;
377 
378                             /* set ImsManager */
379                             mSmsDispatchersController.setImsManager(mImsManager);
380                         }
381                     }
382 
383                     @Override
384                     public void connectionUnavailable(int reason) {
385                         logd("ImsManager: connection unavailable, reason=" + reason);
386                         if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) {
387                             loge("connectionUnavailable: unexpected, received server error");
388                             removeCallbacks(mConnectRunnable);
389                             postDelayed(mConnectRunnable, CONNECT_DELAY_MS);
390                         }
391                         synchronized (mLock) {
392                             mImsManager = null;
393                             mIsImsServiceUp = false;
394 
395                             /* unset ImsManager */
396                             mSmsDispatchersController.setImsManager(null);
397                         }
398                     }
399                 }, this::post);
400         post(mConnectRunnable);
401     }
402 
setListeners()403     private void setListeners() throws ImsException {
404         getImsManager().addRegistrationCallback(mRegistrationCallback, this::post);
405         getImsManager().addCapabilitiesCallback(mCapabilityCallback, this::post);
406         getImsManager().setSmsListener(getSmsListener());
407         getImsManager().onSmsReady();
408     }
409 
isLteService()410     private boolean isLteService() {
411         return ((mPhone.getServiceState().getRilDataRadioTechnology() ==
412             ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && (mPhone.getServiceState().
413                 getDataRegistrationState() == ServiceState.STATE_IN_SERVICE));
414     }
415 
isLimitedLteService()416     private boolean isLimitedLteService() {
417         return ((mPhone.getServiceState().getRilVoiceRadioTechnology() ==
418             ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && mPhone.getServiceState().isEmergencyOnly());
419     }
420 
isEmergencySmsPossible()421     private boolean isEmergencySmsPossible() {
422         return isLteService() || isLimitedLteService();
423     }
424 
isEmergencySmsSupport(String destAddr)425     public boolean isEmergencySmsSupport(String destAddr) {
426         PersistableBundle b;
427         boolean eSmsCarrierSupport = false;
428         if (!mTelephonyManager.isEmergencyNumber(destAddr)) {
429             logi(Rlog.pii(TAG, destAddr) + " is not emergency number");
430             return false;
431         }
432 
433         final long identity = Binder.clearCallingIdentity();
434         try {
435             CarrierConfigManager configManager = (CarrierConfigManager) mContext
436                     .getSystemService(Context.CARRIER_CONFIG_SERVICE);
437             if (configManager == null) {
438                 loge("configManager is null");
439                 return false;
440             }
441             b = configManager.getConfigForSubId(getSubId());
442             if (b == null) {
443                 loge("PersistableBundle is null");
444                 return false;
445             }
446             eSmsCarrierSupport = b.getBoolean(
447                     CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL);
448             boolean lteOrLimitedLte = isEmergencySmsPossible();
449             logi("isEmergencySmsSupport emergencySmsCarrierSupport: "
450                     + eSmsCarrierSupport + " destAddr: " + Rlog.pii(TAG, destAddr)
451                     + " mIsImsServiceUp: " + mIsImsServiceUp + " lteOrLimitedLte: "
452                     + lteOrLimitedLte);
453 
454             return eSmsCarrierSupport && mIsImsServiceUp && lteOrLimitedLte;
455         } finally {
456             Binder.restoreCallingIdentity(identity);
457         }
458     }
459 
isAvailable()460     public boolean isAvailable() {
461         synchronized (mLock) {
462             logd("isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered
463                     + ", cap= " + mIsSmsCapable);
464             return mIsImsServiceUp && mIsRegistered && mIsSmsCapable;
465         }
466     }
467 
468     @Override
getFormat()469     protected String getFormat() {
470         // This is called in the constructor before ImsSmsDispatcher has a chance to initialize
471         // mLock. ImsManager will not be up anyway at this point, so report UNKNOWN.
472         if (mLock == null) return SmsConstants.FORMAT_UNKNOWN;
473         try {
474             return getImsManager().getSmsFormat();
475         } catch (ImsException e) {
476             loge("Failed to get sms format. Error: " + e.getMessage());
477             return SmsConstants.FORMAT_UNKNOWN;
478         }
479     }
480 
481     @Override
getMaxSmsRetryCount()482     public int getMaxSmsRetryCount() {
483         int retryCount = MAX_SEND_RETRIES;
484         CarrierConfigManager mConfigManager;
485 
486         mConfigManager = (CarrierConfigManager)  mContext.getSystemService(
487                 Context.CARRIER_CONFIG_SERVICE);
488 
489         if (mConfigManager != null) {
490             PersistableBundle carrierConfig = mConfigManager.getConfigForSubId(
491                     getSubId());
492 
493             if (carrierConfig != null) {
494                 retryCount = carrierConfig.getInt(
495                         CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_COUNT_INT);
496             }
497         }
498 
499         Rlog.d(TAG, "Retry Count: " + retryCount);
500 
501         return retryCount;
502     }
503 
504     /**
505      * Returns the number of times SMS can be sent over IMS
506      *
507      * @return  retry count over Ims from  carrier configuration
508      */
509     @VisibleForTesting
getMaxRetryCountOverIms()510     public int getMaxRetryCountOverIms() {
511         int retryCountOverIms = MAX_SEND_RETRIES_OVER_IMS;
512         CarrierConfigManager mConfigManager;
513 
514         mConfigManager = (CarrierConfigManager) mContext.getSystemService(Context
515                                                         .CARRIER_CONFIG_SERVICE);
516 
517         if (mConfigManager != null) {
518             PersistableBundle carrierConfig = mConfigManager.getConfigForSubId(
519                     getSubId());
520 
521 
522             if (carrierConfig != null) {
523                 retryCountOverIms = carrierConfig.getInt(
524                         CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT);
525             }
526         }
527 
528         Rlog.d(TAG, "Retry Count Over Ims: " + retryCountOverIms);
529 
530         return retryCountOverIms;
531     }
532 
533     @Override
getSmsRetryDelayValue()534     public int getSmsRetryDelayValue() {
535         int retryDelay = SEND_RETRY_DELAY;
536         CarrierConfigManager mConfigManager;
537 
538         mConfigManager = (CarrierConfigManager)  mContext.getSystemService(
539                 Context.CARRIER_CONFIG_SERVICE);
540 
541         if (mConfigManager != null) {
542             PersistableBundle carrierConfig = mConfigManager.getConfigForSubId(
543                     getSubId());
544 
545             if (carrierConfig != null) {
546                 retryDelay = carrierConfig.getInt(
547                         CarrierConfigManager.ImsSms.KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT);
548             }
549         }
550 
551         Rlog.d(TAG, "Retry delay: " + retryDelay);
552 
553         return retryDelay;
554     }
555 
556     @Override
shouldBlockSmsForEcbm()557     protected boolean shouldBlockSmsForEcbm() {
558         // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA
559         // SMS.
560         return false;
561     }
562 
563     @Override
getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod)564     protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
565             String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
566             int validityPeriod) {
567         return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message,
568                 statusReportRequested, smsHeader, priority, validityPeriod);
569     }
570 
571     @Override
getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested)572     protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
573             int destPort, byte[] message, boolean statusReportRequested) {
574         return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message,
575                 statusReportRequested);
576     }
577 
578     @Override
getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod, int messageRef)579     protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
580             String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
581             int validityPeriod, int messageRef) {
582         return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message,
583                 statusReportRequested, smsHeader, priority, validityPeriod, messageRef);
584     }
585 
586     @Override
getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested, int messageRef)587     protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
588             int destPort, byte[] message, boolean statusReportRequested, int messageRef) {
589         return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message,
590                 statusReportRequested, messageRef);
591     }
592 
593     @Override
calculateLength(CharSequence messageBody, boolean use7bitOnly)594     protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
595         return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly);
596     }
597 
598     /**
599      * Send the Memory Available Event to the ImsService
600      */
onMemoryAvailable()601     public void onMemoryAvailable() {
602         logd("onMemoryAvailable ");
603         int token = mNextToken.incrementAndGet();
604         try {
605             logd("onMemoryAvailable: token = " + token);
606             mMemoryAvailableNotifierList.add(token);
607             getImsManager().onMemoryAvailable(token);
608         } catch (ImsException e) {
609             loge("onMemoryAvailable failed: " + e.getMessage());
610             if (mMemoryAvailableNotifierList.contains(token)) {
611                 mMemoryAvailableNotifierList.remove(Integer.valueOf(token));
612             }
613         }
614     }
615 
616     @Override
sendSms(SmsTracker tracker)617     public void sendSms(SmsTracker tracker) {
618         logd("sendSms: "
619                 + " mRetryCount=" + tracker.mRetryCount
620                 + " mMessageRef=" + tracker.mMessageRef
621                 + " SS=" + mPhone.getServiceState().getState());
622 
623         // Flag that this Tracker is using the ImsService implementation of SMS over IMS for sending
624         // this message. Any fallbacks will happen over CS only.
625         tracker.mUsesImsServiceForIms = true;
626 
627         HashMap<String, Object> map = tracker.getData();
628 
629         byte[] pdu = (byte[]) map.get(MAP_KEY_PDU);
630         byte smsc[] = (byte[]) map.get(MAP_KEY_SMSC);
631         boolean isRetry = tracker.mRetryCount > 0;
632         String format = getFormat();
633 
634         if (SmsConstants.FORMAT_3GPP.equals(format) && isRetry) {
635             // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
636             //   TP-RD (bit 2) is 1 for retry
637             //   and TP-MR is set to previously failed sms TP-MR
638             if (((0x01 & pdu[0]) == 0x01)) {
639                 pdu[0] |= 0x04; // TP-RD
640                 pdu[1] = (byte) tracker.mMessageRef; // TP-MR
641             }
642         }
643 
644         int token = mNextToken.incrementAndGet();
645         mTrackers.put(token, tracker);
646         try {
647             getImsManager().sendSms(
648                     token,
649                     tracker.mMessageRef,
650                     format,
651                     smsc != null ? IccUtils.bytesToHexString(smsc) : null,
652                     isRetry,
653                     pdu);
654             mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
655                     ImsSmsImplBase.SEND_STATUS_OK, tracker.mMessageId);
656         } catch (ImsException e) {
657             loge("sendSms failed. Falling back to PSTN. Error: " + e.getMessage());
658             mTrackers.remove(token);
659             fallbackToPstn(tracker);
660             mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
661                     ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, tracker.mMessageId);
662             mPhone.getSmsStats().onOutgoingSms(
663                     true /* isOverIms */,
664                     SmsConstants.FORMAT_3GPP2.equals(format),
665                     true /* fallbackToCs */,
666                     SmsManager.RESULT_SYSTEM_ERROR,
667                     tracker.mMessageId,
668                     tracker.isFromDefaultSmsApplication(mContext),
669                     tracker.getInterval(),
670                     mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
671             if (mPhone != null) {
672                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
673                 if (telephonyAnalytics != null) {
674                     SmsMmsAnalytics smsMmsAnalytics = telephonyAnalytics.getSmsMmsAnalytics();
675                     if (smsMmsAnalytics != null) {
676                         smsMmsAnalytics.onOutgoingSms(
677                                 true /* isOverIms */,
678                                 SmsManager.RESULT_SYSTEM_ERROR
679                         );
680                     }
681                 }
682             }
683         }
684     }
685 
getImsManager()686     private ImsManager getImsManager() throws ImsException {
687         synchronized (mLock) {
688             if (mImsManager == null) {
689                 throw new ImsException("ImsManager not up",
690                         ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
691             }
692             return mImsManager;
693         }
694     }
695 
696     @VisibleForTesting
fallbackToPstn(SmsTracker tracker)697     public void fallbackToPstn(SmsTracker tracker) {
698         mSmsDispatchersController.sendRetrySms(tracker);
699     }
700 
701     @Override
isCdmaMo()702     protected boolean isCdmaMo() {
703         return mSmsDispatchersController.isCdmaFormat(getFormat());
704     }
705 
706     @VisibleForTesting
getSmsListener()707     public IImsSmsListener getSmsListener() {
708         return mImsSmsListener;
709     }
710 
logd(String s)711     private void logd(String s) {
712         Rlog.d(TAG + " [" + getPhoneId(mPhone) + "]", s);
713     }
714 
logi(String s)715     private void logi(String s) {
716         Rlog.i(TAG + " [" + getPhoneId(mPhone) + "]", s);
717     }
718 
logw(String s)719     private void logw(String s) {
720         Rlog.w(TAG + " [" + getPhoneId(mPhone) + "]", s);
721     }
722 
loge(String s)723     private void loge(String s) {
724         Rlog.e(TAG + " [" + getPhoneId(mPhone) + "]", s);
725     }
726 
getPhoneId(Phone phone)727     private static String getPhoneId(Phone phone) {
728         return (phone != null) ? Integer.toString(phone.getPhoneId()) : "?";
729     }
730 }
731