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 */ 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