Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6e88ce37 authored by Grace Jia's avatar Grace Jia
Browse files

Add transaction related code.

Test: atest VoipCallTransactionTest
Change-Id: If2c9a5573f2099c08b66093ad1e170c952543c14
parent 642e11a3
Loading
Loading
Loading
Loading
+90 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.voip;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A VoipCallTransaction implementation that its sub transactions will be executed in parallel
 */
public class ParallelTransaction extends VoipCallTransaction {
    public ParallelTransaction(List<VoipCallTransaction> subTransactions) {
        super(subTransactions);
    }

    @Override
    public void start() {
        // post timeout work
        mHandler.postDelayed(() -> {
            if (mCompleted.getAndSet(true)) {
                return;
            }
            if (mCompleteListener != null) {
                mCompleteListener.onTransactionTimeout(mTransactionName);
            }
            finish();
        }, TIMEOUT_LIMIT);

        if (mSubTransactions != null && mSubTransactions.size() > 0) {
            TransactionManager.TransactionCompleteListener subTransactionListener =
                    new TransactionManager.TransactionCompleteListener() {
                        private final AtomicInteger mCount = new AtomicInteger(mSubTransactions.size());

                        @Override
                        public void onTransactionCompleted(VoipCallTransactionResult result,
                                String transactionName) {
                            if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
                                mHandler.post(() -> {
                                    VoipCallTransactionResult mainResult =
                                            new VoipCallTransactionResult(
                                                    VoipCallTransactionResult.RESULT_FAILED,
                                                    String.format("sub transaction %s failed",
                                                            transactionName));
                                    mCompleteListener.onTransactionCompleted(mainResult,
                                            mTransactionName);
                                    finish();
                                });
                            } else {
                                if (mCount.decrementAndGet() == 0) {
                                    scheduleTransaction();
                                }
                            }
                        }

                        @Override
                        public void onTransactionTimeout(String transactionName) {
                            mHandler.post(() -> {
                                VoipCallTransactionResult mainResult = new VoipCallTransactionResult(
                                        VoipCallTransactionResult.RESULT_FAILED,
                                        String.format("sub transaction %s timed out",
                                                transactionName));
                                mCompleteListener.onTransactionCompleted(mainResult,
                                        mTransactionName);
                                finish();
                            });
                        }
                    };
            for (VoipCallTransaction transaction : mSubTransactions) {
                transaction.setCompleteListener(subTransactionListener);
                transaction.start();
            }
        } else {
            scheduleTransaction();
        }
    }
}
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.voip;

import java.util.List;

/**
 * A VoipCallTransaction implementation that its sub transactions will be executed in serial
 */
public class SerialTransaction extends VoipCallTransaction {
    public SerialTransaction(List<VoipCallTransaction> subTransactions) {
        super(subTransactions);
    }

    @Override
    public void start() {
        // post timeout work
        mHandler.postDelayed(() -> {
            if (mCompleted.getAndSet(true)) {
                return;
            }
            if (mCompleteListener != null) {
                mCompleteListener.onTransactionTimeout(mTransactionName);
            }
            finish();
        }, TIMEOUT_LIMIT);

        if (mSubTransactions != null && mSubTransactions.size() > 0) {
            TransactionManager.TransactionCompleteListener subTransactionListener =
                    new TransactionManager.TransactionCompleteListener() {

                        @Override
                        public void onTransactionCompleted(VoipCallTransactionResult result,
                                String transactionName) {
                            if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
                                mHandler.post(() -> {
                                    VoipCallTransactionResult mainResult =
                                            new VoipCallTransactionResult(
                                                    VoipCallTransactionResult.RESULT_FAILED,
                                                    String.format("sub transaction %s failed",
                                                            transactionName));
                                    mCompleteListener.onTransactionCompleted(mainResult,
                                            mTransactionName);
                                    finish();
                                });
                            } else {
                                if (mSubTransactions.size() > 0) {
                                    VoipCallTransaction transaction = mSubTransactions.remove(0);
                                    transaction.setCompleteListener(this);
                                    transaction.start();
                                } else {
                                    scheduleTransaction();
                                }
                            }
                        }

                        @Override
                        public void onTransactionTimeout(String transactionName) {
                            mHandler.post(() -> {
                                VoipCallTransactionResult mainResult = new VoipCallTransactionResult(
                                        VoipCallTransactionResult.RESULT_FAILED,
                                        String.format("sub transaction %s timed out",
                                                transactionName));
                                mCompleteListener.onTransactionCompleted(mainResult,
                                        mTransactionName);
                                finish();
                            });
                        }
                    };
            VoipCallTransaction transaction = mSubTransactions.remove(0);
            transaction.setCompleteListener(subTransactionListener);
            transaction.start();
        } else {
            scheduleTransaction();
        }
    }
}
+117 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.voip;

import android.os.OutcomeReceiver;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayDeque;
import java.util.Queue;

public class TransactionManager {
    private static final String TAG = "VoipCallTransactionManager";
    private static TransactionManager INSTANCE = null;
    private static final Object sLock = new Object();
    private Queue<VoipCallTransaction> mTransactions;
    private VoipCallTransaction mCurrentTransaction;

