• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.content.Context;
32 import android.net.Uri;
33 import android.telephony.SubscriptionManager;
34 import android.telephony.TelephonyManager;
35 import android.telephony.ims.RcsContactUceCapability;
36 import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
37 import android.text.TextUtils;
38 
39 import com.android.ims.ResultCode;
40 import com.android.ims.internal.ContactNumberUtils;
41 import com.android.ims.internal.Logger;
42 import com.android.service.ims.RcsSettingUtils;
43 import com.android.service.ims.Task;
44 import com.android.service.ims.TaskManager;
45 import java.util.ArrayList;
46 import java.util.List;
47 
48 public class PresenceSubscriber extends PresenceBase {
49     private Logger logger = Logger.getLogger(this.getClass().getName());
50 
51     private SubscribePublisher mSubscriber;
52     private final Object mSubscriberLock = new Object();
53 
54     private String mAvailabilityRetryNumber = null;
55     private int mAssociatedSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
56 
57     private final String[] mConfigVolteProvisionErrorOnSubscribeResponse;
58     private final String[] mConfigRcsProvisionErrorOnSubscribeResponse;
59 
60     /*
61      * Constructor
62      */
PresenceSubscriber(SubscribePublisher subscriber, Context context, String[] configVolteProvisionErrorOnSubscribeResponse, String[] configRcsProvisionErrorOnSubscribeResponse)63     public PresenceSubscriber(SubscribePublisher subscriber, Context context,
64             String[] configVolteProvisionErrorOnSubscribeResponse,
65             String[] configRcsProvisionErrorOnSubscribeResponse){
66         super(context);
67         synchronized(mSubscriberLock) {
68             this.mSubscriber = subscriber;
69         }
70         mConfigVolteProvisionErrorOnSubscribeResponse
71                 = configVolteProvisionErrorOnSubscribeResponse;
72         mConfigRcsProvisionErrorOnSubscribeResponse = configRcsProvisionErrorOnSubscribeResponse;
73     }
74 
updatePresenceSubscriber(SubscribePublisher subscriber)75     public void updatePresenceSubscriber(SubscribePublisher subscriber) {
76         synchronized(mSubscriberLock) {
77             logger.print("Update PresencePublisher");
78             this.mSubscriber = subscriber;
79         }
80     }
81 
removePresenceSubscriber()82     public void removePresenceSubscriber() {
83         synchronized(mSubscriberLock) {
84                 logger.print("Remove PresenceSubscriber");
85             this.mSubscriber = null;
86         }
87     }
88 
handleAssociatedSubscriptionChanged(int newSubId)89     public void handleAssociatedSubscriptionChanged(int newSubId) {
90         if (mAssociatedSubscription == newSubId) {
91             return;
92         }
93         mAssociatedSubscription = newSubId;
94     }
95 
numberToUriString(String number)96     private String numberToUriString(String number) {
97         String formattedContact = number;
98         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
99         if (tm != null && !formattedContact.startsWith("sip:")
100                 && !formattedContact.startsWith("tel:")){
101             String domain = tm.getIsimDomain();
102             logger.debug("domain=" + domain);
103             if (domain == null || domain.length() == 0){
104                 formattedContact = "tel:" + formattedContact;
105             } else {
106                 formattedContact = "sip:" + formattedContact + "@" + domain;
107             }
108         }
109 
110         logger.print("numberToUriString formattedContact=" + formattedContact);
111         return formattedContact;
112     }
113 
numberToTelString(String number)114     private String numberToTelString(String number){
115         String formatedContact = number;
116         if(!formatedContact.startsWith("sip:") && !formatedContact.startsWith("tel:")){
117             formatedContact = "tel:" + formatedContact;
118         }
119 
120         logger.print("numberToTelString formatedContact=" + formatedContact);
121         return formatedContact;
122     }
123 
requestCapability(List<String> contactsNumber, ContactCapabilityResponse listener)124     public int requestCapability(List<String> contactsNumber,
125             ContactCapabilityResponse listener) {
126 
127         SubscribePublisher subscriber = null;
128         synchronized(mSubscriberLock) {
129             subscriber = mSubscriber;
130         }
131 
132         if (subscriber == null) {
133             logger.error("requestCapability Subscribe not registered");
134             return ResultCode.SUBSCRIBE_NOT_REGISTERED;
135         }
136 
137         if (!RcsSettingUtils.hasUserEnabledContactDiscovery(mContext, mAssociatedSubscription)) {
138             logger.warn("requestCapability request has been denied due to contact discovery being "
139                     + "disabled by the user");
140             return ResultCode.ERROR_SERVICE_NOT_ENABLED;
141         }
142 
143         int ret = subscriber.getStackStatusForCapabilityRequest();
144         if (ret < ResultCode.SUCCESS) {
145             logger.error("requestCapability ret=" + ret);
146             return ret;
147         }
148 
149         if(contactsNumber == null || contactsNumber.size() ==0){
150             ret = ResultCode.SUBSCRIBE_INVALID_PARAM;
151             return ret;
152         }
153 
154         logger.debug("check contact size ...");
155         if (contactsNumber.size() > RcsSettingUtils.getMaxNumbersInRCL(mAssociatedSubscription)) {
156             ret = ResultCode.SUBSCRIBE_TOO_LARGE;
157             logger.error("requestCapability contctNumber size=" + contactsNumber.size());
158             return ret;
159         }
160 
161         String[] formatedNumbers = ContactNumberUtils.getDefault().format(contactsNumber);
162         int formatResult = ContactNumberUtils.getDefault().validate(formatedNumbers);
163         if (formatResult != ContactNumberUtils.NUMBER_VALID) {
164             logger.error("requestCapability formatResult=" + formatResult);
165             return ResultCode.SUBSCRIBE_INVALID_PARAM;
166         }
167 
168         String[] formatedContacts = new String[formatedNumbers.length];
169         for(int i=0; i<formatedContacts.length; i++){
170             formatedContacts[i] = numberToTelString(formatedNumbers[i]);
171         }
172         // In ms
173         long timeout = RcsSettingUtils.getCapabPollListSubExp(mAssociatedSubscription) * 1000;
174         timeout += RcsSettingUtils.getSIPT1Timer(mAssociatedSubscription);
175 
176         // The terminal notification may be received shortly after the time limit of
177         // the subscription due to network delays or retransmissions.
178         // Device shall wait for 3sec after the end of the subscription period in order to
179         // accept such notifications without returning spurious errors (e.g. SIP 481)
180         timeout += 3000;
181 
182         logger.print("add to task manager, formatedNumbers=" +
183                 PresenceUtils.toContactString(formatedNumbers));
184         int taskId = TaskManager.getDefault().addCapabilityTask(mContext, formatedNumbers,
185                 listener, timeout);
186         logger.print("taskId=" + taskId);
187 
188         ret = subscriber.requestCapability(formatedContacts, taskId);
189         if(ret < ResultCode.SUCCESS) {
190             logger.error("requestCapability ret=" + ret + " remove taskId=" + taskId);
191             TaskManager.getDefault().removeTask(taskId);
192         }
193 
194         ret = taskId;
195 
196         return  ret;
197     }
198 
requestAvailability(String contactNumber, ContactCapabilityResponse listener, boolean forceToNetwork)199     public int requestAvailability(String contactNumber, ContactCapabilityResponse listener,
200             boolean forceToNetwork) {
201 
202         String formatedContact = ContactNumberUtils.getDefault().format(contactNumber);
203         int ret = ContactNumberUtils.getDefault().validate(formatedContact);
204         if(ret != ContactNumberUtils.NUMBER_VALID){
205             return ret;
206         }
207 
208         if (!RcsSettingUtils.hasUserEnabledContactDiscovery(mContext, mAssociatedSubscription)) {
209             logger.warn("requestCapability request has been denied due to contact discovery being "
210                     + "disabled by the user");
211             return ResultCode.ERROR_SERVICE_NOT_ENABLED;
212         }
213 
214         if(!forceToNetwork){
215             logger.debug("check if we can use the value in cache");
216             int availabilityExpire =
217                     RcsSettingUtils.getAvailabilityCacheExpiration(mAssociatedSubscription);
218             availabilityExpire = availabilityExpire>0?availabilityExpire*1000:
219                     60*1000; // by default is 60s
220             logger.print("requestAvailability availabilityExpire=" + availabilityExpire);
221 
222             TaskManager.getDefault().clearTimeoutAvailabilityTask(availabilityExpire);
223 
224             Task task = TaskManager.getDefault().getAvailabilityTaskByContact(formatedContact);
225             if(task != null && task instanceof PresenceAvailabilityTask) {
226                 PresenceAvailabilityTask availabilityTask = (PresenceAvailabilityTask)task;
227                 if(availabilityTask.getNotifyTimestamp() == 0) {
228                     // The previous one didn't get response yet.
229                     logger.print("requestAvailability: the request is pending in queue");
230                     return ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE;
231                 }else {
232                     // not expire yet. Can use the previous value.
233                     logger.print("requestAvailability: the prevous valuedoesn't be expired yet");
234                     return ResultCode.SUBSCRIBE_TOO_FREQUENTLY;
235                 }
236             }
237         }
238 
239         // Only poll/fetch capability/availability on LTE
240         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
241         if(tm == null || (tm.getDataNetworkType() != TelephonyManager.NETWORK_TYPE_LTE)) {
242             logger.error("requestAvailability return ERROR_SERVICE_NOT_AVAILABLE" +
243                     " for it is not LTE network");
244             return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
245         }
246 
247         SubscribePublisher subscriber = null;
248         synchronized(mSubscriberLock) {
249             subscriber = mSubscriber;
250         }
251 
252         if (subscriber == null) {
253             logger.error("requestAvailability Subscribe not registered");
254             return ResultCode.SUBSCRIBE_NOT_REGISTERED;
255         }
256 
257         ret = subscriber.getStackStatusForCapabilityRequest();
258         if (ret < ResultCode.SUCCESS) {
259             logger.error("requestAvailability=" + ret);
260             return ret;
261         }
262 
263         // user number format in TaskManager.
264         int taskId = TaskManager.getDefault().addAvailabilityTask(formatedContact, listener);
265 
266         // Change it to URI format.
267         formatedContact = numberToUriString(formatedContact);
268 
269         logger.print("addAvailabilityTask formatedContact=" + formatedContact);
270 
271         ret = subscriber.requestAvailability(formatedContact, taskId);
272         if (ret < ResultCode.SUCCESS) {
273             logger.error("requestAvailability ret=" + ret + " remove taskId=" + taskId);
274             TaskManager.getDefault().removeTask(taskId);
275         }
276 
277         ret = taskId;
278 
279         return ret;
280     }
281 
translateResponse403(String reasonPhrase)282     private int translateResponse403(String reasonPhrase){
283         if(reasonPhrase == null){
284             // No retry. The PS provisioning has not occurred correctly. UX Decision to show errror.
285             return ResultCode.SUBSCRIBE_GENIRIC_FAILURE;
286         }
287 
288         reasonPhrase = reasonPhrase.toLowerCase();
289         if(reasonPhrase.contains("user not registered")){
290             // Register to IMS then retry the single resource subscription if capability polling.
291             // availability fetch: no retry. ignore the availability and allow LVC? (PLM decision)
292             return ResultCode.SUBSCRIBE_NOT_REGISTERED;
293         }
294 
295         if(reasonPhrase.contains("not authorized for presence")){
296             // No retry.
297             return ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE;
298         }
299 
300         // unknown phrase: handle it as the same as no phrase
301         return ResultCode.SUBSCRIBE_FORBIDDEN;
302     }
303 
translateResponseCode(int responseCode, String reasonPhrase)304     private int translateResponseCode(int responseCode, String reasonPhrase) {
305         // pSipResponse should not be null.
306         logger.debug("translateResponseCode getSipResponseCode=" +responseCode);
307         int ret = ResultCode.SUBSCRIBE_GENIRIC_FAILURE;
308 
309         if(responseCode < 100 || responseCode > 699){
310             logger.debug("internal error code sipCode=" + responseCode);
311             ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; //it is internal issue. ignore it.
312             return ret;
313         }
314 
315         switch(responseCode){
316             case 200:
317                 ret = ResultCode.SUCCESS;
318                 break;
319 
320             case 403:
321                 ret = translateResponse403(reasonPhrase);
322                 break;
323 
324             case 404:
325                // Target MDN is not provisioned for VoLTE or it is not  known as VzW IMS subscriber
326                // Device shall not retry. Device shall remove the VoLTE status of the target MDN
327                // and update UI
328                ret = ResultCode.SUBSCRIBE_NOT_FOUND;
329                break;
330 
331             case 408:
332                 // Request Timeout
333                 // Device shall retry with exponential back-off
334                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
335                 break;
336 
337             case 413:
338                 // Too Large.
339                 // Application need shrink the size of request contact list and resend the request
340                 ret = ResultCode.SUBSCRIBE_TOO_LARGE;
341                 break;
342 
343             case 423:
344                 // Interval Too Short. Requested expiry interval too short and server rejects it
345                 // Device shall re-attempt subscription after changing the expiration interval in
346                 // the Expires header field to be equal to or greater than the expiration interval
347                 // within the Min-Expires header field of the 423 response
348                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
349                 break;
350 
351             case 500:
352                 // 500 Server Internal Error
353                 // capability polling: exponential back-off retry (same rule as resource list)
354                 // availability fetch: no retry. ignore the availability and allow LVC
355                 // (PLM decision)
356                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
357                 break;
358 
359             case 503:
360                 // capability polling: exponential back-off retry (same rule as resource list)
361                 // availability fetch: no retry. ignore the availability and allow LVC?
362                 // (PLM decision)
363                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
364                 break;
365 
366                 // capability polling: Device shall retry with exponential back-off
367                 // Availability Fetch: device shall ignore the error and shall not retry
368             case 603:
369                 ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR;
370                 break;
371 
372             default:
373                 // Other 4xx/5xx/6xx
374                 // Device shall not retry
375                 ret = ResultCode.SUBSCRIBE_GENIRIC_FAILURE;
376         }
377 
378         logger.debug("translateResponseCode ret=" + ret);
379         return ret;
380     }
381 
onSipResponse(int requestId, int responseCode, String reasonPhrase)382     public void onSipResponse(int requestId, int responseCode, String reasonPhrase) {
383         SubscribePublisher subscriber = null;
384         synchronized(mSubscriberLock) {
385             subscriber = mSubscriber;
386         }
387 
388         if(isInConfigList(responseCode, reasonPhrase,
389                 mConfigVolteProvisionErrorOnSubscribeResponse)) {
390             logger.print("volte provision sipCode=" + responseCode + " phrase=" + reasonPhrase);
391             if (subscriber != null) {
392                 subscriber.updatePublisherState(PUBLISH_STATE_VOLTE_PROVISION_ERROR);
393             }
394 
395             notifyDm();
396         } else if(isInConfigList(responseCode, reasonPhrase,
397                 mConfigRcsProvisionErrorOnSubscribeResponse)) {
398             logger.print("rcs proRcsPresence.vision sipCode=" + responseCode + " phrase="
399                     + reasonPhrase);
400             if (subscriber != null) {
401                 subscriber.updatePublisherState(PUBLISH_STATE_RCS_PROVISION_ERROR);
402             }
403         }
404 
405         int errorCode = translateResponseCode(responseCode, reasonPhrase);
406         logger.print("handleSipResponse errorCode=" + errorCode);
407 
408         if(errorCode == ResultCode.SUBSCRIBE_NOT_REGISTERED){
409             logger.debug("setPublishState to unknown for subscribe error 403 not registered");
410             if (subscriber != null) {
411                 subscriber.updatePublisherState(PUBLISH_STATE_OTHER_ERROR);
412             }
413         }
414 
415         if(errorCode == ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE) {
416             logger.debug("ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE");
417         }
418 
419         if(errorCode == ResultCode.SUBSCRIBE_FORBIDDEN){
420             logger.debug("ResultCode.SUBSCRIBE_FORBIDDEN");
421         }
422 
423         // Suppose the request ID had been set when IQPresListener_CMDStatus
424         Task task = TaskManager.getDefault().getTaskByRequestId(requestId);
425         logger.debug("handleSipResponse task=" + task);
426         if(task != null){
427             task.mSipResponseCode = responseCode;
428             task.mSipReasonPhrase = reasonPhrase;
429             TaskManager.getDefault().putTask(task.mTaskId, task);
430         }
431 
432         if(errorCode == ResultCode.SUBSCRIBE_NOT_REGISTERED &&
433                 task != null && task.mCmdId == TaskManager.TASK_TYPE_GET_AVAILABILITY) {
434             String[] contacts = ((PresenceTask)task).mContacts;
435             if(contacts != null && contacts.length>0){
436                 mAvailabilityRetryNumber = contacts[0];
437             }
438             logger.debug("retry to get availability for " + mAvailabilityRetryNumber);
439         }
440 
441         // 404 error for single contact only as per requirement
442         // need handle 404 for multiple contacts as per CV 3.24.
443         if(errorCode == ResultCode.SUBSCRIBE_NOT_FOUND &&
444                 task != null && ((PresenceTask)task).mContacts != null) {
445             String[] contacts = ((PresenceTask)task).mContacts;
446             ArrayList<RcsContactUceCapability> contactCapabilities = new ArrayList<>();
447 
448             for(int i=0; i<contacts.length; i++){
449                 if(TextUtils.isEmpty(contacts[i])){
450                     continue;
451                 }
452                 logger.debug("onSipResponse: contact= " + contacts[i] + ", not found.");
453                 // Build contacts with no capabilities.
454                 contactCapabilities.add(buildContactWithNoCapabilities(
455                         PresenceUtils.convertContactNumber(contacts[i])));
456             }
457             handleCapabilityUpdate(task, contactCapabilities, true);
458 
459         } else if(errorCode == ResultCode.SUBSCRIBE_GENIRIC_FAILURE) {
460             updateAvailabilityToUnknown(task);
461         }
462 
463         handleCallback(task, errorCode, false);
464     }
465 
buildContactWithNoCapabilities(Uri contactUri)466     private RcsContactUceCapability buildContactWithNoCapabilities(Uri contactUri) {
467         PresenceBuilder presenceBuilder = new PresenceBuilder(contactUri,
468                 RcsContactUceCapability.SOURCE_TYPE_CACHED,
469                 RcsContactUceCapability.REQUEST_RESULT_FOUND);
470         return presenceBuilder.build();
471     }
472 
handleCapabilityUpdate(Task task, List<RcsContactUceCapability> capabilities, boolean updateLastTimestamp)473     private void handleCapabilityUpdate(Task task, List<RcsContactUceCapability> capabilities,
474             boolean updateLastTimestamp) {
475         if (task == null || task.mListener == null ) {
476             logger.warn("handleCapabilityUpdate, invalid listener!");
477             return;
478         }
479         task.mListener.onCapabilitiesUpdated(task.mTaskId, capabilities, updateLastTimestamp);
480     }
481 
retryToGetAvailability()482     public void retryToGetAvailability() {
483         if(mAvailabilityRetryNumber == null){
484             return;
485         }
486         requestAvailability(mAvailabilityRetryNumber, null, true);
487         //retry one time only
488         mAvailabilityRetryNumber = null;
489     }
490 
updatePresence(RcsContactUceCapability capabilities)491     public void updatePresence(RcsContactUceCapability capabilities) {
492         if(mContext == null){
493             logger.error("updatePresence mContext == null");
494             return;
495         }
496 
497         ArrayList<RcsContactUceCapability> presenceInfos = new ArrayList<>();
498         presenceInfos.add(capabilities);
499 
500         String contactNumber = capabilities.getContactUri().getSchemeSpecificPart();
501         // For single contact number we got 1 NOTIFY only. So regard it as terminated.
502         TaskManager.getDefault().onTerminated(contactNumber);
503 
504         PresenceAvailabilityTask availabilityTask = TaskManager.getDefault().
505                 getAvailabilityTaskByContact(contactNumber);
506         if (availabilityTask != null) {
507             availabilityTask.updateNotifyTimestamp();
508         }
509         Task task = TaskManager.getDefault().getTaskForSingleContactQuery(contactNumber);
510         handleCapabilityUpdate(task, presenceInfos, true);
511     }
512 
updatePresences(int requestId, List<RcsContactUceCapability> contactsCapabilities, boolean isTerminated, String terminatedReason)513     public void updatePresences(int requestId, List<RcsContactUceCapability> contactsCapabilities,
514             boolean isTerminated, String terminatedReason) {
515         if(mContext == null){
516             logger.error("updatePresences: mContext == null");
517             return;
518         }
519 
520         if (isTerminated) {
521             TaskManager.getDefault().onTerminated(requestId, terminatedReason);
522         }
523 
524         Task task = TaskManager.getDefault().getTaskByRequestId(requestId);
525         if (contactsCapabilities.size() > 0 || task != null) {
526             handleCapabilityUpdate(task, contactsCapabilities, true);
527         }
528     }
529 
onCommandStatusUpdated(int taskId, int requestId, int resultCode)530     public void onCommandStatusUpdated(int taskId, int requestId, int resultCode) {
531         Task taskTmp = TaskManager.getDefault().getTask(taskId);
532         logger.print("handleCmdStatus resultCode=" + resultCode);
533         PresenceTask task = null;
534         if(taskTmp != null && (taskTmp instanceof PresenceTask)){
535             task = (PresenceTask)taskTmp;
536             task.mSipRequestId = requestId;
537             task.mCmdStatus = resultCode;
538             TaskManager.getDefault().putTask(task.mTaskId, task);
539 
540             // handle error as the same as temporary network error
541             // set availability to false, keep old capability
542             if(resultCode != ResultCode.SUCCESS && task.mContacts != null){
543                 updateAvailabilityToUnknown(task);
544             }
545         }
546 
547         handleCallback(task, resultCode, true);
548     }
549 
updateAvailabilityToUnknown(Task inTask)550     private void updateAvailabilityToUnknown(Task inTask){
551         //only used for serviceState is offline or unknown.
552         if(mContext == null){
553             logger.error("updateAvailabilityToUnknown mContext=null");
554             return;
555         }
556 
557         if(inTask == null){
558             logger.error("updateAvailabilityToUnknown task=null");
559             return;
560         }
561 
562         if(!(inTask instanceof PresenceTask)){
563             logger.error("updateAvailabilityToUnknown not PresencTask");
564             return;
565         }
566 
567         PresenceTask task = (PresenceTask)inTask;
568 
569         if(task.mContacts == null || task.mContacts.length ==0){
570             logger.error("updateAvailabilityToUnknown no contacts");
571             return;
572         }
573 
574         ArrayList<RcsContactUceCapability> presenceInfoList = new ArrayList<>();
575         for (int i = 0; i< task.mContacts.length; i++) {
576             if(TextUtils.isEmpty(task.mContacts[i])){
577                 continue;
578             }
579             // Add each contacts with no capabilities.
580             Uri uri = PresenceUtils.convertContactNumber(task.mContacts[i]);
581             PresenceBuilder presenceBuilder = new PresenceBuilder(uri,
582                     RcsContactUceCapability.SOURCE_TYPE_CACHED,
583                     RcsContactUceCapability.REQUEST_RESULT_FOUND);
584             presenceInfoList.add(presenceBuilder.build());
585         }
586 
587         if(presenceInfoList.size() > 0) {
588              handleCapabilityUpdate(task, presenceInfoList, false);
589         }
590     }
591 }
592 
593