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 import android.os.ICancellationSignal.Stub; 21 22 /** 23 * Provides the ability to cancel an operation in progress. 24 */ 25 public 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 */ CancellationSignal()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 */ isCanceled()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 */ throwIfCanceled()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 */ cancel()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 */ setOnCancelListener(OnCancelListener listener)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 */ setRemote(ICancellationSignal remote)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 waitForCancelFinishedLocked()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 */ createTransport()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 */ fromTransport(ICancellationSignal transport)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 */ onCancel()199 void onCancel(); 200 } 201 202 private static final class Transport extends ICancellationSignal.Stub { 203 final CancellationSignal mCancellationSignal = new CancellationSignal(); 204 205 @Override cancel()206 public void cancel() throws RemoteException { 207 mCancellationSignal.cancel(); 208 } 209 } 210 } 211