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