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