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