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.internal.infra;
  
  import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.ServiceConnection;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.IBinder.DeathRecipient;
  import android.os.IInterface;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.UserHandle;
  import android.util.Slog;
  import android.util.TimeUtils;
  
  import com.android.internal.annotations.GuardedBy;
  
  import java.io.PrintWriter;
  import java.lang.ref.WeakReference;
  import java.util.ArrayList;
  
  /**
   * Base class representing a remote service.
   *
   * <p>It abstracts away the binding and unbinding from the remote implementation, so clients can
   * call its methods without worrying about when and how to bind/unbind/timeout.
   *
   * <p>All state of this class is modified on a handler thread.
   *
   * <p><b>NOTE: </b>this class should not be extended directly, you should extend either
   * {@link AbstractSinglePendingRequestRemoteService} or
   * {@link AbstractMultiplePendingRequestsRemoteService}.
   *
   * <p>See {@code com.android.server.autofill.RemoteFillService} for a concrete
   * (no pun intended) example of how to use it.
   *
   * @param <S> the concrete remote service class
   * @param <I> the interface of the binder service
   *
   * @hide
   */
  //TODO(b/117779333): improve javadoc above instead of using Autofill as an example
  public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I>,
          I extends IInterface> implements DeathRecipient {
      private static final int MSG_BIND = 1;
      private static final int MSG_UNBIND = 2;
  
      public static final long PERMANENT_BOUND_TIMEOUT_MS = 0;
  
      protected static final int LAST_PRIVATE_MSG = MSG_UNBIND;
  
      // TODO(b/117779333): convert all booleans into an integer / flags
      public final boolean mVerbose;
  
      protected final String mTag = getClass().getSimpleName();
      protected final Handler mHandler;
      protected final ComponentName mComponentName;
  
      private final Context mContext;
      private final Intent mIntent;
      private final VultureCallback<S> mVultureCallback;
      private final int mUserId;
      private final ServiceConnection mServiceConnection = new RemoteServiceConnection();
      private final int mBindingFlags;
      protected I mService;
  
      private boolean mBinding;
      private boolean mDestroyed;
      private boolean mServiceDied;
      private boolean mCompleted;
  
      // Used just for debugging purposes (on dump)
      private long mNextUnbind;
  
      /** Requests that have been scheduled, but that are not finished yet */
      private final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>();
  
      /**
       * Callback called when the service dies.
       *
       * @param <T> service class
       */
      public interface VultureCallback<T> {
          /**
           * Called when the service dies.
           *
           * @param service service that died!
           */
          void onServiceDied(T service);
      }
  
      // NOTE: must be package-protected so this class is not extended outside
      AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface,
              @NonNull ComponentName componentName, int userId, @NonNull VultureCallback<S> callback,
              @NonNull Handler handler, int bindingFlags, boolean verbose) {
          mContext = context;
          mVultureCallback = callback;
          mVerbose = verbose;
          mComponentName = componentName;
          mIntent = new Intent(serviceInterface).setComponent(mComponentName);
          mUserId = userId;
          mHandler = new Handler(handler.getLooper());
          mBindingFlags = bindingFlags;
      }
  
      /**
       * Destroys this service.
       */
      public final void destroy() {
          mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleDestroy, this));
      }
  
      /**
       * Checks whether this service is destroyed.
       */
      public final boolean isDestroyed() {
          return mDestroyed;
      }
  
      /**
       * Gets the name of the service.
       */
      @NonNull
      public final ComponentName getComponentName() {
          return mComponentName;
      }
  
      private void handleOnConnectedStateChangedInternal(boolean connected) {
          handleOnConnectedStateChanged(connected);
          if (connected) {
              handlePendingRequests();
          }
      }
  
      /**
       * Handles the pending requests when the connection it bounds to the remote service.
       */
      abstract void handlePendingRequests();
  
      /**
       * Callback called when the system connected / disconnected to the service and the pending
       * requests have been handled.
       *
       * @param state {@code true} when connected, {@code false} when disconnected.
       */
      protected void handleOnConnectedStateChanged(boolean state) {
      }
  
      /**
       * Gets the base Binder interface from the service.
       */
      @NonNull
      protected abstract I getServiceInterface(@NonNull IBinder service);
  
      /**
       * Defines how long after the last interaction with the service we would unbind.
       *
       * @return time to unbind (in millis), or {@link #PERMANENT_BOUND_TIMEOUT_MS} to not unbind.
       */
      protected abstract long getTimeoutIdleBindMillis();
  
      /**
       * Defines how long after we make a remote request to a fill service we timeout.
       *
       * <p>Just need to be overridden by subclasses that uses sync {@link PendingRequest}s.
       *
       * @throws UnsupportedOperationException if called when not overridden.
       *
       */
      protected long getRemoteRequestMillis() {
          throw new UnsupportedOperationException("not implemented by " + getClass());
      }
  
      /**
       * Gets the currently registered service interface or {@code null} if the service is not
       * connected.
       */
      @Nullable
      public final I getServiceInterface() {
          return mService;
      }
  
      private void handleDestroy() {
          if (checkIfDestroyed()) return;
          handleOnDestroy();
          handleEnsureUnbound();
          mDestroyed = true;
      }
  
      /**
       * Clears the state when this object is destroyed.
       *
       * <p>Typically used to cancel the pending requests.
       */
      protected abstract void handleOnDestroy();
  
      @Override // from DeathRecipient
      public void binderDied() {
          mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleBinderDied, this));
      }
  
      private void handleBinderDied() {
          if (checkIfDestroyed()) return;
          if (mService != null) {
              mService.asBinder().unlinkToDeath(this, 0);
          }
          mService = null;
          mServiceDied = true;
          cancelScheduledUnbind();
          @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning
          final S castService = (S) this;
          mVultureCallback.onServiceDied(castService);
          handleBindFailure();
      }
  
      // Note: we are dumping without a lock held so this is a bit racy but
      // adding a lock to a class that offloads to a handler thread would
      // mean adding a lock adding overhead to normal runtime operation.
      /**
       * Dump it!
       */
      public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
          String tab = "  ";
          pw.append(prefix).append("service:").println();
          pw.append(prefix).append(tab).append("userId=")
                  .append(String.valueOf(mUserId)).println();
          pw.append(prefix).append(tab).append("componentName=")
                  .append(mComponentName.flattenToString()).println();
          pw.append(prefix).append(tab).append("destroyed=")
                  .append(String.valueOf(mDestroyed)).println();
          pw.append(prefix).append(tab).append("numUnfinishedRequests=")
                  .append(String.valueOf(mUnfinishedRequests.size())).println();
          final boolean bound = handleIsBound();
          pw.append(prefix).append(tab).append("bound=")
                  .append(String.valueOf(bound));
          final long idleTimeout = getTimeoutIdleBindMillis();
          if (bound) {
              if (idleTimeout > 0) {
                  pw.append(" (unbind in : ");
                  TimeUtils.formatDuration(mNextUnbind - SystemClock.elapsedRealtime(), pw);
                  pw.append(")");
              } else {
                  pw.append(" (permanently bound)");
              }
          }
          pw.println();
          pw.append(prefix).append("mBindingFlags=").println(mBindingFlags);
          pw.append(prefix).append("idleTimeout=")
              .append(Long.toString(idleTimeout / 1000)).append("s\n");
          pw.append(prefix).append("requestTimeout=");
          try {
              pw.append(Long.toString(getRemoteRequestMillis() / 1000)).append("s\n");
          } catch (UnsupportedOperationException e) {
              pw.append("not supported\n");
          }
          pw.println();
      }
  
      /**
       * Schedules a "sync" request.
       *
       * <p>This request must be responded by the service somehow (typically using a callback),
       * othewise it will trigger a {@link PendingRequest#onTimeout(AbstractRemoteService)} if the
       * service doesn't respond.
       */
      protected void scheduleRequest(@NonNull BasePendingRequest<S, I> pendingRequest) {
          mHandler.sendMessage(obtainMessage(
                  AbstractRemoteService::handlePendingRequest, this, pendingRequest));
      }
  
      /**
       * Marks a pendingRequest as finished.
       *
       * @param finshedRequest The request that is finished
       */
      void finishRequest(@NonNull BasePendingRequest<S, I> finshedRequest) {
          mHandler.sendMessage(
                  obtainMessage(AbstractRemoteService::handleFinishRequest, this, finshedRequest));
      }
  
      private void handleFinishRequest(@NonNull BasePendingRequest<S, I> finshedRequest) {
          mUnfinishedRequests.remove(finshedRequest);
  
          if (mUnfinishedRequests.isEmpty()) {
              scheduleUnbind();
          }
      }
  
      /**
       * Schedules an async request.
       *
       * <p>This request is not expecting a callback from the service, hence it's represented by
       * a simple {@link Runnable}.
       */
      protected void scheduleAsyncRequest(@NonNull AsyncRequest<I> request) {
          // TODO(b/117779333): fix generics below
          @SuppressWarnings({"unchecked", "rawtypes"})
          final MyAsyncPendingRequest<S, I> asyncRequest = new MyAsyncPendingRequest(this, request);
          mHandler.sendMessage(
                  obtainMessage(AbstractRemoteService::handlePendingRequest, this, asyncRequest));
      }
  
      private void cancelScheduledUnbind() {
          mHandler.removeMessages(MSG_UNBIND);
      }
  
      /**
       * Schedules a request to bind to the remote service.
       *
       * <p>Typically used on constructor for implementations that need a permanent connection to
       * the remote service.
       */
      protected void scheduleBind() {
          if (mHandler.hasMessages(MSG_BIND)) {
              if (mVerbose) Slog.v(mTag, "scheduleBind(): already scheduled");
              return;
          }
          mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleEnsureBound, this)
                  .setWhat(MSG_BIND));
      }
  
      /**
       * Schedules a request to automatically unbind from the service after the
       * {@link #getTimeoutIdleBindMillis() idle timeout} expires.
       */
      protected void scheduleUnbind() {
          scheduleUnbind(true);
      }
  
      private void scheduleUnbind(boolean delay) {
          long unbindDelay = getTimeoutIdleBindMillis();
  
          if (unbindDelay <= PERMANENT_BOUND_TIMEOUT_MS) {
              if (mVerbose) Slog.v(mTag, "not scheduling unbind when value is " + unbindDelay);
              return;
          }
  
          if (!delay) {
              unbindDelay = 0;
          }
  
          cancelScheduledUnbind();
          // TODO(b/117779333): make sure it's unbound if the service settings changing (right now
          // it's not)
  
          mNextUnbind = SystemClock.elapsedRealtime() + unbindDelay;
          if (mVerbose) Slog.v(mTag, "unbinding in " + unbindDelay + "ms: " + mNextUnbind);
          mHandler.sendMessageDelayed(obtainMessage(AbstractRemoteService::handleUnbind, this)
                  .setWhat(MSG_UNBIND), unbindDelay);
      }
  
      private void handleUnbind() {
          if (checkIfDestroyed()) return;
  
          handleEnsureUnbound();
      }
  
      /**
       * Handles a request, either processing it right now when bound, or saving it to be handled when
       * bound.
       */
      protected final void handlePendingRequest(@NonNull BasePendingRequest<S, I> pendingRequest) {
          if (checkIfDestroyed() || mCompleted) return;
  
          if (!handleIsBound()) {
              if (mVerbose) Slog.v(mTag, "handlePendingRequest(): queuing " + pendingRequest);
              handlePendingRequestWhileUnBound(pendingRequest);
              handleEnsureBound();
          } else {
              if (mVerbose) Slog.v(mTag, "handlePendingRequest(): " + pendingRequest);
  
              mUnfinishedRequests.add(pendingRequest);
              cancelScheduledUnbind();
  
              pendingRequest.run();
              if (pendingRequest.isFinal()) {
                  mCompleted = true;
              }
          }
      }
  
      /**
       * Defines what to do with a request that arrives while not bound to the service.
       */
      abstract void handlePendingRequestWhileUnBound(
              @NonNull BasePendingRequest<S, I> pendingRequest);
  
      /**
       * Called if {@link Context#bindServiceAsUser} returns {@code false}, or
       * if {@link DeathRecipient#binderDied()} is called.
       */
      abstract void handleBindFailure();
  
      private boolean handleIsBound() {
          return mService != null;
      }
  
      private void handleEnsureBound() {
          if (handleIsBound() || mBinding) return;
  
          if (mVerbose) Slog.v(mTag, "ensureBound()");
          mBinding = true;
  
          final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                  | mBindingFlags;
  
          final boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags,
                  mHandler, new UserHandle(mUserId));
  
          if (!willBind) {
              Slog.w(mTag, "could not bind to " + mIntent + " using flags " + flags);
              mBinding = false;
  
              if (!mServiceDied) {
                  handleBinderDied();
              }
          }
      }
  
      private void handleEnsureUnbound() {
          if (!handleIsBound() && !mBinding) return;
  
          if (mVerbose) Slog.v(mTag, "ensureUnbound()");
          mBinding = false;
          if (handleIsBound()) {
              handleOnConnectedStateChangedInternal(false);
              if (mService != null) {
                  mService.asBinder().unlinkToDeath(this, 0);
                  mService = null;
              }
          }
          mNextUnbind = 0;
          mContext.unbindService(mServiceConnection);
      }
  
      private class RemoteServiceConnection implements ServiceConnection {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              if (mVerbose) Slog.v(mTag, "onServiceConnected()");
              if (mDestroyed || !mBinding) {
                  // This is abnormal. Unbinding the connection has been requested already.
                  Slog.wtf(mTag, "onServiceConnected() was dispatched after unbindService.");
                  return;
              }
              mBinding = false;
              try {
                  service.linkToDeath(AbstractRemoteService.this, 0);
              } catch (RemoteException re) {
                  handleBinderDied();
                  return;
              }
              mService = getServiceInterface(service);
              handleOnConnectedStateChangedInternal(true);
              mServiceDied = false;
          }
  
          @Override
          public void onServiceDisconnected(ComponentName name) {
              if (mVerbose) Slog.v(mTag, "onServiceDisconnected()");
              mBinding = true;
              mService = null;
          }
  
          @Override
          public void onBindingDied(ComponentName name) {
              if (mVerbose) Slog.v(mTag, "onBindingDied()");
              scheduleUnbind(false);
          }
      }
  
      private boolean checkIfDestroyed() {
          if (mDestroyed) {
              if (mVerbose) {
                  Slog.v(mTag, "Not handling operation as service for " + mComponentName
                          + " is already destroyed");
              }
          }
          return mDestroyed;
      }
  
      @Override
      public String toString() {
          return getClass().getSimpleName() + "[" + mComponentName
                  + " " + System.identityHashCode(this)
                  + (mService != null ? " (bound)" : " (unbound)")
                  + (mDestroyed ? " (destroyed)" : "")
                  + "]";
      }
  
      /**
       * Base class for the requests serviced by the remote service.
       *
       * <p><b>NOTE: </b> this class is not used directly, you should either override
       * {@link com.android.internal.infra.AbstractRemoteService.PendingRequest} for sync requests, or
       * use {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} for async requests.
       *
       * @param <S> the remote service class
       * @param <I> the interface of the binder service
       */
      public abstract static class BasePendingRequest<S extends AbstractRemoteService<S, I>,
              I extends IInterface> implements Runnable {
          protected final String mTag = getClass().getSimpleName();
          protected final Object mLock = new Object();
  
          final WeakReference<S> mWeakService;
  
          @GuardedBy("mLock")
          boolean mCancelled;
  
          @GuardedBy("mLock")
          boolean mCompleted;
  
          BasePendingRequest(@NonNull S service) {
              mWeakService = new WeakReference<>(service);
          }
  
          /**
           * Gets a reference to the remote service.
           */
          protected final S getService() {
              return mWeakService.get();
          }
  
          /**
           * Subclasses must call this method when the remote service finishes, i.e., when the service
           * finishes processing a request.
           *
           * @return {@code false} in the service is already finished, {@code true} otherwise.
           */
          protected final boolean finish() {
              synchronized (mLock) {
                  if (mCompleted || mCancelled) {
                      return false;
                  }
                  mCompleted = true;
              }
  
              S service = mWeakService.get();
              if (service != null) {
                  service.finishRequest(this);
              }
  
              onFinished();
  
              return true;
          }
  
          void onFinished() { }
  
          /**
           * Called when request fails due to reasons internal to {@link AbstractRemoteService},
           * e.g. failure to bind to service.
           */
          protected void onFailed() { }
  
          /**
           * Checks whether this request was cancelled.
           */
          @GuardedBy("mLock")
          protected final boolean isCancelledLocked() {
              return mCancelled;
          }
  
          /**
           * Cancels the service.
           *
           * @return {@code false} if service is already canceled, {@code true} otherwise.
           */
          public boolean cancel() {
              synchronized (mLock) {
                  if (mCancelled || mCompleted) {
                      return false;
                  }
                  mCancelled = true;
              }
  
              onCancel();
              return true;
          }
  
          void onCancel() {}
  
          /**
           * Checks whether this request leads to a final state where no other requests can be made.
           */
          protected boolean isFinal() {
              return false;
          }
  
          protected boolean isRequestCompleted() {
              synchronized (mLock) {
                  return mCompleted;
              }
          }
      }
  
      /**
       * Base class for the requests serviced by the remote service.
       *
       * <p><b>NOTE: </b> this class is typically used when the service needs to use a callback to
       * communicate back with the system server. For cases where that's not needed, you should use
       * {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} instead.
       *
       * <p><b>NOTE: </b> you must override {@link AbstractRemoteService#getRemoteRequestMillis()},
       * otherwise the constructor will throw an {@link UnsupportedOperationException}.
       *
       * @param <S> the remote service class
       * @param <I> the interface of the binder service
       */
      public abstract static class PendingRequest<S extends AbstractRemoteService<S, I>,
              I extends IInterface> extends BasePendingRequest<S, I> {
  
          private final Runnable mTimeoutTrigger;
          private final Handler mServiceHandler;
  
          protected PendingRequest(S service) {
              super(service);
              mServiceHandler = service.mHandler;
  
              mTimeoutTrigger = () -> {
                  synchronized (mLock) {
                      if (mCancelled) {
                          return;
                      }
                      mCompleted = true;
                  }
  
                  final S remoteService = mWeakService.get();
                  if (remoteService != null) {
                      // TODO(b/117779333): we should probably ignore it if service is destroyed.
                      Slog.w(mTag, "timed out after " + service.getRemoteRequestMillis() + " ms");
                      remoteService.finishRequest(this);
                      onTimeout(remoteService);
                  } else {
                      Slog.w(mTag, "timed out (no service)");
                  }
              };
              mServiceHandler.postAtTime(mTimeoutTrigger,
                      SystemClock.uptimeMillis() + service.getRemoteRequestMillis());
          }
  
          @Override
          final void onFinished() {
              mServiceHandler.removeCallbacks(mTimeoutTrigger);
          }
  
          @Override
          final void onCancel() {
              mServiceHandler.removeCallbacks(mTimeoutTrigger);
          }
  
          /**
           * Called by the self-destruct timeout when the remote service didn't reply to the
           * request on time.
           */
          protected abstract void onTimeout(S remoteService);
      }
  
      /**
       * Represents a request that does not expect a callback from the remote service.
       *
       * @param <I> the interface of the binder service
       */
      public interface AsyncRequest<I extends IInterface> {
  
          /**
           * Run Forrest, run!
           */
          void run(@NonNull I binder) throws RemoteException;
      }
  
      private static final class MyAsyncPendingRequest<S extends AbstractRemoteService<S, I>,
              I extends IInterface> extends BasePendingRequest<S, I> {
          private static final String TAG = MyAsyncPendingRequest.class.getSimpleName();
  
          private final AsyncRequest<I> mRequest;
  
          protected MyAsyncPendingRequest(@NonNull S service, @NonNull AsyncRequest<I> request) {
              super(service);
  
              mRequest = request;
          }
  
          @Override
          public void run() {
              final S remoteService = getService();
              if (remoteService == null) return;
              try {
                  mRequest.run(remoteService.mService);
              } catch (RemoteException e) {
                  Slog.w(TAG, "exception handling async request (" + this + "): " + e);
              } finally {
                  finish();
              }
          }
      }
  }
  