Home | History | Annotate | Download | only in presence
      1 /*
      2  * Copyright (c) 2015, Motorola Mobility LLC
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *     - Redistributions of source code must retain the above copyright
      8  *       notice, this list of conditions and the following disclaimer.
      9  *     - Redistributions in binary form must reproduce the above copyright
     10  *       notice, this list of conditions and the following disclaimer in the
     11  *       documentation and/or other materials provided with the distribution.
     12  *     - Neither the name of Motorola Mobility nor the
     13  *       names of its contributors may be used to endorse or promote products
     14  *       derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     18  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
     26  * DAMAGE.
     27  */
     28 
     29 package com.android.service.ims.presence;
     30 
     31 import java.util.List;
     32 import java.util.ArrayList;
     33 import java.util.Timer;
     34 import java.util.TimerTask;
     35 import java.util.concurrent.Semaphore;
     36 import android.content.ContentValues;
     37 import android.text.TextUtils;
     38 
     39 import android.content.BroadcastReceiver;
     40 import android.content.ComponentName;
     41 import android.content.Context;
     42 import android.content.Intent;
     43 import android.content.IntentFilter;
     44 import com.android.internal.telephony.TelephonyIntents;
     45 import android.os.HandlerThread;
     46 import android.os.RemoteException;
     47 import android.telephony.TelephonyManager;
     48 import android.database.Cursor;
     49 
     50 import java.lang.String;
     51 import android.content.Context;
     52 import android.util.Log;
     53 
     54 import com.android.ims.internal.uce.presence.PresSipResponse;
     55 import com.android.ims.internal.uce.common.StatusCode;
     56 import com.android.ims.internal.uce.common.StatusCode;
     57 import com.android.ims.internal.uce.presence.PresSubscriptionState;
     58 import com.android.ims.internal.uce.presence.PresCmdStatus;
     59 import com.android.ims.internal.uce.presence.PresResInfo;
     60 import com.android.ims.internal.uce.presence.PresRlmiInfo;
     61 import com.android.ims.internal.uce.presence.PresTupleInfo;
     62 
     63 import com.android.ims.RcsPresenceInfo;
     64 import com.android.ims.RcsPresence;
     65 import com.android.ims.IRcsPresenceListener;
     66 import com.android.ims.RcsManager.ResultCode;
     67 import com.android.ims.RcsPresence.PublishState;
     68 
     69 import com.android.ims.internal.Logger;
     70 import com.android.ims.internal.ContactNumberUtils;
     71 import com.android.service.ims.TaskManager;
     72 import com.android.service.ims.Task;
     73 import com.android.service.ims.RcsStackAdaptor;
     74 import com.android.service.ims.RcsUtils;
     75 import com.android.service.ims.RcsSettingUtils;
     76 
     77 import com.android.service.ims.R;
     78 
     79 public class PresenceSubscriber extends PresenceBase{
     80     /*
     81      * The logger
     82      */
     83     private Logger logger = Logger.getLogger(this.getClass().getName());
     84 
     85     private RcsStackAdaptor mRcsStackAdaptor = null;
     86 
     87     private static PresenceSubscriber sSubsriber = null;
     88 
     89     private String mAvailabilityRetryNumber = null;
     90 
     91     /*
     92      * Constructor
     93      */
     94     public PresenceSubscriber(RcsStackAdaptor rcsStackAdaptor, Context context){
     95         mRcsStackAdaptor = rcsStackAdaptor;
     96         mContext = context;
     97     }
     98 
     99     private String numberToUriString(String number){
    100         String formatedContact = number;
    101         if(!formatedContact.startsWith("sip:") && !formatedContact.startsWith("tel:")){
    102             String domain = TelephonyManager.getDefault().getIsimDomain();
    103             logger.debug("domain=" + domain);
    104             if(domain == null || domain.length() ==0){
    105                 formatedContact = "tel:" + formatedContact;
    106             }else{
    107                 formatedContact = "sip:" + formatedContact + "@" + domain;
    108             }
    109         }
    110 
    111         logger.print("numberToUriString formatedContact=" + formatedContact);
    112         return formatedContact;
    113     }
    114 
    115     private String numberToTelString(String number){
    116         String formatedContact = number;
    117         if(!formatedContact.startsWith("sip:") && !formatedContact.startsWith("tel:")){
    118             formatedContact = "tel:" + formatedContact;
    119         }
    120 
    121         logger.print("numberToTelString formatedContact=" + formatedContact);
    122         return formatedContact;
    123     }
    124 
    125     public int requestCapability(List<String> contactsNumber,
    126             IRcsPresenceListener listener){
    127 
    128         int ret = mRcsStackAdaptor.checkStackAndPublish();
    129         if(ret < ResultCode.SUCCESS){
    130             logger.error("requestCapability ret=" + ret);
    131             return ret;
    132         }
    133 
    134         if(contactsNumber == null || contactsNumber.size() ==0){
    135             ret = ResultCode.SUBSCRIBE_INVALID_PARAM;
    136             return ret;
    137         }
    138 
    139         logger.debug("check contact size ...");
    140         if(contactsNumber.size() > RcsSettingUtils.getMaxNumbersInRCL(mContext)){
    141             ret = ResultCode.SUBSCRIBE_TOO_LARGE;
    142             logger.error("requestCapability contctNumber size=" + contactsNumber.size());
    143             return ret;
    144         }
    145 
    146         String[] formatedNumbers = ContactNumberUtils.getDefault().format(contactsNumber);
    147         ret = ContactNumberUtils.getDefault().validate(formatedNumbers);
    148         if(ret != ContactNumberUtils.NUMBER_VALID){
    149             logger.error("requestCapability ret=" + ret);
    150             return ret;
    151         }
    152 
    153         String[] formatedContacts = new String[formatedNumbers.length];
    154         for(int i=0; i<formatedContacts.length; i++){
    155             formatedContacts[i] = numberToTelString(formatedNumbers[i]);
    156         }
    157         // In ms
    158         long timeout = RcsSettingUtils.getCapabPollListSubExp(mContext) * 1000;
    159         timeout += RcsSettingUtils.getSIPT1Timer(mContext);
    160 
    161         // The terminal notification may be received shortly after the time limit of
    162         // the subscription due to network delays or retransmissions.
    163         // Device shall wait for 3sec after the end of the subscription period in order to
    164         // accept such notifications without returning spurious errors (e.g. SIP 481)
    165         timeout += 3000;
    166 
    167         logger.print("add to task manager, formatedNumbers=" +
    168                 RcsUtils.toContactString(formatedNumbers));
    169         int taskId = TaskManager.getDefault().addCapabilityTask(mContext, formatedNumbers,
    170                 listener, timeout);
    171         logger.print("taskId=" + taskId);
    172 
    173         ret = mRcsStackAdaptor.requestCapability(formatedContacts, taskId);
    174         if(ret < ResultCode.SUCCESS){
    175             logger.error("requestCapability ret=" + ret + " remove taskId=" + taskId);
    176             TaskManager.getDefault().removeTask(taskId);
    177         }
    178 
    179         ret = taskId;
    180 
    181         return  ret;
    182     }
    183 
    184     public int requestAvailability(String contactNumber, IRcsPresenceListener listener,
    185             boolean forceToNetwork){
    186 
    187         String formatedContact = ContactNumberUtils.getDefault().format(contactNumber);
    188         int ret = ContactNumberUtils.getDefault().validate(formatedContact);
    189         if(ret != ContactNumberUtils.NUMBER_VALID){
    190             return ret;
    191         }
    192 
    193         if(!forceToNetwork){
    194             logger.debug("check if we can use the value in cache");
    195             int availabilityExpire = RcsSettingUtils.getAvailabilityCacheExpiration(mContext);
    196             availabilityExpire = availabilityExpire>0?availabilityExpire*1000:
    197                     60*1000; // by default is 60s
    198             logger.print("requestAvailability availabilityExpire=" + availabilityExpire);
    199 
    200             TaskManager.getDefault().clearTimeoutAvailabilityTask(availabilityExpire);
    201 
    202             Task task = TaskManager.getDefault().getAvailabilityTaskByContact(formatedContact);
    203             if(task != null && task instanceof PresenceAvailabilityTask) {
    204                 PresenceAvailabilityTask availabilityTask = (PresenceAvailabilityTask)task;
    205                 if(availabilityTask.getNotifyTimestamp() == 0) {
    206                     // The previous one didn't get response yet.
    207                     logger.print("requestAvailability: the request is pending in queue");
    208                     return ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE;
    209                 }else {
    210                     // not expire yet. Can use the previous value.
    211                     logger.print("requestAvailability: the prevous valuedoesn't be expired yet");
    212                     return ResultCode.SUBSCRIBE_TOO_FREQUENTLY;
    213                 }
    214             }
    215         }
    216 
    217         boolean isFtSupported = false; // hard code to not support FT at present.
    218         boolean isChatSupported = false;  // hard code to not support chat at present.
    219         // Only poll/fetch capability/availability on LTE
    220         if(((TelephonyManager.getDefault().getNetworkType() != TelephonyManager.NETWORK_TYPE_LTE)
    221                 && !isFtSupported && !isChatSupported)){
    222             logger.error("requestAvailability return ERROR_SERVICE_NOT_AVAILABLE" +
    223                     " for it is not LTE network");
    224             return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
    225         }
    226 
    227         ret = mRcsStackAdaptor.checkStackAndPublish();
    228         if(ret < ResultCode.SUCCESS){
    229             logger.error("requestAvailability=" + ret);
    230             return ret;
    231         }
    232 
    233         // user number format in TaskManager.
    234         int taskId = TaskManager.getDefault().addAvailabilityTask(formatedContact, listener);
    235 
    236         // Change it to URI format.
    237         formatedContact = numberToUriString(formatedContact);
    238 
    239         logger.print("addAvailabilityTask formatedContact=" + formatedContact);
    240 
    241         ret = mRcsStackAdaptor.requestAvailability(formatedContact, taskId);
    242         if(ret < ResultCode.SUCCESS){
    243             logger.error("requestAvailability ret=" + ret + " remove taskId=" + taskId);
    244             TaskManager.getDefault().removeTask(taskId);
    245         }
    246 
    247         ret = taskId;
    248 
    249         return  ret;
    250     }
    251 
    252     private int translateResponse403(PresSipResponse pSipResponse){
    253         String reasonPhrase = pSipResponse.getReasonPhrase();
    254         if(reasonPhrase == null){
    255             // No retry. The PS provisioning has not occurred correctly. UX Decision to show errror.
    256             return ResultCode.SUBSCRIBE_GENIRIC_FAILURE;
    257         }
    258 
    259         reasonPhrase = reasonPhrase.toLowerCase();
    260         if(reasonPhrase.contains("user not registered")){
    261             // Register to IMS then retry the single resource subscription if capability polling.
    262             // availability fetch: no retry. ignore the availability and allow LVC? (PLM decision)
    263             return ResultCode.SUBSCRIBE_NOT_REGISTERED;
    264         }
    265 
    266         if(reasonPhrase.contains("not authorized for presence")){
    267             // No retry.
    268             return ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE;
    269         }
    270 
    271         // unknown phrase: handle it as the same as no phrase
    272         return ResultCode.SUBSCRIBE_FORBIDDEN;
    273     }
    274 
    275     private int translateResponseCode(PresSipResponse pSipResponse){
    276         // pSipResponse should not be null.
    277         logger.debug("translateResponseCode getSipResponseCode=" +
    278                 pSipResponse.getSipResponseCode());
    279         int ret = ResultCode.SUBSCRIBE_GENIRIC_FAILURE;
    280 
    281         int sipCode = pSipResponse.getSipResponseCode();
    282         if(sipCode < 100 || sipCode > 699){
    283             logger.debug("internal error code sipCode=" + sipCode);
    284             ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; //it is internal issue. ignore it.
    285             return ret;
    286         }
    287 
    288         switch(sipCode){
    289             case 200:
    290                 ret = ResultCode.SUCCESS;
    291                 break;
    292 
    293             case 403:
    294                 ret = translateResponse403(pSipResponse);
    295                 break;
    296 
    297             case 404:
    298                // Target MDN is not provisioned for VoLTE or it is not  known as VzW IMS subscriber
    299                // Device shall not retry. Device shall remove the VoLTE status of the target MDN
    300                // and update UI
    301                ret = ResultCode.SUBSCRIBE_NOT_FOUND;
    302                break;
    303 
    304             case 408:
    305                 // Request Timeout
    306                 // Device shall retry with exponential back-off
    307                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
    308                 break;
    309 
    310             case 413:
    311                 // Too Large.
    312                 // Application need shrink the size of request contact list and resend the request
    313                 ret = ResultCode.SUBSCRIBE_TOO_LARGE;
    314                 break;
    315 
    316             case 423:
    317                 // Interval Too Short. Requested expiry interval too short and server rejects it
    318                 // Device shall re-attempt subscription after changing the expiration interval in
    319                 // the Expires header field to be equal to or greater than the expiration interval
    320                 // within the Min-Expires header field of the 423 response
    321                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
    322                 break;
    323 
    324             case 500:
    325                 // 500 Server Internal Error
    326                 // capability polling: exponential back-off retry (same rule as resource list)
    327                 // availability fetch: no retry. ignore the availability and allow LVC
    328                 // (PLM decision)
    329                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
    330                 break;
    331 
    332             case 503:
    333                 // capability polling: exponential back-off retry (same rule as resource list)
    334                 // availability fetch: no retry. ignore the availability and allow LVC?
    335                 // (PLM decision)
    336                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
    337                 break;
    338 
    339                 // capability polling: Device shall retry with exponential back-off
    340                 // Availability Fetch: device shall ignore the error and shall not retry
    341             case 603:
    342                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
    343                 break;
    344 
    345             default:
    346                 // Other 4xx/5xx/6xx
    347                 // Device shall not retry
    348                 ret = ResultCode.SUBSCRIBE_GENIRIC_FAILURE;
    349         }
    350 
    351         logger.debug("translateResponseCode ret=" + ret);
    352         return ret;
    353     }
    354 
    355     public void handleSipResponse(PresSipResponse pSipResponse){
    356         if(pSipResponse == null){
    357             logger.debug("handleSipResponse pSipResponse = null");
    358             return;
    359         }
    360 
    361         int sipCode = pSipResponse.getSipResponseCode();
    362         String phrase = pSipResponse.getReasonPhrase();
    363         if(isInConfigList(sipCode, phrase,
    364                 R.array.config_volte_provision_error_on_subscribe_response)) {
    365             logger.print("volte provision sipCode=" + sipCode + " phrase=" + phrase);
    366             mRcsStackAdaptor.setPublishState(PublishState.PUBLISH_STATE_VOLTE_PROVISION_ERROR);
    367 
    368             notifyDm();
    369         } else if(isInConfigList(sipCode, phrase,
    370                 R.array.config_rcs_provision_error_on_subscribe_response)) {
    371             logger.print("rcs provision sipCode=" + sipCode + " phrase=" + phrase);
    372             mRcsStackAdaptor.setPublishState(PublishState.PUBLISH_STATE_RCS_PROVISION_ERROR);
    373         }
    374 
    375         int errorCode = translateResponseCode(pSipResponse);
    376         logger.print("handleSipResponse errorCode=" + errorCode);
    377 
    378         if(errorCode == ResultCode.SUBSCRIBE_NOT_REGISTERED){
    379             logger.debug("setPublishState to unknown" +
    380                    " for subscribe error 403 not registered");
    381             mRcsStackAdaptor.setPublishState(
    382                    PublishState.PUBLISH_STATE_OTHER_ERROR);
    383         }
    384 
    385         if(errorCode == ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE) {
    386             logger.debug("ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE");
    387         }
    388 
    389         if(errorCode == ResultCode.SUBSCRIBE_FORBIDDEN){
    390             logger.debug("ResultCode.SUBSCRIBE_FORBIDDEN");
    391         }
    392 
    393         // Suppose the request ID had been set when IQPresListener_CMDStatus
    394         Task task = TaskManager.getDefault().getTaskByRequestId(
    395                 pSipResponse.getRequestId());
    396         logger.debug("handleSipResponse task=" + task);
    397         if(task != null){
    398             task.mSipResponseCode = sipCode;
    399             task.mSipReasonPhrase = phrase;
    400             TaskManager.getDefault().putTask(task.mTaskId, task);
    401         }
    402 
    403         if(errorCode == ResultCode.SUBSCRIBE_NOT_REGISTERED &&
    404                 task != null && task.mCmdId == TaskManager.TASK_TYPE_GET_AVAILABILITY) {
    405             String[] contacts = ((PresenceTask)task).mContacts;
    406             if(contacts != null && contacts.length>0){
    407                 mAvailabilityRetryNumber = contacts[0];
    408             }
    409             logger.debug("retry to get availability for " + mAvailabilityRetryNumber);
    410         }
    411 
    412         // 404 error for single contact only as per requirement
    413         // need handle 404 for multiple contacts as per CV 3.24.
    414         if(errorCode == ResultCode.SUBSCRIBE_NOT_FOUND &&
    415                 task != null && ((PresenceTask)task).mContacts != null) {
    416             String[] contacts = ((PresenceTask)task).mContacts;
    417             ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>();
    418 
    419             for(int i=0; i<contacts.length; i++){
    420                 if(TextUtils.isEmpty(contacts[i])){
    421                     continue;
    422                 }
    423 
    424                 RcsPresenceInfo presenceInfo = new RcsPresenceInfo(contacts[i],
    425                         RcsPresenceInfo.VolteStatus.VOLTE_DISABLED,
    426                         RcsPresenceInfo.ServiceState.OFFLINE, null, System.currentTimeMillis(),
    427                         RcsPresenceInfo.ServiceState.OFFLINE, null, System.currentTimeMillis());
    428                 presenceInfoList.add(presenceInfo);
    429             }
    430 
    431             // Notify presence information changed.
    432             logger.debug("notify presence changed for 404 error");
    433             Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED);
    434             intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST,
    435                     presenceInfoList);
    436             intent.putExtra("updateLastTimestamp", true);
    437             launchPersistService(intent);
    438         } else if(errorCode == ResultCode.SUBSCRIBE_GENIRIC_FAILURE) {
    439             updateAvailabilityToUnknown(task);
    440         }
    441 
    442         handleCallback(task, errorCode, false);
    443     }
    444 
    445     private void launchPersistService(Intent intent) {
    446         ComponentName component = new ComponentName("com.android.service.ims.presence",
    447                 "com.android.service.ims.presence.PersistService");
    448         intent.setComponent(component);
    449         mContext.startService(intent);
    450     }
    451 
    452     public void retryToGetAvailability() {
    453         if(mAvailabilityRetryNumber == null){
    454             return;
    455         }
    456         requestAvailability(mAvailabilityRetryNumber, null, true);
    457         //retry one time only
    458         mAvailabilityRetryNumber = null;
    459     }
    460 
    461     public void updatePresence(String pPresentityUri, PresTupleInfo[] pTupleInfo){
    462         if(mContext == null){
    463             logger.error("updatePresence mContext == null");
    464             return;
    465         }
    466 
    467         RcsPresenceInfo rcsPresenceInfo = PresenceInfoParser.getPresenceInfoFromTuple(
    468                 pPresentityUri, pTupleInfo);
    469         if(rcsPresenceInfo == null || TextUtils.isEmpty(
    470                 rcsPresenceInfo.getContactNumber())){
    471             logger.error("rcsPresenceInfo is null or " +
    472                     "TextUtils.isEmpty(rcsPresenceInfo.getContactNumber()");
    473             return;
    474         }
    475 
    476         ArrayList<RcsPresenceInfo> rcsPresenceInfoList = new ArrayList<RcsPresenceInfo>();
    477         rcsPresenceInfoList.add(rcsPresenceInfo);
    478 
    479         // For single contact number we got 1 NOTIFY only. So regard it as terminated.
    480         TaskManager.getDefault().onTerminated(rcsPresenceInfo.getContactNumber());
    481 
    482         PresenceAvailabilityTask availabilityTask = TaskManager.getDefault().
    483                 getAvailabilityTaskByContact(rcsPresenceInfo.getContactNumber());
    484         if(availabilityTask != null){
    485             availabilityTask.updateNotifyTimestamp();
    486         }
    487 
    488         // Notify presence information changed.
    489         Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED);
    490         intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST,
    491                 rcsPresenceInfoList);
    492         intent.putExtra("updateLastTimestamp", true);
    493         launchPersistService(intent);
    494     }
    495 
    496     public void updatePresences(PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo) {
    497         if(mContext == null){
    498             logger.error("updatePresences: mContext == null");
    499             return;
    500         }
    501 
    502         RcsPresenceInfo[] rcsPresenceInfos = PresenceInfoParser.
    503                 getPresenceInfosFromPresenceRes(pRlmiInfo, pRcsPresenceInfo);
    504         if(rcsPresenceInfos == null){
    505             logger.error("updatePresences: rcsPresenceInfos == null");
    506             return;
    507         }
    508 
    509         ArrayList<RcsPresenceInfo> rcsPresenceInfoList = new ArrayList<RcsPresenceInfo>();
    510 
    511         for (int i=0; i < rcsPresenceInfos.length; i++) {
    512             RcsPresenceInfo rcsPresenceInfo = rcsPresenceInfos[i];
    513             if(rcsPresenceInfo != null && !TextUtils.isEmpty(rcsPresenceInfo.getContactNumber())){
    514                 logger.debug("rcsPresenceInfo=" + rcsPresenceInfo);
    515 
    516                 rcsPresenceInfoList.add(rcsPresenceInfo);
    517             }
    518         }
    519 
    520         boolean isTerminated = false;
    521         if(pRlmiInfo.getPresSubscriptionState() != null){
    522             if(pRlmiInfo.getPresSubscriptionState().getPresSubscriptionStateValue() ==
    523                     PresSubscriptionState.UCE_PRES_SUBSCRIPTION_STATE_TERMINATED){
    524                 isTerminated = true;
    525             }
    526         }
    527 
    528         if(isTerminated){
    529             TaskManager.getDefault().onTerminated(pRlmiInfo.getRequestId(),
    530                     pRlmiInfo.getSubscriptionTerminatedReason());
    531         }
    532 
    533         if (rcsPresenceInfoList.size() > 0) {
    534             // Notify presence changed
    535             Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED);
    536             intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST,
    537                     rcsPresenceInfoList);
    538             logger.debug("broadcast ACTION_PRESENCE_CHANGED, rcsPresenceInfo=" +
    539                     rcsPresenceInfoList);
    540             intent.putExtra("updateLastTimestamp", true);
    541             launchPersistService(intent);
    542         }
    543     }
    544 
    545     public void handleCmdStatus(PresCmdStatus pCmdStatus){
    546         if(pCmdStatus == null){
    547             logger.error("handleCallbackForCmdStatus pCmdStatus=null");
    548             return;
    549         }
    550 
    551 
    552         Task taskTmp = TaskManager.getDefault().getTask(pCmdStatus.getUserData());
    553         int resultCode = RcsUtils.statusCodeToResultCode(pCmdStatus.getStatus().getStatusCode());
    554         logger.print("handleCmdStatus resultCode=" + resultCode);
    555         PresenceTask task = null;
    556         if(taskTmp != null && (taskTmp instanceof PresenceTask)){
    557             task = (PresenceTask)taskTmp;
    558             task.mSipRequestId = pCmdStatus.getRequestId();
    559             task.mCmdStatus = resultCode;
    560             TaskManager.getDefault().putTask(task.mTaskId, task);
    561 
    562             // handle error as the same as temporary network error
    563             // set availability to false, keep old capability
    564             if(resultCode != ResultCode.SUCCESS && task.mContacts != null){
    565                 updateAvailabilityToUnknown(task);
    566             }
    567         }
    568 
    569         handleCallback(task, resultCode, true);
    570     }
    571 
    572     private void updateAvailabilityToUnknown(Task inTask){
    573         //only used for serviceState is offline or unknown.
    574         if(mContext == null){
    575             logger.error("updateAvailabilityToUnknown mContext=null");
    576             return;
    577         }
    578 
    579         if(inTask == null){
    580             logger.error("updateAvailabilityToUnknown task=null");
    581             return;
    582         }
    583 
    584         if(!(inTask instanceof PresenceTask)){
    585             logger.error("updateAvailabilityToUnknown not PresencTask");
    586             return;
    587         }
    588 
    589         PresenceTask task = (PresenceTask)inTask;
    590 
    591         if(task.mContacts == null || task.mContacts.length ==0){
    592             logger.error("updateAvailabilityToUnknown no contacts");
    593             return;
    594         }
    595 
    596         ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>();
    597         for(int i = 0; i< task.mContacts.length; i++){
    598             if(task.mContacts[i] == null || task.mContacts[i].length() == 0){
    599                 continue;
    600             }
    601 
    602             RcsPresenceInfo presenceInfo = new RcsPresenceInfo(
    603                 PresenceInfoParser.getPhoneFromUri(task.mContacts[i]),
    604                         RcsPresenceInfo.VolteStatus.VOLTE_UNKNOWN,
    605                         RcsPresenceInfo.ServiceState.UNKNOWN, null, System.currentTimeMillis(),
    606                         RcsPresenceInfo.ServiceState.UNKNOWN, null, System.currentTimeMillis());
    607             presenceInfoList.add(presenceInfo);
    608         }
    609 
    610         if(presenceInfoList.size() > 0) {
    611              // Notify presence information changed.
    612              logger.debug("notify presence changed for cmd error");
    613              Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED);
    614              intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST,
    615                      presenceInfoList);
    616 
    617              // don't update timestamp so it can be subscribed soon.
    618              intent.putExtra("updateLastTimestamp", false);
    619              launchPersistService(intent);
    620         }
    621     }
    622 }
    623 
    624