content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.server.backup.remote;
  
  import android.annotation.WorkerThread;
  import android.app.backup.IBackupCallback;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.RemoteException;
  
  import com.android.internal.util.Preconditions;
  
  import java.util.concurrent.CompletableFuture;
  import java.util.concurrent.ExecutionException;
  
  /**
   * A wrapper that encapsulates an outbound call from the system process, converting an asynchronous
   * operation into a synchronous operation with time-out and cancellation built-in. This was built to
   * be able to call one-way binder methods that accept a {@link IBackupCallback} as a callback and
   * handle the result inline.
   *
   * <p>Create one {@link RemoteCall} object providing the actual call in the form of a {@link
   * RemoteCallable} that accepts a {@link IBackupCallback}. Perform the call by calling {@link
   * #call()}, at which point {@link RemoteCall} will execute the callable providing an implementation
   * of the callback that communicates the result back to this object. Even if the call returns
   * straight away (which is the case for one-way methods) the method will only return when either the
   * callback is called, time-out happens, or someone calls {@link #cancel()}.
   *
   * <p>This class was designed to have the method {@link #call()} called only once.
   */
  // TODO: Kick-off callable in dedicated thread (because of local calls, which are synchronous)
  public class RemoteCall {
      /**
       * Creates a {@link RemoteCall} object with {@code callable} and {@code timeoutMs} and calls
       * {@link #call()} on it immediately after.
       *
       * <p>Note that you won't be able to cancel the call, to do that construct an object regularly
       * first, then use {@link #call()}.
       *
       * @see #RemoteCall(RemoteCallable, long)
       * @see #call()
       */
      public static RemoteResult execute(RemoteCallable<IBackupCallback> callable, long timeoutMs)
              throws RemoteException {
          return new RemoteCall(callable, timeoutMs).call();
      }
  
      private final RemoteCallable<IBackupCallback> mCallable;
      private final CompletableFuture<RemoteResult> mFuture;
      private final long mTimeoutMs;
  
      /**
       * Creates a new {@link RemoteCall} object for a given callable.
       *
       * @param callable A function that signals its completion by calling {@link
       *     IBackupCallback#operationComplete(long)} on the object provided as a parameter.
       * @param timeoutMs The time in milliseconds after which {@link #call()} will return with {@link
       *     RemoteResult#FAILED_TIMED_OUT} if the callable hasn't completed and no one canceled. The
       *     time starts to be counted in {@link #call()}.
       */
      public RemoteCall(RemoteCallable<IBackupCallback> callable, long timeoutMs) {
          this(false, callable, timeoutMs);
      }
  
      /**
       * Same as {@link #RemoteCall(RemoteCallable, long)} but with parameter {@code cancelled}.
       *
       * @param cancelled Whether the call has already been canceled. It has the same effect of
       *     calling {@link #cancel()} before {@link #call()}.
       * @see #RemoteCall(RemoteCallable, long)
       */
      public RemoteCall(boolean cancelled, RemoteCallable<IBackupCallback> callable, long timeoutMs) {
          mCallable = callable;
          mTimeoutMs = timeoutMs;
          mFuture = new CompletableFuture<>();
          if (cancelled) {
              cancel();
          }
      }
  
      /**
       * Kicks-off the callable provided in the constructor and blocks before returning, waiting for
       * the first of these to happen:
       *
       * <ul>
       *   <li>The callback passed to {@link RemoteCallable} is called with the result. We return a
       *       present {@link RemoteResult} with the result.
       *   <li>Time-out happens. We return {@link RemoteResult#FAILED_TIMED_OUT}.
       *   <li>Someone calls {@link #cancel()} on this object. We return {@link
       *       RemoteResult#FAILED_CANCELLED}.
       * </ul>
       *
       * <p>This method can't be called from the main thread and was designed to be called only once.
       *
       * @return A {@link RemoteResult} with the result of the operation.
       * @throws RemoteException If the callable throws it.
       */
      @WorkerThread
      public RemoteResult call() throws RemoteException {
          // If called on the main-thread we would never get a time-out != 0
          Preconditions.checkState(
                  !Looper.getMainLooper().isCurrentThread(), "Can't call call() on main thread");
  
          if (!mFuture.isDone()) {
              if (mTimeoutMs == 0L) {
                  timeOut();
              } else {
                  Handler.getMain().postDelayed(this::timeOut, mTimeoutMs);
                  mCallable.call(new FutureBackupCallback(mFuture));
              }
          }
          try {
              return mFuture.get();
          } catch (InterruptedException e) {
              return RemoteResult.FAILED_THREAD_INTERRUPTED;
          } catch (ExecutionException e) {
              throw new IllegalStateException("Future unexpectedly completed with an exception");
          }
      }
  
      /**
       * Attempts to cancel the operation. It will only be successful if executed before the callback
       * is called and before the time-out.
       *
       * <p>This method can be called from any thread, any time, including the same thread that called
       * {@link #call()} (which is obviously only possible if the former is called before the latter).
       */
      public void cancel() {
          mFuture.complete(RemoteResult.FAILED_CANCELLED);
      }
  
      private void timeOut() {
          mFuture.complete(RemoteResult.FAILED_TIMED_OUT);
      }
  }
  