1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.modules.utils; 17 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Handler; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.RemoteException; 25 import android.os.SystemClock; 26 import android.util.Log; 27 28 import com.android.internal.annotations.GuardedBy; 29 30 import java.io.Serializable; 31 import java.time.Duration; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.concurrent.CompletableFuture; 35 import java.util.concurrent.ConcurrentLinkedQueue; 36 import java.util.concurrent.ExecutionException; 37 import java.util.concurrent.TimeUnit; 38 import java.util.concurrent.TimeoutException; 39 40 /** 41 * Generic interface for receiving a callback result from someone. 42 * Allow the server end to synchronously wait on the response from the client. 43 * This enables an RPC like system but with the ability to timeout and discard late results. 44 * 45 * <p>NOTE: Use the static {@link #get} method to retrieve an available instance of this class. 46 * If no instances are available, a new one is created. 47 */ 48 public final class SynchronousResultReceiver<T> implements Parcelable { 49 private static final String TAG = "SynchronousResultReceiver"; 50 private final boolean mLocal; 51 private boolean mIsCompleted; 52 private final static Object sLock = new Object(); 53 private final static int QUEUE_THRESHOLD = 4; 54 55 @GuardedBy("sLock") 56 private CompletableFuture<Result<T>> mFuture = new CompletableFuture<>(); 57 58 @GuardedBy("sLock") 59 private static final ConcurrentLinkedQueue<SynchronousResultReceiver> sAvailableReceivers 60 = new ConcurrentLinkedQueue<>(); 61 get()62 public static <T> SynchronousResultReceiver<T> get() { 63 synchronized(sLock) { 64 if (sAvailableReceivers.isEmpty()) { 65 return new SynchronousResultReceiver(); 66 } 67 SynchronousResultReceiver receiver = sAvailableReceivers.poll(); 68 receiver.resetLocked(); 69 return receiver; 70 } 71 } 72 SynchronousResultReceiver()73 private SynchronousResultReceiver() { 74 mLocal = true; 75 mIsCompleted = false; 76 } 77 78 @GuardedBy("sLock") releaseLocked()79 private void releaseLocked() { 80 mFuture = null; 81 if (sAvailableReceivers.size() < QUEUE_THRESHOLD) { 82 sAvailableReceivers.add(this); 83 } 84 } 85 86 @GuardedBy("sLock") resetLocked()87 private void resetLocked() { 88 mFuture = new CompletableFuture<>(); 89 mIsCompleted = false; 90 } 91 getFuture()92 private CompletableFuture<Result<T>> getFuture() { 93 synchronized (sLock) { 94 return mFuture; 95 } 96 } 97 98 public static class Result<T> implements Parcelable { 99 private final @Nullable T mObject; 100 private final RuntimeException mException; 101 Result(RuntimeException exception)102 public Result(RuntimeException exception) { 103 mObject = null; 104 mException = exception; 105 } 106 Result(@ullable T object)107 public Result(@Nullable T object) { 108 mObject = object; 109 mException = null; 110 } 111 112 /** 113 * Return the stored value 114 * May throw a {@link RuntimeException} thrown from the client 115 */ getValue(T defaultValue)116 public T getValue(T defaultValue) { 117 if (mException != null) { 118 throw mException; 119 } 120 if (mObject == null) { 121 return defaultValue; 122 } 123 return mObject; 124 } 125 describeContents()126 public int describeContents() { 127 return 0; 128 } 129 writeToParcel(@onNull Parcel out, int flags)130 public void writeToParcel(@NonNull Parcel out, int flags) { 131 out.writeValue(mObject); 132 out.writeValue(mException); 133 } 134 Result(Parcel in)135 private Result(Parcel in) { 136 mObject = (T)in.readValue(null); 137 mException= (RuntimeException)in.readValue(null); 138 } 139 140 public static final @NonNull Parcelable.Creator<Result<?>> CREATOR = 141 new Parcelable.Creator<Result<?>>() { 142 public Result createFromParcel(Parcel in) { 143 return new Result(in); 144 } 145 public Result[] newArray(int size) { 146 return new Result[size]; 147 } 148 }; 149 } 150 complete(Result<T> result)151 private void complete(Result<T> result) { 152 if (mIsCompleted) { 153 throw new IllegalStateException("Receiver has already been completed"); 154 } 155 mIsCompleted = true; 156 if (mLocal) { 157 getFuture().complete(result); 158 } else { 159 final ISynchronousResultReceiver rr; 160 synchronized (this) { 161 rr = mReceiver; 162 } 163 if (rr != null) { 164 try { 165 rr.send(result); 166 } catch (RemoteException e) { 167 Log.w(TAG, "Failed to complete future"); 168 } 169 } 170 } 171 } 172 173 /** 174 * Deliver a result to this receiver. 175 * 176 * @param resultData Additional data provided by you. 177 */ send(@ullable T resultData)178 public void send(@Nullable T resultData) { 179 complete(new Result<>(resultData)); 180 } 181 182 /** 183 * Deliver an {@link Exception} to this receiver 184 * 185 * @param e exception to be sent 186 */ propagateException(@onNull RuntimeException e)187 public void propagateException(@NonNull RuntimeException e) { 188 Objects.requireNonNull(e, "RuntimeException cannot be null"); 189 complete(new Result<>(e)); 190 } 191 192 /** 193 * Blocks waiting for the result from the remote client. 194 * 195 * If it is interrupted before completion of the duration, wait again with remaining time until 196 * the deadline. 197 * 198 * @param timeout The duration to wait before sending a {@link TimeoutException} 199 * @return the Result 200 * @throws TimeoutException if the timeout in milliseconds expired. 201 */ awaitResultNoInterrupt(@onNull Duration timeout)202 public @NonNull Result<T> awaitResultNoInterrupt(@NonNull Duration timeout) 203 throws TimeoutException { 204 Objects.requireNonNull(timeout, "Null timeout is not allowed"); 205 206 final long startWaitNanoTime = SystemClock.elapsedRealtimeNanos(); 207 Duration remainingTime = timeout; 208 while (!remainingTime.isNegative()) { 209 try { 210 Result<T> result = getFuture().get(remainingTime.toMillis(), TimeUnit.MILLISECONDS); 211 synchronized (sLock) { 212 releaseLocked(); 213 return result; 214 } 215 } catch (ExecutionException e) { 216 // This will NEVER happen. 217 throw new AssertionError("Error receiving response", e); 218 } catch (InterruptedException e) { 219 // The thread was interrupted, try and get the value again, this time 220 // with the remaining time until the deadline. 221 remainingTime = timeout.minus( 222 Duration.ofNanos(SystemClock.elapsedRealtimeNanos() - startWaitNanoTime)); 223 } 224 } 225 synchronized (sLock) { 226 releaseLocked(); 227 } 228 throw new TimeoutException(); 229 } 230 231 ISynchronousResultReceiver mReceiver = null; 232 233 private final class MyResultReceiver extends ISynchronousResultReceiver.Stub { send(@uppressWarnings"rawtypes") @onNull Result result)234 public void send(@SuppressWarnings("rawtypes") @NonNull Result result) { 235 @SuppressWarnings("unchecked") Result<T> res = (Result<T>) result; 236 CompletableFuture<Result<T>> future; 237 future = getFuture(); 238 if (future != null) { 239 future.complete(res); 240 } 241 } 242 } 243 describeContents()244 public int describeContents() { 245 return 0; 246 } 247 writeToParcel(@onNull Parcel out, int flags)248 public void writeToParcel(@NonNull Parcel out, int flags) { 249 synchronized (this) { 250 if (mReceiver == null) { 251 mReceiver = new MyResultReceiver(); 252 } 253 out.writeStrongBinder(mReceiver.asBinder()); 254 } 255 } 256 SynchronousResultReceiver(Parcel in)257 private SynchronousResultReceiver(Parcel in) { 258 mLocal = false; 259 mIsCompleted = false; 260 mReceiver = ISynchronousResultReceiver.Stub.asInterface(in.readStrongBinder()); 261 } 262 263 public static final @NonNull Parcelable.Creator<SynchronousResultReceiver<?>> CREATOR = 264 new Parcelable.Creator<SynchronousResultReceiver<?>>() { 265 public SynchronousResultReceiver<?> createFromParcel(Parcel in) { 266 return new SynchronousResultReceiver(in); 267 } 268 public SynchronousResultReceiver<?>[] newArray(int size) { 269 return new SynchronousResultReceiver[size]; 270 } 271 }; 272 } 273