1 /*
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 
17 package android.os;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 /**
22  * Provides the ability to cancel an operation in progress.
23  */
24 public 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      */
CancellationSignal()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      */
isCanceled()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      */
throwIfCanceled()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      */
cancel()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      */
setOnCancelListener(OnCancelListener listener)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      */
setRemote(ICancellationSignal remote)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)
waitForCancelFinishedLocked()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      */
createTransport()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      */
fromTransport(ICancellationSignal transport)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          */
onCancel()203         void onCancel();
204     }
205 
206     private static final class Transport extends ICancellationSignal.Stub {
207         final CancellationSignal mCancellationSignal = new CancellationSignal();
208 
209         @Override
cancel()210         public void cancel() throws RemoteException {
211             mCancellationSignal.cancel();
212         }
213     }
214 }
215