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 static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE; 20 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.os.RemoteException; 24 import android.telephony.ims.RcsContactUceCapability; 25 import android.telephony.ims.SipDetails; 26 import android.telephony.ims.stub.ImsRegistrationImplBase; 27 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; 28 import android.text.TextUtils; 29 import android.util.IndentingPrintWriter; 30 import android.util.LocalLog; 31 import android.util.Log; 32 33 import com.android.ims.RcsFeatureManager; 34 import com.android.ims.rcs.uce.UceStatsWriter; 35 import com.android.ims.rcs.uce.presence.pidfparser.PidfParser; 36 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback; 37 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishTriggerType; 38 import com.android.ims.rcs.uce.util.UceUtils; 39 import com.android.internal.annotations.VisibleForTesting; 40 41 import java.io.PrintWriter; 42 import java.time.Instant; 43 import java.util.Optional; 44 45 /** 46 * Send the publish request and handle the response of the publish request result. 47 */ 48 public class PublishProcessor { 49 50 private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishProcessor"; 51 52 // The length of time waiting for the response callback. 53 private static final long RESPONSE_CALLBACK_WAITING_TIME = 60000L; 54 55 private final int mSubId; 56 private final Context mContext; 57 private volatile boolean mIsDestroyed; 58 private volatile RcsFeatureManager mRcsFeatureManager; 59 60 private final UceStatsWriter mUceStatsWriter; 61 62 // Manage the state of the publish processor. 63 private PublishProcessorState mProcessorState; 64 65 // The information of the device's capabilities. 66 private final DeviceCapabilityInfo mDeviceCapabilities; 67 68 // The callback of the PublishController 69 private final PublishControllerCallback mPublishCtrlCallback; 70 71 // The lock of processing the pending request. 72 private final Object mPendingRequestLock = new Object(); 73 74 private final LocalLog mLocalLog = new LocalLog(UceUtils.LOG_SIZE); 75 PublishProcessor(Context context, int subId, DeviceCapabilityInfo capabilityInfo, PublishControllerCallback publishCtrlCallback)76 public PublishProcessor(Context context, int subId, DeviceCapabilityInfo capabilityInfo, 77 PublishControllerCallback publishCtrlCallback) { 78 mSubId = subId; 79 mContext = context; 80 mDeviceCapabilities = capabilityInfo; 81 mPublishCtrlCallback = publishCtrlCallback; 82 mProcessorState = new PublishProcessorState(subId); 83 mUceStatsWriter = UceStatsWriter.getInstance(); 84 } 85 86 @VisibleForTesting PublishProcessor(Context context, int subId, DeviceCapabilityInfo capabilityInfo, PublishControllerCallback publishCtrlCallback, UceStatsWriter instance)87 public PublishProcessor(Context context, int subId, DeviceCapabilityInfo capabilityInfo, 88 PublishControllerCallback publishCtrlCallback, UceStatsWriter instance) { 89 mSubId = subId; 90 mContext = context; 91 mDeviceCapabilities = capabilityInfo; 92 mPublishCtrlCallback = publishCtrlCallback; 93 mProcessorState = new PublishProcessorState(subId); 94 mUceStatsWriter = instance; 95 } 96 97 /** 98 * The RcsFeature has been connected to the framework. 99 */ onRcsConnected(RcsFeatureManager featureManager)100 public void onRcsConnected(RcsFeatureManager featureManager) { 101 mLocalLog.log("onRcsConnected"); 102 logi("onRcsConnected"); 103 mRcsFeatureManager = featureManager; 104 // Check if there is a pending request. 105 checkAndSendPendingRequest(); 106 } 107 108 /** 109 * The framework has lost the binding to the RcsFeature. 110 */ onRcsDisconnected()111 public void onRcsDisconnected() { 112 mLocalLog.log("onRcsDisconnected"); 113 logi("onRcsDisconnected"); 114 mRcsFeatureManager = null; 115 mProcessorState.onRcsDisconnected(); 116 // reset the publish capabilities. 117 mDeviceCapabilities.resetPresenceCapability(); 118 } 119 120 /** 121 * Set the destroy flag 122 */ onDestroy()123 public void onDestroy() { 124 mLocalLog.log("onDestroy"); 125 logi("onDestroy"); 126 mIsDestroyed = true; 127 } 128 129 /** 130 * Execute the publish request. This method is called by the handler of the PublishController. 131 * @param triggerType The type of triggering the publish request. 132 */ doPublish(@ublishTriggerType int triggerType)133 public void doPublish(@PublishTriggerType int triggerType) { 134 mProcessorState.setPublishingFlag(true); 135 if (!doPublishInternal(triggerType)) { 136 // Reset the publishing flag if the request cannot be sent to the IMS service. 137 mProcessorState.setPublishingFlag(false); 138 } 139 } 140 /** 141 * Execute the publish request internally. 142 * @param triggerType The type of triggering the publish request. 143 * @return true if the publish is sent to the IMS service successfully, false otherwise. 144 */ doPublishInternal(@ublishTriggerType int triggerType)145 private boolean doPublishInternal(@PublishTriggerType int triggerType) { 146 if (mIsDestroyed) return false; 147 148 mLocalLog.log("doPublishInternal: trigger type=" + triggerType); 149 logi("doPublishInternal: trigger type=" + triggerType); 150 151 // Return if this request is not allowed to be executed. 152 if (!isRequestAllowed(triggerType)) { 153 mLocalLog.log("doPublishInternal: The request is not allowed."); 154 return false; 155 } 156 157 // Get the latest device's capabilities. 158 RcsContactUceCapability deviceCapability; 159 if (triggerType == PublishController.PUBLISH_TRIGGER_SERVICE) { 160 deviceCapability = mDeviceCapabilities.getDeviceCapabilities( 161 CAPABILITY_MECHANISM_PRESENCE, mContext); 162 } else { 163 deviceCapability = mDeviceCapabilities.getChangedPresenceCapability(mContext); 164 } 165 if (deviceCapability == null) { 166 logi("doPublishInternal: device capability hasn't changed or is null"); 167 return false; 168 } 169 170 // Convert the device's capabilities to pidf format. 171 String pidfXml = PidfParser.convertToPidf(deviceCapability); 172 if (TextUtils.isEmpty(pidfXml)) { 173 logw("doPublishInternal: pidfXml is empty"); 174 return false; 175 } 176 177 // Set the pending request and return if RCS is not connected. When the RCS is connected 178 // afterward, it will send a new request if there's a pending request. 179 RcsFeatureManager featureManager = mRcsFeatureManager; 180 if (featureManager == null) { 181 logw("doPublishInternal: RCS is not connected."); 182 setPendingRequest(triggerType); 183 return false; 184 } 185 186 featureManager.getImsRegistrationTech((tech) -> { 187 int registrationTech = (tech == null) 188 ? ImsRegistrationImplBase.REGISTRATION_TECH_NONE : tech; 189 mUceStatsWriter.setImsRegistrationServiceDescStats(mSubId, 190 deviceCapability.getCapabilityTuples(), registrationTech); 191 }); 192 193 // Publish to the Presence server. 194 return publishCapabilities(featureManager, pidfXml); 195 } 196 197 /* 198 * According to the given trigger type, check whether the request is allowed to be executed or 199 * not. 200 */ isRequestAllowed(@ublishTriggerType int triggerType)201 private boolean isRequestAllowed(@PublishTriggerType int triggerType) { 202 // Check if the instance is destroyed. 203 if (mIsDestroyed) { 204 logd("isPublishAllowed: This instance is already destroyed"); 205 return false; 206 } 207 208 // Check if it has provisioned. When the provisioning changes, a new publish request will 209 // be triggered. 210 if (!isEabProvisioned()) { 211 logd("isPublishAllowed: NOT provisioned"); 212 return false; 213 } 214 215 // Do not request publish if the IMS is not registered. When the IMS is registered 216 // afterward, a new publish request will be triggered. 217 if (!mDeviceCapabilities.isImsRegistered()) { 218 logd("isPublishAllowed: IMS is not registered"); 219 return false; 220 } 221 222 // Skip this request if the PUBLISH is not allowed at current time. Resend the PUBLISH 223 // request and it will be triggered with an appropriate delay time. 224 if (!mProcessorState.isPublishAllowedAtThisTime()) { 225 logd("isPublishAllowed: Current time is not allowed, resend this request"); 226 mPublishCtrlCallback.requestPublishFromInternal(triggerType); 227 return false; 228 } 229 return true; 230 } 231 232 // Publish the device capabilities with the given pidf. publishCapabilities(@onNull RcsFeatureManager featureManager, @NonNull String pidfXml)233 private boolean publishCapabilities(@NonNull RcsFeatureManager featureManager, 234 @NonNull String pidfXml) { 235 PublishRequestResponse requestResponse = null; 236 try { 237 // Clear the pending flag because it is going to send the latest device's capabilities. 238 clearPendingRequest(); 239 240 // Generate a unique taskId to track this request. 241 long taskId = mProcessorState.generatePublishTaskId(); 242 requestResponse = new PublishRequestResponse(mPublishCtrlCallback, taskId, pidfXml); 243 244 mLocalLog.log("publish capabilities: taskId=" + taskId); 245 logi("publishCapabilities: taskId=" + taskId); 246 247 // request publication 248 featureManager.requestPublication(pidfXml, requestResponse.getResponseCallback()); 249 250 // Send a request canceled timer to avoid waiting too long for the response callback. 251 mPublishCtrlCallback.setupRequestCanceledTimer(taskId, RESPONSE_CALLBACK_WAITING_TIME); 252 253 // Inform that the publish request has been sent to the Ims Service. 254 mPublishCtrlCallback.notifyPendingPublishRequest(); 255 return true; 256 } catch (RemoteException e) { 257 mLocalLog.log("publish capability exception: " + e.getMessage()); 258 logw("publishCapabilities: exception=" + e.getMessage()); 259 // Exception occurred, end this request. 260 setRequestEnded(requestResponse); 261 checkAndSendPendingRequest(); 262 return false; 263 } 264 } 265 266 /** 267 * Handle the command error callback of the publish request. This method is called by the 268 * handler of the PublishController. 269 */ onCommandError(PublishRequestResponse requestResponse)270 public void onCommandError(PublishRequestResponse requestResponse) { 271 if (!checkRequestRespValid(requestResponse)) { 272 mLocalLog.log("Command error callback is invalid"); 273 logw("onCommandError: request response is invalid"); 274 setRequestEnded(requestResponse); 275 checkAndSendPendingRequest(); 276 return; 277 } 278 279 mLocalLog.log("Receive command error code=" + requestResponse.getCmdErrorCode()); 280 logd("onCommandError: " + requestResponse.toString()); 281 282 int cmdError = requestResponse.getCmdErrorCode().orElse(0); 283 boolean successful = false; 284 if (cmdError == RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE) { 285 successful = true; 286 } 287 mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.PUBLISH_EVENT, successful, cmdError, 0); 288 289 if (requestResponse.needRetry() && !mProcessorState.isReachMaximumRetries()) { 290 handleRequestRespWithRetry(requestResponse); 291 } else { 292 handleRequestRespWithoutRetry(requestResponse); 293 } 294 } 295 296 /** 297 * Handle the network response callback of the publish request. This method is called by the 298 * handler of the PublishController. 299 */ onNetworkResponse(PublishRequestResponse requestResponse)300 public void onNetworkResponse(PublishRequestResponse requestResponse) { 301 if (!checkRequestRespValid(requestResponse)) { 302 mLocalLog.log("Network response callback is invalid"); 303 logw("onNetworkResponse: request response is invalid"); 304 setRequestEnded(requestResponse); 305 checkAndSendPendingRequest(); 306 return; 307 } 308 309 mLocalLog.log("Receive network response code=" + requestResponse.getNetworkRespSipCode()); 310 logd("onNetworkResponse: " + requestResponse.toString()); 311 312 int responseCode = requestResponse.getNetworkRespSipCode().orElse(0); 313 mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.PUBLISH_EVENT, true, 0, 314 responseCode); 315 316 if (requestResponse.needRetry() && !mProcessorState.isReachMaximumRetries()) { 317 handleRequestRespWithRetry(requestResponse); 318 } else { 319 handleRequestRespWithoutRetry(requestResponse); 320 } 321 } 322 323 // Check if the request response callback is valid. checkRequestRespValid(PublishRequestResponse requestResponse)324 private boolean checkRequestRespValid(PublishRequestResponse requestResponse) { 325 if (requestResponse == null) { 326 logd("checkRequestRespValid: request response is null"); 327 return false; 328 } 329 330 if (!mProcessorState.isPublishingNow()) { 331 logd("checkRequestRespValid: the request is finished"); 332 return false; 333 } 334 335 // Abandon this response callback if the current taskId is different to the response 336 // callback taskId. This response callback is obsoleted. 337 long taskId = mProcessorState.getCurrentTaskId(); 338 long responseTaskId = requestResponse.getTaskId(); 339 if (taskId != responseTaskId) { 340 logd("checkRequestRespValid: invalid taskId! current taskId=" + taskId 341 + ", response callback taskId=" + responseTaskId); 342 return false; 343 } 344 345 if (mIsDestroyed) { 346 logd("checkRequestRespValid: is already destroyed! taskId=" + taskId); 347 return false; 348 } 349 return true; 350 } 351 352 /* 353 * Handle the publishing request with retry. This method is called when it receives a failed 354 * request response and need to retry. 355 */ handleRequestRespWithRetry(PublishRequestResponse requestResponse)356 private void handleRequestRespWithRetry(PublishRequestResponse requestResponse) { 357 // Increase the retry count 358 mProcessorState.increaseRetryCount(); 359 360 // reset the last capabilities because of the request is failed 361 mDeviceCapabilities.setPresencePublishResult(false); 362 // Reset the pending flag because it is going to resend a request. 363 clearPendingRequest(); 364 365 // Finish this request and resend a new publish request 366 setRequestEnded(requestResponse); 367 mPublishCtrlCallback.requestPublishFromInternal(PublishController.PUBLISH_TRIGGER_RETRY); 368 } 369 370 /* 371 * Handle the publishing request without retry. This method is called when it receives the 372 * request response and it does not need to retry. 373 */ handleRequestRespWithoutRetry(PublishRequestResponse requestResponse)374 private void handleRequestRespWithoutRetry(PublishRequestResponse requestResponse) { 375 updatePublishStateFromResponse(requestResponse); 376 // Finish the request and check if there is pending request. 377 setRequestEnded(requestResponse); 378 checkAndSendPendingRequest(); 379 } 380 381 // After checking the response, it handles calling PublishCtrlCallback. updatePublishStateFromResponse(PublishRequestResponse response)382 private void updatePublishStateFromResponse(PublishRequestResponse response) { 383 Instant responseTime = response.getResponseTimestamp(); 384 385 // Record the time when the request is successful and reset the retry count. 386 boolean publishSuccess = false; 387 if (response.isRequestSuccess()) { 388 mProcessorState.setLastPublishedTime(responseTime); 389 mProcessorState.resetRetryCount(); 390 publishSuccess = true; 391 } 392 // set the last capabilities according to the result of request. 393 mDeviceCapabilities.setPresencePublishResult(publishSuccess); 394 395 // Update the publish state after the request has finished. 396 int publishState = response.getPublishState(); 397 String pidfXml = response.getPidfXml(); 398 SipDetails details = response.getSipDetails().orElse(null); 399 mPublishCtrlCallback.updatePublishRequestResult(publishState, responseTime, pidfXml, 400 details); 401 402 // Refresh the device state with the publish request result. 403 response.getResponseSipCode().ifPresent(sipCode -> { 404 String reason = response.getResponseReason().orElse(""); 405 mPublishCtrlCallback.refreshDeviceState(sipCode, reason); 406 }); 407 } 408 409 /** 410 * Cancel the publishing request since it has token too long for waiting the response callback. 411 * This method is called by the handler of the PublishController. 412 */ cancelPublishRequest(long taskId)413 public void cancelPublishRequest(long taskId) { 414 mLocalLog.log("cancel publish request: taskId=" + taskId); 415 logd("cancelPublishRequest: taskId=" + taskId); 416 setRequestEnded(null); 417 checkAndSendPendingRequest(); 418 } 419 420 /* 421 * Finish the publishing request. This method is required to be called before the publishing 422 * request is finished. 423 */ setRequestEnded(PublishRequestResponse requestResponse)424 private void setRequestEnded(PublishRequestResponse requestResponse) { 425 long taskId = -1L; 426 if (requestResponse != null) { 427 requestResponse.onDestroy(); 428 taskId = requestResponse.getTaskId(); 429 } 430 mProcessorState.setPublishingFlag(false); 431 mPublishCtrlCallback.clearRequestCanceledTimer(); 432 433 mLocalLog.log("Set request ended: taskId=" + taskId); 434 logd("setRequestEnded: taskId=" + taskId); 435 } 436 437 /* 438 * Set the pending flag when it cannot be executed now. 439 */ setPendingRequest(@ublishTriggerType int triggerType)440 public void setPendingRequest(@PublishTriggerType int triggerType) { 441 synchronized (mPendingRequestLock) { 442 mProcessorState.setPendingRequest(triggerType); 443 } 444 } 445 446 /** 447 * Check and trigger a new publish request if there is a pending request. 448 */ checkAndSendPendingRequest()449 public void checkAndSendPendingRequest() { 450 synchronized (mPendingRequestLock) { 451 if (mIsDestroyed) return; 452 if (mProcessorState.hasPendingRequest()) { 453 // Retrieve the trigger type of the pending request 454 int type = mProcessorState.getPendingRequestTriggerType() 455 .orElse(PublishController.PUBLISH_TRIGGER_RETRY); 456 logd("checkAndSendPendingRequest: send pending request, type=" + type); 457 458 // Clear the pending flag because it is going to send a PUBLISH request. 459 mProcessorState.clearPendingRequest(); 460 mPublishCtrlCallback.requestPublishFromInternal(type); 461 } 462 } 463 } 464 465 /** 466 * Clear the pending request. It means that the publish request is triggered and this flag can 467 * be removed. 468 */ clearPendingRequest()469 private void clearPendingRequest() { 470 synchronized (mPendingRequestLock) { 471 mProcessorState.clearPendingRequest(); 472 } 473 } 474 475 /** 476 * Update the publishing allowed time with the given trigger type. This method wil be called 477 * before adding a PUBLISH request to the handler. 478 * @param triggerType The trigger type of this PUBLISH request 479 */ updatePublishingAllowedTime(@ublishTriggerType int triggerType)480 public void updatePublishingAllowedTime(@PublishTriggerType int triggerType) { 481 mProcessorState.updatePublishingAllowedTime(triggerType); 482 } 483 484 /** 485 * @return The delay time to allow to execute the PUBLISH request. This method will be called 486 * to determine the delay time before adding a PUBLISH request to the handler. 487 */ getPublishingDelayTime()488 public Optional<Long> getPublishingDelayTime() { 489 return mProcessorState.getPublishingDelayTime(); 490 } 491 492 /** 493 * Update the publish throttle. 494 */ updatePublishThrottle(int publishThrottle)495 public void updatePublishThrottle(int publishThrottle) { 496 mProcessorState.updatePublishThrottle(publishThrottle); 497 } 498 499 /** 500 * @return true if the publish request is running now. 501 */ isPublishingNow()502 public boolean isPublishingNow() { 503 return mProcessorState.isPublishingNow(); 504 } 505 506 /** 507 * Reset the retry count and time related publish. 508 */ resetState()509 public void resetState() { 510 mProcessorState.resetState(); 511 // reset the publish capabilities. 512 mDeviceCapabilities.resetPresenceCapability(); 513 } 514 515 /** 516 * Update publish status after handling on onPublishUpdate case 517 */ publishUpdated(PublishRequestResponse response)518 public void publishUpdated(PublishRequestResponse response) { 519 updatePublishStateFromResponse(response); 520 if (response != null) { 521 response.onDestroy(); 522 } 523 } 524 525 @VisibleForTesting setProcessorState(PublishProcessorState processorState)526 public void setProcessorState(PublishProcessorState processorState) { 527 mProcessorState = processorState; 528 } 529 530 @VisibleForTesting isEabProvisioned()531 protected boolean isEabProvisioned() { 532 return UceUtils.isEabProvisioned(mContext, mSubId); 533 } 534 logd(String log)535 private void logd(String log) { 536 Log.d(LOG_TAG, getLogPrefix().append(log).toString()); 537 } 538 logi(String log)539 private void logi(String log) { 540 Log.i(LOG_TAG, getLogPrefix().append(log).toString()); 541 } 542 logw(String log)543 private void logw(String log) { 544 Log.w(LOG_TAG, getLogPrefix().append(log).toString()); 545 } 546 getLogPrefix()547 private StringBuilder getLogPrefix() { 548 StringBuilder builder = new StringBuilder("["); 549 builder.append(mSubId); 550 builder.append("] "); 551 return builder; 552 } 553 dump(PrintWriter printWriter)554 public void dump(PrintWriter printWriter) { 555 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 556 pw.println("PublishProcessor" + "[subId: " + mSubId + "]:"); 557 pw.increaseIndent(); 558 559 pw.print("ProcessorState: isPublishing="); 560 pw.print(mProcessorState.isPublishingNow()); 561 pw.print(", hasReachedMaxRetries="); 562 pw.print(mProcessorState.isReachMaximumRetries()); 563 pw.print(", delayTimeToAllowPublish="); 564 pw.println(mProcessorState.getPublishingDelayTime().orElse(-1L)); 565 566 pw.println("Log:"); 567 pw.increaseIndent(); 568 mLocalLog.dump(pw); 569 pw.decreaseIndent(); 570 pw.println("---"); 571 572 pw.decreaseIndent(); 573 } 574 } 575