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