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;
20
21/**
22 * Provides the ability to cancel an operation in progress.
23 */
24public 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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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         */
198        void onCancel();
199    }
200
201    private static final class Transport extends ICancellationSignal.Stub {
202        final CancellationSignal mCancellationSignal = new CancellationSignal();
203
204        @Override
205        public void cancel() throws RemoteException {
206            mCancellationSignal.cancel();
207        }
208    }
209}
210