summaryrefslogtreecommitdiff
path: root/core/java/com/android/internal/inputmethod/CancellationGroup.java
blob: 3b2e1cd9b556a67d1cd914409170fd1f4f8af5d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/*
 * Copyright (C) 2020 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.internal.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

/**
 * A utility class, which works as both a factory class of a cancellation signal to cancel
 * all the completable objects.
 *
 * <p>TODO: Make this lock-free.</p>
 */
public final class CancellationGroup {
    private final Object mLock = new Object();

    /**
     * List of {@link CompletableFuture}, which can be used to propagate {@link #cancelAll()} to
     * completable objects.
     *
     * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p>
     */
    @Nullable
    @GuardedBy("mLock")
    private ArrayList<CompletableFuture<?>> mFutureList = null;

    @GuardedBy("mLock")
    private boolean mCanceled = false;

    /**
     * Tries to register the given {@link CompletableFuture} into the callback list if this
     * {@link CancellationGroup} is not yet cancelled.
     *
     * <p>If this {@link CancellationGroup} is already cancelled, then this method will immediately
     * call {@link CompletableFuture#cancel(boolean)} then return {@code false}.</p>
     *
     * <p>When this method returns {@code true}, call {@link #unregisterFuture(CompletableFuture)}
     * to remove the unnecessary object reference.</p>
     *
     * @param future {@link CompletableFuture} to be added to the cancellation callback list.
     * @return {@code true} if the given {@code future} is added to the callback list.
     *         {@code false} otherwise.
     */
    @AnyThread
    boolean tryRegisterFutureOrCancelImmediately(@NonNull CompletableFuture<?> future) {
        synchronized (mLock) {
            if (mCanceled) {
                future.cancel(false);
                return false;
            }
            if (mFutureList == null) {
                // Set the initial capacity to 1 with an assumption that usually there is up to 1
                // on-going operation.
                mFutureList = new ArrayList<>(1);
            }
            mFutureList.add(future);
            return true;
        }
    }

    @AnyThread
    void unregisterFuture(@NonNull CompletableFuture<?> future) {
        synchronized (mLock) {
            if (mFutureList != null) {
                mFutureList.remove(future);
            }
        }
    }

    /**
     * Cancel all the completable objects created from this {@link CancellationGroup}.
     *
     * <p>Secondary calls will be silently ignored.</p>
     */
    @AnyThread
    public void cancelAll() {
        synchronized (mLock) {
            if (!mCanceled) {
                mCanceled = true;
                if (mFutureList != null) {
                    mFutureList.forEach(future -> future.cancel(false));
                    mFutureList.clear();
                    mFutureList = null;
                }
            }
        }
    }

    /**
     * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise.
     */
    @AnyThread
    public boolean isCanceled() {
        synchronized (mLock) {
            return mCanceled;
        }
    }
}