blob: 260f7ade8ab713b68d11d45850c59d15c2107b16 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2012 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
17package android.os;
18
19import android.compat.annotation.UnsupportedAppUsage;
20
21/**
22 * Provides the ability to cancel an operation in progress.
23 */
24public final class CancellationSignal {
25 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
26 private boolean mIsCanceled;
27 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
28 private OnCancelListener mOnCancelListener;
29 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
30 private ICancellationSignal mRemote;
31 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
32 private boolean mCancelInProgress;
33
34 /**
35 * Creates a cancellation signal, initially not canceled.
36 */
37 public CancellationSignal() {
38 }
39
40 /**
41 * Returns true if the operation has been canceled.
42 *
43 * @return True if the operation has been canceled.
44 */
45 public boolean isCanceled() {
46 synchronized (this) {
47 return mIsCanceled;
48 }
49 }
50
51 /**
52 * Throws {@link OperationCanceledException} if the operation has been canceled.
53 *
54 * @throws OperationCanceledException if the operation has been canceled.
55 */
56 public void throwIfCanceled() {
57 if (isCanceled()) {
58 throw new OperationCanceledException();
59 }
60 }
61
62 /**
63 * Cancels the operation and signals the cancellation listener.
64 * If the operation has not yet started, then it will be canceled as soon as it does.
65 */
66 public void cancel() {
67 final OnCancelListener listener;
68 final ICancellationSignal remote;
69 synchronized (this) {
70 if (mIsCanceled) {
71 return;
72 }
73 mIsCanceled = true;
74 mCancelInProgress = true;
75 listener = mOnCancelListener;
76 remote = mRemote;
77 }
78
79 try {
80 if (listener != null) {
81 listener.onCancel();
82 }
83 if (remote != null) {
84 try {
85 remote.cancel();
86 } catch (RemoteException ex) {
87 }
88 }
89 } finally {
90 synchronized (this) {
91 mCancelInProgress = false;
92 notifyAll();
93 }
94 }
95 }
96
97 /**
98 * Sets the cancellation listener to be called when canceled.
99 *
100 * This method is intended to be used by the recipient of a cancellation signal
101 * such as a database or a content provider to handle cancellation requests
102 * while performing a long-running operation. This method is not intended to be
103 * used by applications themselves.
104 *
105 * If {@link CancellationSignal#cancel} has already been called, then the provided
106 * listener is invoked immediately.
107 *
108 * This method is guaranteed that the listener will not be called after it
109 * has been removed.
110 *
111 * @param listener The cancellation listener, or null to remove the current listener.
112 */
113 public void setOnCancelListener(OnCancelListener listener) {
114 synchronized (this) {
115 waitForCancelFinishedLocked();
116
117 if (mOnCancelListener == listener) {
118 return;
119 }
120 mOnCancelListener = listener;
121 if (!mIsCanceled || listener == null) {
122 return;
123 }
124 }
125 listener.onCancel();
126 }
127
128 /**
129 * Sets the remote transport.
130 *
131 * If {@link CancellationSignal#cancel} has already been called, then the provided
132 * remote transport is canceled immediately.
133 *
134 * This method is guaranteed that the remote transport will not be called after it
135 * has been removed.
136 *
137 * @param remote The remote transport, or null to remove.
138 *
139 * @hide
140 */
141 public void setRemote(ICancellationSignal remote) {
142 synchronized (this) {
143 waitForCancelFinishedLocked();
144
145 if (mRemote == remote) {
146 return;
147 }
148 mRemote = remote;
149 if (!mIsCanceled || remote == null) {
150 return;
151 }
152 }
153 try {
154 remote.cancel();
155 } catch (RemoteException ex) {
156 }
157 }
158
159 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
160 private void waitForCancelFinishedLocked() {
161 while (mCancelInProgress) {
162 try {
163 wait();
164 } catch (InterruptedException ex) {
165 }
166 }
167 }
168
169 /**
170 * Creates a transport that can be returned back to the caller of
171 * a Binder function and subsequently used to dispatch a cancellation signal.
172 *
173 * @return The new cancellation signal transport.
174 *
175 * @hide
176 */
177 public static ICancellationSignal createTransport() {
178 return new Transport();
179 }
180
181 /**
182 * Given a locally created transport, returns its associated cancellation signal.
183 *
184 * @param transport The locally created transport, or null if none.
185 * @return The associated cancellation signal, or null if none.
186 *
187 * @hide
188 */
189 public static CancellationSignal fromTransport(ICancellationSignal transport) {
190 if (transport instanceof Transport) {
191 return ((Transport)transport).mCancellationSignal;
192 }
193 return null;
194 }
195
196 /**
197 * Listens for cancellation.
198 */
199 public interface OnCancelListener {
200 /**
201 * Called when {@link CancellationSignal#cancel} is invoked.
202 */
203 void onCancel();
204 }
205
206 private static final class Transport extends ICancellationSignal.Stub {
207 final CancellationSignal mCancellationSignal = new CancellationSignal();
208
209 @Override
210 public void cancel() throws RemoteException {
211 mCancellationSignal.cancel();
212 }
213 }
214}