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.autofill;
  
  import static android.service.autofill.augmented.Helper.logResponse;
  
  import static com.android.server.autofill.Helper.sDebug;
  import static com.android.server.autofill.Helper.sVerbose;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UserIdInt;
  import android.app.AppGlobals;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.content.pm.ServiceInfo;
  import android.os.Bundle;
  import android.os.IBinder;
  import android.os.ICancellationSignal;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.service.autofill.augmented.AugmentedAutofillService;
  import android.service.autofill.augmented.IAugmentedAutofillService;
  import android.service.autofill.augmented.IFillCallback;
  import android.util.Pair;
  import android.util.Slog;
  import android.view.autofill.AutofillId;
  import android.view.autofill.AutofillManager;
  import android.view.autofill.AutofillValue;
  import android.view.autofill.IAutoFillManagerClient;
  
  import com.android.internal.infra.AbstractSinglePendingRequestRemoteService;
  import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
  import com.android.internal.os.IResultReceiver;
  
  final class RemoteAugmentedAutofillService
          extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService,
              IAugmentedAutofillService> {
  
      private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName();
  
      private final int mIdleUnbindTimeoutMs;
      private final int mRequestTimeoutMs;
  
      RemoteAugmentedAutofillService(Context context, ComponentName serviceName,
              int userId, RemoteAugmentedAutofillServiceCallbacks callbacks,
              boolean bindInstantServiceAllowed, boolean verbose, int idleUnbindTimeoutMs,
              int requestTimeoutMs) {
          super(context, AugmentedAutofillService.SERVICE_INTERFACE, serviceName, userId, callbacks,
                  context.getMainThreadHandler(),
                  bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, verbose);
          mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;
          mRequestTimeoutMs = requestTimeoutMs;
  
          // Bind right away.
          scheduleBind();
      }
  
      @Nullable
      static Pair<ServiceInfo, ComponentName> getComponentName(@NonNull String componentName,
              @UserIdInt int userId, boolean isTemporary) {
          int flags = PackageManager.GET_META_DATA;
          if (!isTemporary) {
              flags |= PackageManager.MATCH_SYSTEM_ONLY;
          }
  
          final ComponentName serviceComponent;
          ServiceInfo serviceInfo = null;
          try {
              serviceComponent = ComponentName.unflattenFromString(componentName);
              serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags,
                      userId);
              if (serviceInfo == null) {
                  Slog.e(TAG, "Bad service name for flags " + flags + ": " + componentName);
                  return null;
              }
          } catch (Exception e) {
              Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e);
              return null;
          }
          return new Pair<>(serviceInfo, serviceComponent);
      }
  
      @Override // from RemoteService
      protected void handleOnConnectedStateChanged(boolean state) {
          if (state && getTimeoutIdleBindMillis() != PERMANENT_BOUND_TIMEOUT_MS) {
              scheduleUnbind();
          }
          try {
              if (state) {
                  mService.onConnected(sDebug, sVerbose);
              } else {
                  mService.onDisconnected();
              }
          } catch (Exception e) {
              Slog.w(mTag, "Exception calling onConnectedStateChanged(" + state + "): " + e);
          }
      }
  
      @Override // from AbstractRemoteService
      protected IAugmentedAutofillService getServiceInterface(IBinder service) {
          return IAugmentedAutofillService.Stub.asInterface(service);
      }
  
      @Override // from AbstractRemoteService
      protected long getTimeoutIdleBindMillis() {
          return mIdleUnbindTimeoutMs;
      }
  
      @Override // from AbstractRemoteService
      protected long getRemoteRequestMillis() {
          return mRequestTimeoutMs;
      }
  
      /**
       * Called by {@link Session} to request augmented autofill.
       */
      public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client,
              int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
              @Nullable AutofillValue focusedValue) {
          scheduleRequest(new PendingAutofillRequest(this, sessionId, client, taskId,
                  activityComponent, focusedId, focusedValue));
      }
  
      @Override
      public String toString() {
          return "RemoteAugmentedAutofillService["
                  + ComponentName.flattenToShortString(getComponentName()) + "]";
      }
  
      /**
       * Called by {@link Session} when it's time to destroy all augmented autofill requests.
       */
      public void onDestroyAutofillWindowsRequest() {
          scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
      }
  
      private void dispatchOnFillTimeout(@NonNull ICancellationSignal cancellation) {
          mHandler.post(() -> {
              try {
                  cancellation.cancel();
              } catch (RemoteException e) {
                  Slog.w(mTag, "Error calling cancellation signal: " + e);
              }
          });
      }
  
      // TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
      private abstract static class MyPendingRequest
              extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
          protected final int mSessionId;
  
          private MyPendingRequest(@NonNull RemoteAugmentedAutofillService service, int sessionId) {
              super(service);
              mSessionId = sessionId;
          }
      }
  
      private static final class PendingAutofillRequest extends MyPendingRequest {
          private final @NonNull AutofillId mFocusedId;
          private final @Nullable AutofillValue mFocusedValue;
          private final @NonNull IAutoFillManagerClient mClient;
          private final @NonNull ComponentName mActivityComponent;
          private final int mSessionId;
          private final int mTaskId;
          private final long mRequestTime = SystemClock.elapsedRealtime();
          private final @NonNull IFillCallback mCallback;
          private ICancellationSignal mCancellation;
  
          protected PendingAutofillRequest(@NonNull RemoteAugmentedAutofillService service,
                  int sessionId, @NonNull IAutoFillManagerClient client, int taskId,
                  @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
                  @Nullable AutofillValue focusedValue) {
              super(service, sessionId);
              mClient = client;
              mSessionId = sessionId;
              mTaskId = taskId;
              mActivityComponent = activityComponent;
              mFocusedId = focusedId;
              mFocusedValue = focusedValue;
              mCallback = new IFillCallback.Stub() {
                  @Override
                  public void onSuccess() {
                      if (!finish()) return;
                      // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
                  }
  
                  @Override
                  public void onCancellable(ICancellationSignal cancellation) {
                      synchronized (mLock) {
                          final boolean cancelled;
                          synchronized (mLock) {
                              mCancellation = cancellation;
                              cancelled = isCancelledLocked();
                          }
                          if (cancelled) {
                              try {
                                  cancellation.cancel();
                              } catch (RemoteException e) {
                                  Slog.e(mTag, "Error requesting a cancellation", e);
                              }
                          }
                      }
                  }
  
                  @Override
                  public boolean isCompleted() {
                      return isRequestCompleted();
                  }
  
                  @Override
                  public void cancel() {
                      PendingAutofillRequest.this.cancel();
                  }
              };
          }
  
          @Override
          public void run() {
              synchronized (mLock) {
                  if (isCancelledLocked()) {
                      if (sDebug) Slog.d(mTag, "run() called after canceled");
                      return;
                  }
              }
              final RemoteAugmentedAutofillService remoteService = getService();
              if (remoteService == null) return;
  
              final IResultReceiver receiver = new IResultReceiver.Stub() {
  
                  @Override
                  public void send(int resultCode, Bundle resultData) throws RemoteException {
                      final IBinder realClient = resultData
                              .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT);
                      remoteService.mService.onFillRequest(mSessionId, realClient, mTaskId,
                              mActivityComponent, mFocusedId, mFocusedValue, mRequestTime, mCallback);
                  }
              };
  
              // TODO(b/122728762): set cancellation signal, timeout (from both mClient and service),
              // cache IAugmentedAutofillManagerClient reference, etc...
              try {
                  mClient.getAugmentedAutofillClient(receiver);
              } catch (RemoteException e) {
                  Slog.e(TAG, "exception handling getAugmentedAutofillClient() for "
                          + mSessionId + ": " + e);
                  finish();
              }
          }
  
          @Override
          protected void onTimeout(RemoteAugmentedAutofillService remoteService) {
              // TODO(b/122858578): must update the logged AUTOFILL_AUGMENTED_REQUEST with the
              // timeout
              Slog.w(TAG, "PendingAutofillRequest timed out (" + remoteService.mRequestTimeoutMs
                      + "ms) for " + remoteService);
              // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
              final ICancellationSignal cancellation;
              synchronized (mLock) {
                  cancellation = mCancellation;
              }
              if (cancellation != null) {
                  remoteService.dispatchOnFillTimeout(cancellation);
              }
              finish();
              logResponse(MetricsEvent.TYPE_ERROR, remoteService.getComponentName().getPackageName(),
                      mActivityComponent, mSessionId, remoteService.mRequestTimeoutMs);
          }
  
          @Override
          public boolean cancel() {
              if (!super.cancel()) return false;
  
              final ICancellationSignal cancellation;
              synchronized (mLock) {
                  cancellation = mCancellation;
              }
              if (cancellation != null) {
                  try {
                      cancellation.cancel();
                  } catch (RemoteException e) {
                      Slog.e(mTag, "Error cancelling an augmented fill request", e);
                  }
              }
              return true;
          }
      }
  
      public interface RemoteAugmentedAutofillServiceCallbacks
              extends VultureCallback<RemoteAugmentedAutofillService> {
          // NOTE: so far we don't need to notify the callback implementation (an inner class on
          // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
          // callback interface is empty.
      }
  }
  