• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.server.telecom.callsequencing;
18 
19 import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
20 
21 import android.os.OutcomeReceiver;
22 import android.telecom.TelecomManager;
23 import android.telecom.CallException;
24 import android.util.IndentingPrintWriter;
25 import android.util.Log;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.server.telecom.AnomalyReporterAdapter;
29 import com.android.server.telecom.flags.FeatureFlags;
30 import com.android.server.telecom.flags.Flags;
31 import java.util.ArrayDeque;
32 import java.util.ArrayList;
33 import java.util.Deque;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Queue;
37 import java.util.UUID;
38 import java.util.concurrent.CompletableFuture;
39 
40 public class TransactionManager {
41     private static final String TAG = "CallTransactionManager";
42     private static final int TRANSACTION_HISTORY_SIZE = 20;
43     private static TransactionManager INSTANCE = null;
44     private static final Object sLock = new Object();
45     private final Queue<CallTransaction> mTransactions;
46     private final Deque<CallTransaction> mCompletedTransactions;
47     private CallTransaction mCurrentTransaction;
48     private boolean mProcessingCallSequencing;
49     private AnomalyReporterAdapter mAnomalyReporter;
50     private FeatureFlags mFeatureFlags;
51     public static final UUID TRANSACTION_MANAGER_TIMEOUT_UUID =
52             UUID.fromString("9ccce52e-6694-4357-9e5e-516a9531b062");
53     public static final String TRANSACTION_MANAGER_TIMEOUT_MSG =
54             "TransactionManager hit a timeout while processing a transaction";
55 
56     public interface TransactionCompleteListener {
onTransactionCompleted(CallTransactionResult result, String transactionName)57         void onTransactionCompleted(CallTransactionResult result, String transactionName);
onTransactionTimeout(String transactionName)58         void onTransactionTimeout(String transactionName);
59     }
60 
TransactionManager()61     private TransactionManager() {
62         mTransactions = new ArrayDeque<>();
63         mCurrentTransaction = null;
64         if (Flags.enableCallSequencing()) {
65             mCompletedTransactions = new ArrayDeque<>();
66         } else
67             mCompletedTransactions = null;
68     }
69 
getInstance()70     public static TransactionManager getInstance() {
71         synchronized (sLock) {
72             if (INSTANCE == null) {
73                 INSTANCE = new TransactionManager();
74             }
75         }
76         return INSTANCE;
77     }
78 
setFeatureFlag(FeatureFlags flag)79     public void setFeatureFlag(FeatureFlags flag){
80        mFeatureFlags = flag;
81     }
82 
setAnomalyReporter(AnomalyReporterAdapter callAnomalyReporter)83     public void setAnomalyReporter(AnomalyReporterAdapter callAnomalyReporter){
84         mAnomalyReporter = callAnomalyReporter;
85     }
86 
87     @VisibleForTesting
getTestInstance()88     public static TransactionManager getTestInstance() {
89         return new TransactionManager();
90     }
91 
addTransaction(CallTransaction transaction, OutcomeReceiver<CallTransactionResult, CallException> receiver)92     public CompletableFuture<Boolean> addTransaction(CallTransaction transaction,
93             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
94         CompletableFuture<Boolean> transactionCompleteFuture = new CompletableFuture<>();
95         synchronized (sLock) {
96             mTransactions.add(transaction);
97         }
98         transaction.setCompleteListener(new TransactionCompleteListener() {
99             @Override
100             public void onTransactionCompleted(CallTransactionResult result,
101                     String transactionName) {
102                 Log.i(TAG, String.format("transaction %s completed: with result=[%d]",
103                         transactionName, result.getResult()));
104                 try {
105                     if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
106                         receiver.onResult(result);
107                         transactionCompleteFuture.complete(true);
108                     } else {
109                         receiver.onError(
110                                 new CallException(result.getMessage(),
111                                         result.getResult()));
112                         transactionCompleteFuture.complete(false);
113                     }
114                 } catch (Exception e) {
115                     Log.e(TAG, String.format("onTransactionCompleted: Notifying transaction result"
116                             + " %s resulted in an Exception.", result), e);
117                     transactionCompleteFuture.complete(false);
118                 }
119                 finishTransaction();
120             }
121 
122             @Override
123             public void onTransactionTimeout(String transactionName){
124                 Log.i(TAG, String.format("transaction %s timeout", transactionName));
125                 try {
126                     receiver.onError(new CallException(transactionName + " timeout",
127                             CODE_OPERATION_TIMED_OUT));
128                     transactionCompleteFuture.complete(false);
129                     if (mFeatureFlags != null && mAnomalyReporter != null &&
130                             mFeatureFlags.enableCallExceptionAnomReports()) {
131                         mAnomalyReporter.reportAnomaly(
132                                 TRANSACTION_MANAGER_TIMEOUT_UUID,
133                                 TRANSACTION_MANAGER_TIMEOUT_MSG);
134                     }
135                 } catch (Exception e) {
136                     Log.e(TAG, String.format("onTransactionTimeout: Notifying transaction "
137                             + " %s resulted in an Exception.", transactionName), e);
138                     transactionCompleteFuture.complete(false);
139                 }
140                 finishTransaction();
141             }
142         });
143 
144         startTransactions();
145         return transactionCompleteFuture;
146     }
147 
startTransactions()148     private void startTransactions() {
149         synchronized (sLock) {
150             if (mTransactions.isEmpty()) {
151                 // No transaction waiting for process
152                 return;
153             }
154 
155             if (mCurrentTransaction != null) {
156                 // Ongoing transaction
157                 return;
158             }
159             mCurrentTransaction = mTransactions.poll();
160         }
161         mCurrentTransaction.start();
162     }
163 
finishTransaction()164     private void finishTransaction() {
165         synchronized (sLock) {
166             if (mCurrentTransaction != null) {
167                 addTransactionToHistory(mCurrentTransaction);
168                 mCurrentTransaction = null;
169             }
170         }
171         startTransactions();
172     }
173 
174     @VisibleForTesting
clear()175     public void clear() {
176         List<CallTransaction> pendingTransactions;
177         synchronized (sLock) {
178             pendingTransactions = new ArrayList<>(mTransactions);
179         }
180         for (CallTransaction t : pendingTransactions) {
181             t.finish(new CallTransactionResult(CallException.CODE_ERROR_UNKNOWN
182                     /* TODO:: define error b/335703584 */, "clear called"));
183         }
184     }
185 
addTransactionToHistory(CallTransaction t)186     private void addTransactionToHistory(CallTransaction t) {
187         if (!Flags.enableCallSequencing()) return;
188 
189         mCompletedTransactions.add(t);
190         if (mCompletedTransactions.size() > TRANSACTION_HISTORY_SIZE) {
191             mCompletedTransactions.poll();
192         }
193     }
194 
195     /**
196      * Called when the dumpsys is created for telecom to capture the current state.
197      */
dump(IndentingPrintWriter pw)198     public void dump(IndentingPrintWriter pw) {
199         if (!Flags.enableCallSequencing()) {
200             pw.println("<<Flag not enabled>>");
201             return;
202         }
203         synchronized (sLock) {
204             pw.println("Pending Transactions:");
205             pw.increaseIndent();
206             for (CallTransaction t : mTransactions) {
207                 printPendingTransactionStats(t, pw);
208             }
209             pw.decreaseIndent();
210 
211             pw.println("Ongoing Transaction:");
212             pw.increaseIndent();
213             if (mCurrentTransaction != null) {
214                 printPendingTransactionStats(mCurrentTransaction, pw);
215             }
216             pw.decreaseIndent();
217 
218             pw.println("Completed Transactions:");
219             pw.increaseIndent();
220             for (CallTransaction t : mCompletedTransactions) {
221                 printCompleteTransactionStats(t, pw);
222             }
223             pw.decreaseIndent();
224         }
225     }
226 
227     /**
228      * Recursively print the pending {@link CallTransaction} stats for logging purposes.
229      * @param t The transaction that stats should be printed for
230      * @param pw The IndentingPrintWriter to print the result to
231      */
printPendingTransactionStats(CallTransaction t, IndentingPrintWriter pw)232     private void printPendingTransactionStats(CallTransaction t, IndentingPrintWriter pw) {
233         CallTransaction.Stats s = t.getStats();
234         if (s == null) {
235             pw.println(String.format(Locale.getDefault(), "%s: <NO STATS>", t.mTransactionName));
236             return;
237         }
238         pw.println(String.format(Locale.getDefault(),
239                 "[%s] %s: (result=[%s]), (created -> now : [%+d] mS),"
240                         + " (created -> started : [%+d] mS),"
241                         + " (started -> now : [%+d] mS)",
242                 s.addedTimeStamp, t.mTransactionName, parseTransactionResult(s),
243                 s.measureTimeSinceCreatedMs(), s.measureCreatedToStartedMs(),
244                 s.measureTimeSinceStartedMs()));
245 
246         if (t.mSubTransactions == null || t.mSubTransactions.isEmpty()) {
247             return;
248         }
249         pw.increaseIndent();
250         for (CallTransaction subTransaction : t.mSubTransactions) {
251             printPendingTransactionStats(subTransaction, pw);
252         }
253         pw.decreaseIndent();
254     }
255 
256     /**
257      * Recursively print the complete Transaction stats for logging purposes.
258      * @param t The transaction that stats should be printed for
259      * @param pw The IndentingPrintWriter to print the result to
260      */
printCompleteTransactionStats(CallTransaction t, IndentingPrintWriter pw)261     private void printCompleteTransactionStats(CallTransaction t, IndentingPrintWriter pw) {
262         CallTransaction.Stats s = t.getStats();
263         if (s == null) {
264             pw.println(String.format(Locale.getDefault(), "%s: <NO STATS>", t.mTransactionName));
265             return;
266         }
267         pw.println(String.format(Locale.getDefault(),
268                 "[%s] %s: (result=[%s]), (created -> started : [%+d] mS), "
269                         + "(started -> completed : [%+d] mS)",
270                 s.addedTimeStamp, t.mTransactionName, parseTransactionResult(s),
271                 s.measureCreatedToStartedMs(), s.measureStartedToCompletedMs()));
272 
273         if (t.mSubTransactions == null || t.mSubTransactions.isEmpty()) {
274             return;
275         }
276         pw.increaseIndent();
277         for (CallTransaction subTransaction : t.mSubTransactions) {
278             printCompleteTransactionStats(subTransaction, pw);
279         }
280         pw.decreaseIndent();
281     }
282 
parseTransactionResult(CallTransaction.Stats s)283     private String parseTransactionResult(CallTransaction.Stats s) {
284         if (s.isTimedOut()) return "TIMED OUT";
285         if (s.getTransactionResult() == null) return "PENDING";
286         if (s.getTransactionResult().getResult() == CallTransactionResult.RESULT_SUCCEED) {
287             return "SUCCESS";
288         }
289         return s.getTransactionResult().toString();
290     }
291 }
292