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