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.support.v4.os;
18
19import android.os.Build;
20
21/**
22 * Static library support version of the framework's {@link android.os.CancellationSignal}.
23 * Used to write apps that run on platforms prior to Android 4.1.  See the framework SDK
24 * documentation for a class overview.
25 */
26public final class CancellationSignal {
27    private boolean mIsCanceled;
28    private OnCancelListener mOnCancelListener;
29    private Object mCancellationSignalObj;
30    private boolean mCancelInProgress;
31
32    /**
33     * Creates a cancellation signal, initially not canceled.
34     */
35    public CancellationSignal() {
36    }
37
38    /**
39     * Returns true if the operation has been canceled.
40     *
41     * @return True if the operation has been canceled.
42     */
43    public boolean isCanceled() {
44        synchronized (this) {
45            return mIsCanceled;
46        }
47    }
48
49    /**
50     * Throws {@link OperationCanceledException} if the operation has been canceled.
51     *
52     * @throws OperationCanceledException if the operation has been canceled.
53     */
54    public void throwIfCanceled() {
55        if (isCanceled()) {
56            throw new OperationCanceledException();
57        }
58    }
59
60    /**
61     * Cancels the operation and signals the cancellation listener.
62     * If the operation has not yet started, then it will be canceled as soon as it does.
63     */
64    public void cancel() {
65        final OnCancelListener listener;
66        final Object obj;
67        synchronized (this) {
68            if (mIsCanceled) {
69                return;
70            }
71            mIsCanceled = true;
72            mCancelInProgress = true;
73            listener = mOnCancelListener;
74            obj = mCancellationSignalObj;
75        }
76
77        try {
78            if (listener != null) {
79                listener.onCancel();
80            }
81            if (obj != null) {
82                CancellationSignalCompatJellybean.cancel(obj);
83            }
84        } finally {
85            synchronized (this) {
86                mCancelInProgress = false;
87                notifyAll();
88            }
89        }
90    }
91
92    /**
93     * Sets the cancellation listener to be called when canceled.
94     *
95     * This method is intended to be used by the recipient of a cancellation signal
96     * such as a database or a content provider to handle cancellation requests
97     * while performing a long-running operation.  This method is not intended to be
98     * used by applications themselves.
99     *
100     * If {@link CancellationSignal#cancel} has already been called, then the provided
101     * listener is invoked immediately.
102     *
103     * This method is guaranteed that the listener will not be called after it
104     * has been removed.
105     *
106     * @param listener The cancellation listener, or null to remove the current listener.
107     */
108    public void setOnCancelListener(OnCancelListener listener) {
109        synchronized (this) {
110            waitForCancelFinishedLocked();
111
112            if (mOnCancelListener == listener) {
113                return;
114            }
115            mOnCancelListener = listener;
116            if (!mIsCanceled || listener == null) {
117                return;
118            }
119        }
120        listener.onCancel();
121    }
122
123    /**
124     * Gets the framework {@link android.os.CancellationSignal} associated with this object.
125     * <p>
126     * Framework support for cancellation signals was added in
127     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} so this method will always
128     * return null on older versions of the platform.
129     * </p>
130     *
131     * @return A framework cancellation signal object, or null on platform versions
132     * prior to Jellybean.
133     */
134    public Object getCancellationSignalObject() {
135        if (Build.VERSION.SDK_INT < 16) {
136            return null;
137        }
138        synchronized (this) {
139            if (mCancellationSignalObj == null) {
140                mCancellationSignalObj = CancellationSignalCompatJellybean.create();
141                if (mIsCanceled) {
142                    CancellationSignalCompatJellybean.cancel(mCancellationSignalObj);
143                }
144            }
145            return mCancellationSignalObj;
146        }
147    }
148
149    private void waitForCancelFinishedLocked() {
150        while (mCancelInProgress) {
151            try {
152                wait();
153            } catch (InterruptedException ex) {
154            }
155        }
156    }
157
158     /**
159     * Listens for cancellation.
160     */
161    public interface OnCancelListener {
162        /**
163         * Called when {@link CancellationSignal#cancel} is invoked.
164         */
165        void onCancel();
166    }
167}
168