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