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
29package com.android.service.ims.presence;
30
31import java.util.List;
32import java.util.ArrayList;
33import java.util.Timer;
34import java.util.TimerTask;
35import java.util.concurrent.Semaphore;
36import android.content.ContentValues;
37import android.text.TextUtils;
38
39import android.content.BroadcastReceiver;
40import android.content.ComponentName;
41import android.content.Context;
42import android.content.Intent;
43import android.content.IntentFilter;
44import com.android.internal.telephony.TelephonyIntents;
45import android.os.HandlerThread;
46import android.os.RemoteException;
47import android.telephony.TelephonyManager;
48import android.database.Cursor;
49
50import java.lang.String;
51import android.content.Context;
52import android.util.Log;
53
54import com.android.ims.internal.uce.presence.PresSipResponse;
55import com.android.ims.internal.uce.common.StatusCode;
56import com.android.ims.internal.uce.common.StatusCode;
57import com.android.ims.internal.uce.presence.PresSubscriptionState;
58import com.android.ims.internal.uce.presence.PresCmdStatus;
59import com.android.ims.internal.uce.presence.PresResInfo;
60import com.android.ims.internal.uce.presence.PresRlmiInfo;
61import com.android.ims.internal.uce.presence.PresTupleInfo;
62
63import com.android.ims.RcsPresenceInfo;
64import com.android.ims.RcsPresence;
65import com.android.ims.IRcsPresenceListener;
66import com.android.ims.RcsManager.ResultCode;
67import com.android.ims.RcsPresence.PublishState;
68
69import com.android.ims.internal.Logger;
70import com.android.ims.internal.ContactNumberUtils;
71import com.android.service.ims.TaskManager;
72import com.android.service.ims.Task;
73import com.android.service.ims.RcsStackAdaptor;
74import com.android.service.ims.RcsUtils;
75import com.android.service.ims.RcsSettingUtils;
76
77import com.android.service.ims.R;
78
79public 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