• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
20 import android.os.Binder;
21 import android.os.Message;
22 import android.os.PersistableBundle;
23 import android.os.RemoteException;
24 import android.provider.Telephony.Sms.Intents;
25 import android.telephony.CarrierConfigManager;
26 import android.telephony.ServiceState;
27 import android.telephony.SmsManager;
28 import android.telephony.ims.ImsReasonInfo;
29 import android.telephony.ims.RegistrationManager;
30 import android.telephony.ims.aidl.IImsSmsListener;
31 import android.telephony.ims.feature.MmTelFeature;
32 import android.telephony.ims.stub.ImsRegistrationImplBase;
33 import android.telephony.ims.stub.ImsSmsImplBase;
34 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult;
35 
36 import com.android.ims.FeatureConnector;
37 import com.android.ims.ImsException;
38 import com.android.ims.ImsManager;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
41 import com.android.internal.telephony.metrics.TelephonyMetrics;
42 import com.android.internal.telephony.uicc.IccUtils;
43 import com.android.internal.telephony.util.SMSDispatcherUtil;
44 import com.android.telephony.Rlog;
45 
46 import java.util.HashMap;
47 import java.util.Map;
48 import java.util.concurrent.ConcurrentHashMap;
49 import java.util.concurrent.Executor;
50 import java.util.concurrent.atomic.AtomicInteger;
51 
52 /**
53  * Responsible for communications with {@link com.android.ims.ImsManager} to send/receive messages
54  * over IMS.
55  * @hide
56  */
57 public class ImsSmsDispatcher extends SMSDispatcher {
58 
59     private static final String TAG = "ImsSmsDispatcher";
60     private static final int CONNECT_DELAY_MS = 5000; // 5 seconds;
61 
62     /**
63      * Creates FeatureConnector instances for ImsManager, used during testing to inject mock
64      * connector instances.
65      */
66     @VisibleForTesting
67     public interface FeatureConnectorFactory {
68         /**
69          * Create a new FeatureConnector for ImsManager.
70          */
create(Context context, int phoneId, String logPrefix, FeatureConnector.Listener<ImsManager> listener, Executor executor)71         FeatureConnector<ImsManager> create(Context context, int phoneId, String logPrefix,
72                 FeatureConnector.Listener<ImsManager> listener, Executor executor);
73     }
74 
75     @VisibleForTesting
76     public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>();
77     @VisibleForTesting
78     public AtomicInteger mNextToken = new AtomicInteger();
79     private final Object mLock = new Object();
80     private volatile boolean mIsSmsCapable;
81     private volatile boolean mIsImsServiceUp;
82     private volatile boolean mIsRegistered;
83     private final FeatureConnector<ImsManager> mImsManagerConnector;
84     /** Telephony metrics instance for logging metrics event */
85     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
86     private ImsManager mImsManager;
87     private FeatureConnectorFactory mConnectorFactory;
88 
89     private Runnable mConnectRunnable = new Runnable() {
90         @Override
91         public void run() {
92             mImsManagerConnector.connect();
93         }
94     };
95 
96     /**
97      * Listen to the IMS service state change
98      *
99      */
100     private RegistrationManager.RegistrationCallback mRegistrationCallback =
101             new RegistrationManager.RegistrationCallback() {
102                 @Override
103                 public void onRegistered(
104                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
105                     logd("onImsConnected imsRadioTech=" + imsRadioTech);
106                     synchronized (mLock) {
107                         mIsRegistered = true;
108                     }
109                 }
110 
111                 @Override
112                 public void onRegistering(
113                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
114                     logd("onImsProgressing imsRadioTech=" + imsRadioTech);
115                     synchronized (mLock) {
116                         mIsRegistered = false;
117                     }
118                 }
119 
120                 @Override
121                 public void onUnregistered(ImsReasonInfo info) {
122                     logd("onImsDisconnected imsReasonInfo=" + info);
123                     synchronized (mLock) {
124                         mIsRegistered = false;
125                     }
126                 }
127             };
128 
129     private android.telephony.ims.ImsMmTelManager.CapabilityCallback mCapabilityCallback =
130             new android.telephony.ims.ImsMmTelManager.CapabilityCallback() {
131                 @Override
132                 public void onCapabilitiesStatusChanged(
133                         MmTelFeature.MmTelCapabilities capabilities) {
134                     synchronized (mLock) {
135                         mIsSmsCapable = capabilities.isCapable(
136                                 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS);
137                     }
138                 }
139     };
140 
141     private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() {
142         @Override
143         public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
144                 @SmsManager.Result int reason, int networkReasonCode) {
145             final long identity = Binder.clearCallingIdentity();
146             try {
147                 logd("onSendSmsResult token=" + token + " messageRef=" + messageRef
148                         + " status=" + status + " reason=" + reason + " networkReasonCode="
149                         + networkReasonCode);
150                 // TODO integrate networkReasonCode into IMS SMS metrics.
151                 SmsTracker tracker = mTrackers.get(token);
152                 mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason,
153                         (tracker != null ? tracker.mMessageId : 0L));
154                 if (tracker == null) {
155                     throw new IllegalArgumentException("Invalid token.");
156                 }
157                 tracker.mMessageRef = messageRef;
158                 switch(status) {
159                     case ImsSmsImplBase.SEND_STATUS_OK:
160                         if (tracker.mDeliveryIntent != null) {
161                             // Expecting a status report. Put this tracker to the map.
162                             mSmsDispatchersController.putDeliveryPendingTracker(tracker);
163                         }
164                         tracker.onSent(mContext);
165                         mTrackers.remove(token);
166                         mPhone.notifySmsSent(tracker.mDestAddress);
167                         break;
168                     case ImsSmsImplBase.SEND_STATUS_ERROR:
169                         tracker.onFailed(mContext, reason, networkReasonCode);
170                         mTrackers.remove(token);
171                         break;
172                     case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
173                         if (tracker.mRetryCount < MAX_SEND_RETRIES) {
174                             tracker.mRetryCount += 1;
175                             sendMessageDelayed(
176                                     obtainMessage(EVENT_SEND_RETRY, tracker), SEND_RETRY_DELAY);
177                         } else {
178                             tracker.onFailed(mContext, reason, networkReasonCode);
179                             mTrackers.remove(token);
180                         }
181                         break;
182                     case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
183                         // Skip MAX_SEND_RETRIES checking here. It allows CSFB after
184                         // SEND_STATUS_ERROR_RETRY up to MAX_SEND_RETRIES even.
185                         tracker.mRetryCount += 1;
186                         mTrackers.remove(token);
187                         fallbackToPstn(tracker);
188                         break;
189                     default:
190                 }
191                 mPhone.getSmsStats().onOutgoingSms(
192                         true /* isOverIms */,
193                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
194                         status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK,
195                         reason,
196                         tracker.mMessageId,
197                         tracker.isFromDefaultSmsApplication(mContext),
198                         tracker.getInterval());
199             } finally {
200                 Binder.restoreCallingIdentity(identity);
201             }
202         }
203 
204         @Override
205         public void onSmsStatusReportReceived(int token, String format, byte[] pdu)
206                 throws RemoteException {
207             final long identity = Binder.clearCallingIdentity();
208             try {
209                 logd("Status report received.");
210                 android.telephony.SmsMessage message =
211                         android.telephony.SmsMessage.createFromPdu(pdu, format);
212                 if (message == null || message.mWrappedSmsMessage == null) {
213                     throw new RemoteException(
214                             "Status report received with a PDU that could not be parsed.");
215                 }
216                 mSmsDispatchersController.handleSmsStatusReport(format, pdu);
217                 try {
218                     getImsManager().acknowledgeSmsReport(
219                             token,
220                             message.mWrappedSmsMessage.mMessageRef,
221                             ImsSmsImplBase.STATUS_REPORT_STATUS_OK);
222                 } catch (ImsException e) {
223                     loge("Failed to acknowledgeSmsReport(). Error: " + e.getMessage());
224                 }
225             } finally {
226                 Binder.restoreCallingIdentity(identity);
227             }
228         }
229 
230         @Override
231         public void onSmsReceived(int token, String format, byte[] pdu) {
232             final long identity = Binder.clearCallingIdentity();
233             try {
234                 logd("SMS received.");
235                 android.telephony.SmsMessage message =
236                         android.telephony.SmsMessage.createFromPdu(pdu, format);
237                 mSmsDispatchersController.injectSmsPdu(message, format, result -> {
238                     logd("SMS handled result: " + result);
239                     int mappedResult;
240                     switch (result) {
241                         case Intents.RESULT_SMS_HANDLED:
242                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_OK;
243                             break;
244                         case Intents.RESULT_SMS_OUT_OF_MEMORY:
245                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_NO_MEMORY;
246                             break;
247                         case Intents.RESULT_SMS_UNSUPPORTED:
248                             mappedResult =
249                                     ImsSmsImplBase.DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED;
250                             break;
251                         default:
252                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC;
253                             break;
254                     }
255                     try {
256                         if (message != null && message.mWrappedSmsMessage != null) {
257                             getImsManager().acknowledgeSms(token,
258                                     message.mWrappedSmsMessage.mMessageRef, mappedResult);
259                         } else {
260                             logw("SMS Received with a PDU that could not be parsed.");
261                             getImsManager().acknowledgeSms(token, 0, mappedResult);
262                         }
263                     } catch (ImsException e) {
264                         loge("Failed to acknowledgeSms(). Error: " + e.getMessage());
265                     }
266                 }, true /* ignoreClass */, true /* isOverIms */);
267             } finally {
268                 Binder.restoreCallingIdentity(identity);
269             }
270         }
271     };
272 
273     @Override
handleMessage(Message msg)274     public void handleMessage(Message msg) {
275         switch (msg.what) {
276             case EVENT_SEND_RETRY:
277                 logd("SMS retry..");
278                 sendSms((SmsTracker) msg.obj);
279                 break;
280             default:
281                 super.handleMessage(msg);
282         }
283     }
284 
ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, FeatureConnectorFactory factory)285     public ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController,
286             FeatureConnectorFactory factory) {
287         super(phone, smsDispatchersController);
288         mConnectorFactory = factory;
289 
290         mImsManagerConnector = mConnectorFactory.create(mContext, mPhone.getPhoneId(), TAG,
291                 new FeatureConnector.Listener<ImsManager>() {
292                     public void connectionReady(ImsManager manager, int subId) throws ImsException {
293                         logd("ImsManager: connection ready.");
294                         synchronized (mLock) {
295                             mImsManager = manager;
296                             setListeners();
297                             mIsImsServiceUp = true;
298                         }
299                     }
300 
301                     @Override
302                     public void connectionUnavailable(int reason) {
303                         logd("ImsManager: connection unavailable, reason=" + reason);
304                         if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) {
305                             loge("connectionUnavailable: unexpected, received server error");
306                             removeCallbacks(mConnectRunnable);
307                             postDelayed(mConnectRunnable, CONNECT_DELAY_MS);
308                         }
309                         synchronized (mLock) {
310                             mImsManager = null;
311                             mIsImsServiceUp = false;
312                         }
313                     }
314                 }, this::post);
315         post(mConnectRunnable);
316     }
317 
setListeners()318     private void setListeners() throws ImsException {
319         getImsManager().addRegistrationCallback(mRegistrationCallback, this::post);
320         getImsManager().addCapabilitiesCallback(mCapabilityCallback, this::post);
321         getImsManager().setSmsListener(getSmsListener());
322         getImsManager().onSmsReady();
323     }
324 
isLteService()325     private boolean isLteService() {
326         return ((mPhone.getServiceState().getRilDataRadioTechnology() ==
327             ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && (mPhone.getServiceState().
328                 getDataRegistrationState() == ServiceState.STATE_IN_SERVICE));
329     }
330 
isLimitedLteService()331     private boolean isLimitedLteService() {
332         return ((mPhone.getServiceState().getRilVoiceRadioTechnology() ==
333             ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && mPhone.getServiceState().isEmergencyOnly());
334     }
335 
isEmergencySmsPossible()336     private boolean isEmergencySmsPossible() {
337         return isLteService() || isLimitedLteService();
338     }
339 
isEmergencySmsSupport(String destAddr)340     public boolean isEmergencySmsSupport(String destAddr) {
341         PersistableBundle b;
342         boolean eSmsCarrierSupport = false;
343         if (!mTelephonyManager.isEmergencyNumber(destAddr)) {
344             logi(Rlog.pii(TAG, destAddr) + " is not emergency number");
345             return false;
346         }
347 
348         final long identity = Binder.clearCallingIdentity();
349         try {
350             CarrierConfigManager configManager = (CarrierConfigManager) mContext
351                     .getSystemService(Context.CARRIER_CONFIG_SERVICE);
352             if (configManager == null) {
353                 loge("configManager is null");
354                 return false;
355             }
356             b = configManager.getConfigForSubId(getSubId());
357             if (b == null) {
358                 loge("PersistableBundle is null");
359                 return false;
360             }
361             eSmsCarrierSupport = b.getBoolean(
362                     CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL);
363             boolean lteOrLimitedLte = isEmergencySmsPossible();
364             logi("isEmergencySmsSupport emergencySmsCarrierSupport: "
365                     + eSmsCarrierSupport + " destAddr: " + Rlog.pii(TAG, destAddr)
366                     + " mIsImsServiceUp: " + mIsImsServiceUp + " lteOrLimitedLte: "
367                     + lteOrLimitedLte);
368 
369             return eSmsCarrierSupport && mIsImsServiceUp && lteOrLimitedLte;
370         } finally {
371             Binder.restoreCallingIdentity(identity);
372         }
373     }
374 
isAvailable()375     public boolean isAvailable() {
376         synchronized (mLock) {
377             logd("isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered
378                     + ", cap= " + mIsSmsCapable);
379             return mIsImsServiceUp && mIsRegistered && mIsSmsCapable;
380         }
381     }
382 
383     @Override
getFormat()384     protected String getFormat() {
385         // This is called in the constructor before ImsSmsDispatcher has a chance to initialize
386         // mLock. ImsManager will not be up anyway at this point, so report UNKNOWN.
387         if (mLock == null) return SmsConstants.FORMAT_UNKNOWN;
388         try {
389             return getImsManager().getSmsFormat();
390         } catch (ImsException e) {
391             loge("Failed to get sms format. Error: " + e.getMessage());
392             return SmsConstants.FORMAT_UNKNOWN;
393         }
394     }
395 
396     @Override
shouldBlockSmsForEcbm()397     protected boolean shouldBlockSmsForEcbm() {
398         // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA
399         // SMS.
400         return false;
401     }
402 
403     @Override
getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod)404     protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
405             String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
406             int validityPeriod) {
407         return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message,
408                 statusReportRequested, smsHeader, priority, validityPeriod);
409     }
410 
411     @Override
getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested)412     protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
413             int destPort, byte[] message, boolean statusReportRequested) {
414         return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message,
415                 statusReportRequested);
416     }
417 
418     @Override
calculateLength(CharSequence messageBody, boolean use7bitOnly)419     protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
420         return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly);
421     }
422 
423     @Override
sendSms(SmsTracker tracker)424     public void sendSms(SmsTracker tracker) {
425         logd("sendSms: "
426                 + " mRetryCount=" + tracker.mRetryCount
427                 + " mMessageRef=" + tracker.mMessageRef
428                 + " SS=" + mPhone.getServiceState().getState());
429 
430         // Flag that this Tracker is using the ImsService implementation of SMS over IMS for sending
431         // this message. Any fallbacks will happen over CS only.
432         tracker.mUsesImsServiceForIms = true;
433 
434         HashMap<String, Object> map = tracker.getData();
435 
436         byte[] pdu = (byte[]) map.get(MAP_KEY_PDU);
437         byte smsc[] = (byte[]) map.get(MAP_KEY_SMSC);
438         boolean isRetry = tracker.mRetryCount > 0;
439         String format = getFormat();
440 
441         if (SmsConstants.FORMAT_3GPP.equals(format) && isRetry) {
442             // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
443             //   TP-RD (bit 2) is 1 for retry
444             //   and TP-MR is set to previously failed sms TP-MR
445             if (((0x01 & pdu[0]) == 0x01)) {
446                 pdu[0] |= 0x04; // TP-RD
447                 pdu[1] = (byte) tracker.mMessageRef; // TP-MR
448             }
449         }
450 
451         int token = mNextToken.incrementAndGet();
452         mTrackers.put(token, tracker);
453         try {
454             getImsManager().sendSms(
455                     token,
456                     tracker.mMessageRef,
457                     format,
458                     smsc != null ? IccUtils.bytesToHexString(smsc) : null,
459                     isRetry,
460                     pdu);
461             mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
462                     ImsSmsImplBase.SEND_STATUS_OK, tracker.mMessageId);
463         } catch (ImsException e) {
464             loge("sendSms failed. Falling back to PSTN. Error: " + e.getMessage());
465             mTrackers.remove(token);
466             fallbackToPstn(tracker);
467             mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
468                     ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, tracker.mMessageId);
469             mPhone.getSmsStats().onOutgoingSms(
470                     true /* isOverIms */,
471                     SmsConstants.FORMAT_3GPP2.equals(format),
472                     true /* fallbackToCs */,
473                     SmsManager.RESULT_SYSTEM_ERROR,
474                     tracker.mMessageId,
475                     tracker.isFromDefaultSmsApplication(mContext),
476                     tracker.getInterval());
477         }
478     }
479 
getImsManager()480     private ImsManager getImsManager() throws ImsException {
481         synchronized (mLock) {
482             if (mImsManager == null) {
483                 throw new ImsException("ImsManager not up",
484                         ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
485             }
486             return mImsManager;
487         }
488     }
489 
490     @VisibleForTesting
fallbackToPstn(SmsTracker tracker)491     public void fallbackToPstn(SmsTracker tracker) {
492         mSmsDispatchersController.sendRetrySms(tracker);
493     }
494 
495     @Override
isCdmaMo()496     protected boolean isCdmaMo() {
497         return mSmsDispatchersController.isCdmaFormat(getFormat());
498     }
499 
500     @VisibleForTesting
getSmsListener()501     public IImsSmsListener getSmsListener() {
502         return mImsSmsListener;
503     }
504 
logd(String s)505     private void logd(String s) {
506         Rlog.d(TAG + " [" + getPhoneId(mPhone) + "]", s);
507     }
508 
logi(String s)509     private void logi(String s) {
510         Rlog.i(TAG + " [" + getPhoneId(mPhone) + "]", s);
511     }
512 
logw(String s)513     private void logw(String s) {
514         Rlog.w(TAG + " [" + getPhoneId(mPhone) + "]", s);
515     }
516 
loge(String s)517     private void loge(String s) {
518         Rlog.e(TAG + " [" + getPhoneId(mPhone) + "]", s);
519     }
520 
getPhoneId(Phone phone)521     private static String getPhoneId(Phone phone) {
522         return (phone != null) ? Integer.toString(phone.getPhoneId()) : "?";
523     }
524 }
525