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