1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ims.rcs.uce.presence.publish;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.telephony.ims.RcsUceAdapter;
22 import android.telephony.ims.SipDetails;
23 import android.telephony.ims.aidl.IPublishResponseCallback;
24 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
29 import com.android.ims.rcs.uce.util.NetworkSipCode;
30 import com.android.ims.rcs.uce.util.UceUtils;
31 
32 import java.time.Instant;
33 import java.util.Optional;
34 
35 /**
36  * Receiving the result callback of the publish request.
37  */
38 public class PublishRequestResponse {
39 
40     private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishRequestResp";
41 
42     private final long mTaskId;
43     private final String mPidfXml;
44     private volatile boolean mNeedRetry;
45     private volatile PublishControllerCallback mPublishCtrlCallback;
46 
47     private Optional<Integer> mCmdErrorCode;
48     private Optional<Integer> mNetworkRespSipCode;
49     private Optional<String> mReasonPhrase;
50     private Optional<Integer> mReasonHeaderCause;
51     private Optional<String> mReasonHeaderText;
52     private Optional<SipDetails> mSipDetails;
53     // The timestamp when receive the response from the network.
54     private Instant mResponseTimestamp;
55 
PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId, String pidfXml)56     public PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId,
57             String pidfXml) {
58         mTaskId = taskId;
59         mPidfXml = pidfXml;
60         mPublishCtrlCallback = publishCtrlCallback;
61         mCmdErrorCode = Optional.empty();
62         mNetworkRespSipCode = Optional.empty();
63         mReasonPhrase = Optional.empty();
64         mReasonHeaderCause = Optional.empty();
65         mReasonHeaderText = Optional.empty();
66         mSipDetails = Optional.empty();
67     }
68 
PublishRequestResponse(String pidfXml, @Nullable SipDetails details)69     public PublishRequestResponse(String pidfXml, @Nullable SipDetails details) {
70         mTaskId = 0L;
71         mPublishCtrlCallback = null;
72         mCmdErrorCode = Optional.empty();
73 
74         mPidfXml = pidfXml;
75         mResponseTimestamp = Instant.now();
76         mNetworkRespSipCode = Optional.of(details.getResponseCode());
77         mReasonPhrase = Optional.ofNullable(details.getResponsePhrase());
78         if (details.getReasonHeaderCause() != 0) {
79             mReasonHeaderCause = Optional.of(details.getReasonHeaderCause());
80         } else {
81             mReasonHeaderCause = Optional.empty();
82         }
83         if (TextUtils.isEmpty(details.getReasonHeaderText())) {
84             mReasonHeaderText = Optional.empty();
85         } else {
86             mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText());
87         }
88         mSipDetails = Optional.ofNullable(details);
89     }
90 
91     // The result callback of the publish capability request.
92     private IPublishResponseCallback mResponseCallback = new IPublishResponseCallback.Stub() {
93         @Override
94         public void onCommandError(int code) {
95             PublishRequestResponse.this.onCommandError(code);
96         }
97 
98         @Override
99         public void onNetworkResponse(@NonNull SipDetails details) {
100             PublishRequestResponse.this.onNetworkResponse(details);
101         }
102     };
103 
getResponseCallback()104     public IPublishResponseCallback getResponseCallback() {
105         return mResponseCallback;
106     }
107 
getTaskId()108     public long getTaskId() {
109         return mTaskId;
110     }
111 
112     /**
113      * Retrieve the command error code which received from the network.
114      */
getCmdErrorCode()115     public Optional<Integer> getCmdErrorCode() {
116         return mCmdErrorCode;
117     }
118 
119     /**
120      * Retrieve the network response sip code which received from the network.
121      */
getNetworkRespSipCode()122     public Optional<Integer> getNetworkRespSipCode() {
123         return mNetworkRespSipCode;
124     }
125 
126     /**
127      * Retrieve the reason phrase of the network response which received from the network.
128      */
getReasonPhrase()129     public Optional<String> getReasonPhrase() {
130         return mReasonPhrase;
131     }
132 
133     /**
134      * Retrieve the reason header from the network response.
135      */
getReasonHeaderCause()136     public Optional<Integer> getReasonHeaderCause() {
137         return mReasonHeaderCause;
138     }
139 
140     /**
141      * Retrieve the description of the reason header.
142      */
getReasonHeaderText()143     public Optional<String> getReasonHeaderText() {
144         return mReasonHeaderText;
145     }
146 
147     /**
148      * Retrieve the sip information which received from the network.
149      */
getSipDetails()150     public Optional<SipDetails> getSipDetails() {
151         return mSipDetails;
152     }
153     /**
154      * Retrieve the SIP code from the network response. It will get the value from the Reason
155      * Header first. If the ReasonHeader is not present, it will get the value from the Network
156      * response instead.
157      */
getResponseSipCode()158     public Optional<Integer> getResponseSipCode() {
159         return (mReasonHeaderCause.isPresent()) ? mReasonHeaderCause : mNetworkRespSipCode;
160     }
161 
162     /**
163      * Retrieve the REASON from the network response. It will get the value from the Reason Header
164      * first. If the ReasonHeader is not present, it will get the value from the Network response
165      * instead.
166      */
getResponseReason()167     public Optional<String> getResponseReason() {
168         return (mReasonHeaderText.isPresent()) ? mReasonHeaderText : mReasonPhrase;
169     }
170 
171     /**
172      * Get the timestamp of receiving the network response callback.
173      */
getResponseTimestamp()174     public @Nullable Instant getResponseTimestamp() {
175         return mResponseTimestamp;
176     }
177 
178     /**
179      * @return the PIDF XML sent during this request.
180      */
getPidfXml()181     public String getPidfXml() {
182         return mPidfXml;
183     }
184 
onDestroy()185     public void onDestroy() {
186         mPublishCtrlCallback = null;
187     }
188 
onCommandError(int errorCode)189     private void onCommandError(int errorCode) {
190         mResponseTimestamp = Instant.now();
191         mCmdErrorCode = Optional.of(errorCode);
192         updateRetryFlagByCommandError();
193 
194         PublishControllerCallback ctrlCallback = mPublishCtrlCallback;
195         if (ctrlCallback != null) {
196             ctrlCallback.onRequestCommandError(this);
197         } else {
198             Log.d(LOG_TAG, "onCommandError: already destroyed. error code=" + errorCode);
199         }
200     }
201 
onNetworkResponse(@onNull SipDetails details)202     private void onNetworkResponse(@NonNull SipDetails details) {
203         // When we send a request to PUBLISH and there is no change to the UCE capabilities, we
204         // expected onCommandError() with COMMAND_CODE_NO_CHANGE.
205         // But some of the vendor will instead send SIP code 999.
206         if (details.getResponseCode() == 999) {
207             onCommandError(RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE);
208             return;
209         }
210         mResponseTimestamp = Instant.now();
211         mNetworkRespSipCode = Optional.of(details.getResponseCode());
212         mReasonPhrase = Optional.ofNullable(details.getResponsePhrase());
213         if (details.getReasonHeaderCause() != 0) {
214             mReasonHeaderCause = Optional.of(details.getReasonHeaderCause());
215         }
216         if (TextUtils.isEmpty(details.getReasonHeaderText())) {
217             mReasonHeaderText = Optional.empty();
218         } else {
219             mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText());
220         }
221         mSipDetails = Optional.ofNullable(details);
222         updateRetryFlagByNetworkResponse();
223 
224         PublishControllerCallback ctrlCallback = mPublishCtrlCallback;
225         if (ctrlCallback != null) {
226             ctrlCallback.onRequestNetworkResp(this);
227         } else {
228             Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sip code="
229                     + details.getResponseCode());
230         }
231     }
232 
updateRetryFlagByCommandError()233     private void updateRetryFlagByCommandError() {
234         switch(getCmdErrorCode().orElse(-1)) {
235             case RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT:
236             case RcsCapabilityExchangeImplBase.COMMAND_CODE_INSUFFICIENT_MEMORY:
237             case RcsCapabilityExchangeImplBase.COMMAND_CODE_LOST_NETWORK_CONNECTION:
238             case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNAVAILABLE:
239                 mNeedRetry = true;
240                 break;
241         }
242     }
243 
updateRetryFlagByNetworkResponse()244     private void updateRetryFlagByNetworkResponse() {
245         // Disable retry flag because the retry mechanism is implemented in the ImsService.
246         mNeedRetry = false;
247     }
248 
249     /*
250      * Check whether the publishing request is successful.
251      */
isRequestSuccess()252     public boolean isRequestSuccess() {
253         if (isCommandError()) {
254             return false;
255         }
256         // The result of the request was treated as successful if the command error code is present
257         // and its value is COMMAND_CODE_NO_CHANGE.
258         if (isCommandCodeNoChange()) {
259             return true;
260         }
261 
262         final int sipCodeOk = NetworkSipCode.SIP_CODE_OK;
263         if (getNetworkRespSipCode().filter(c -> c == sipCodeOk).isPresent() &&
264                 (!getReasonHeaderCause().isPresent()
265                         || getReasonHeaderCause().filter(c -> c == sipCodeOk).isPresent())) {
266             return true;
267         }
268         return false;
269     }
270 
271     /**
272      * Check if the PUBLISH request is failed with receiving the command error.
273      * @return true if the command is failure.
274      */
isCommandError()275     private boolean isCommandError() {
276         // The request is failed if the command error code is present and its value is not
277         // COMMAND_CODE_NO_CHANGE.
278         if (getCmdErrorCode().isPresent() && !isCommandCodeNoChange()) {
279             return true;
280         }
281         return false;
282     }
283 
284     // @return true If it received the command code COMMAND_CODE_NO_CHANGE
isCommandCodeNoChange()285     private boolean isCommandCodeNoChange() {
286         if (getCmdErrorCode().filter(code ->
287                 code == RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE).isPresent()) {
288             return true;
289         }
290         return false;
291     }
292 
293     /**
294      * Check whether the publishing request needs to be retried.
295      */
needRetry()296     public boolean needRetry() {
297         return mNeedRetry;
298     }
299 
300     /**
301      * @return The publish state when the publish request is finished.
302      */
getPublishState()303      public int getPublishState() {
304          if (isCommandError()) {
305              return getPublishStateByCmdErrorCode();
306          } else {
307              return getPublishStateByNetworkResponse();
308          }
309      }
310 
311     /**
312      * Convert the command error code to the publish state
313      */
getPublishStateByCmdErrorCode()314     private int getPublishStateByCmdErrorCode() {
315         if (getCmdErrorCode().orElse(-1) ==
316                 RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT) {
317             return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
318         }
319         return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
320     }
321 
322     /**
323      * Convert the network sip code to the publish state
324      */
getPublishStateByNetworkResponse()325     private int getPublishStateByNetworkResponse() {
326         int respSipCode;
327         if (isCommandCodeNoChange()) {
328             // If the command code is COMMAND_CODE_NO_CHANGE, it should be treated as successful.
329             respSipCode = NetworkSipCode.SIP_CODE_OK;
330         } else if (getReasonHeaderCause().isPresent()) {
331             respSipCode = getReasonHeaderCause().get();
332         } else {
333             respSipCode = getNetworkRespSipCode().orElse(-1);
334         }
335 
336         switch (respSipCode) {
337             case NetworkSipCode.SIP_CODE_OK:
338                 return RcsUceAdapter.PUBLISH_STATE_OK;
339             case NetworkSipCode.SIP_CODE_FORBIDDEN:
340             case NetworkSipCode.SIP_CODE_NOT_FOUND:
341             case NetworkSipCode.SIP_CODE_SERVER_TIMEOUT:
342                 return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR;
343             case NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT:
344                 return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
345             default:
346                 return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
347         }
348     }
349 
350     /**
351      * Get the information of the publish request response.
352      */
353     @Override
toString()354     public String toString() {
355         StringBuilder builder = new StringBuilder();
356         builder.append("taskId=").append(mTaskId)
357                 .append(", CmdErrorCode=").append(getCmdErrorCode().orElse(-1))
358                 .append(", NetworkRespSipCode=").append(getNetworkRespSipCode().orElse(-1))
359                 .append(", ReasonPhrase=").append(getReasonPhrase().orElse(""))
360                 .append(", ReasonHeaderCause=").append(getReasonHeaderCause().orElse(-1))
361                 .append(", ReasonHeaderText=").append(getReasonHeaderText().orElse(""))
362                 .append(", ResponseTimestamp=").append(mResponseTimestamp)
363                 .append(", isRequestSuccess=").append(isRequestSuccess())
364                 .append(", needRetry=").append(mNeedRetry);
365         return builder.toString();
366     }
367 }
368