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

Commit d2b2000d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add transaction related code."

parents a9a45f33 6e88ce37
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