    public interface TransactionCompleteListener {
        void onTransactionCompleted(VoipCallTransactionResult result, String transactionName);
        void onTransactionTimeout(String transactionName);
    }

    private TransactionManager() {
        mTransactions = new ArrayDeque<>();
        mCurrentTransaction = null;

    }

    public static TransactionManager getInstance() {
        synchronized (sLock) {
            if (INSTANCE == null) {
                INSTANCE = new TransactionManager();
            }
        }
        return INSTANCE;
    }

    @VisibleForTesting
    public static TransactionManager getTestInstance() {
        return new TransactionManager();
    }

    public void addTransaction(VoipCallTransaction transaction,
            OutcomeReceiver<VoipCallTransactionResult, Exception> receiver) {
        synchronized (sLock) {
            mTransactions.add(transaction);
            transaction.setCompleteListener(new TransactionCompleteListener() {
                @Override
                public void onTransactionCompleted(VoipCallTransactionResult result,
                        String transactionName) {
                    if (result.getResult() == 0
                        /* TODO: change this to static value in TelecomManager */) {
                        receiver.onResult(result);
                    } else {
                        receiver.onError(new Exception());
                    }
                    finishTransaction();
                }

                @Override
                public void onTransactionTimeout(String transactionName) {
                    receiver.onResult(new VoipCallTransactionResult(
                            VoipCallTransactionResult.RESULT_FAILED, transactionName + " timeout"));
                    finishTransaction();
                }
            });
        }
        startTransactions();
    }

    private void startTransactions() {
        synchronized (sLock) {
            if (mTransactions.isEmpty()) {
                // No transaction waiting for process
                return;
            }

            if (mCurrentTransaction != null) {
                // Ongoing transaction
                return;
            }
            mCurrentTransaction = mTransactions.poll();
            mCurrentTransaction.start();
        }
    }

    private void finishTransaction() {
        synchronized (sLock) {
            mCurrentTransaction = null;
        }
        startTransactions();
    }

    @VisibleForTesting
    public void clear() {
        synchronized (sLock) {
            for (VoipCallTransaction transaction : mTransactions) {
                transaction.finish();
            }
        }
    }
}
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.voip;

import android.os.Handler;
import android.os.HandlerThread;

import com.android.server.telecom.LoggedHandlerExecutor;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

public class VoipCallTransaction {
    //TODO: add log events
    protected static final long TIMEOUT_LIMIT = 5000L;
    protected final AtomicBoolean mCompleted = new AtomicBoolean(false);
    protected String mTransactionName = this.getClass().getSimpleName();
    private HandlerThread mHandlerThread;
    protected Handler mHandler;
    protected TransactionManager.TransactionCompleteListener mCompleteListener;
    protected List<VoipCallTransaction> mSubTransactions;

    public VoipCallTransaction(
            List<VoipCallTransaction> subTransactions) {
        mSubTransactions = subTransactions;
        mHandlerThread = new HandlerThread(this.toString());
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
    }

    public VoipCallTransaction() {
        this(null /** mSubTransactions */);
    }

    public void start() {
        // post timeout work
        mHandler.postDelayed(() -> {
            if (mCompleted.getAndSet(true)) {
                return;
            }
            if (mCompleteListener != null) {
                mCompleteListener.onTransactionTimeout(mTransactionName);
            }
            finish();
        }, TIMEOUT_LIMIT);

        scheduleTransaction();
    }

    protected void scheduleTransaction() {
        CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
        future.thenComposeAsync(this::processTransaction,
                        new LoggedHandlerExecutor(mHandler, mTransactionName + "@"
                                + hashCode() + ".pT", null))
                .thenApplyAsync(
                        (Function<VoipCallTransactionResult, Void>) result -> {
                            mCompleted.set(true);
                            if (mCompleteListener != null) {
                                mCompleteListener.onTransactionCompleted(result, mTransactionName);
                            }
                            finish();
                            return null;
                        });
    }

    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
        return CompletableFuture.completedFuture(
                new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED, null));
    }

    public void setCompleteListener(TransactionManager.TransactionCompleteListener listener) {
        mCompleteListener = listener;
    }

    public void finish() {
        // finish all sub transactions
        if (mSubTransactions != null && mSubTransactions.size() > 0) {
            mSubTransactions.forEach(VoipCallTransaction::finish);
        }
        mHandlerThread.quit();
    }
}
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.voip;

import com.android.server.telecom.Call;

import java.util.Objects;

public class VoipCallTransactionResult {
    public static final int RESULT_SUCCEED = 0;
    public static final int RESULT_FAILED = 1;

    private int mResult;
    private String mMessage;
    private Call mCall;

    public VoipCallTransactionResult(int result, String message) {
        mResult = result;
        mMessage = message;
    }

    public VoipCallTransactionResult(int result, Call call, String message) {
        mResult = result;
        mCall = call;
        mMessage = message;
    }

    public int getResult() {
        return mResult;
    }

    public String getMessage() {
        return mMessage;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof VoipCallTransactionResult)) return false;
        VoipCallTransactionResult that = (VoipCallTransactionResult) o;
        return mResult == that.mResult && Objects.equals(mMessage, that.mMessage);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mResult, mMessage);
    }
}
Loading