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.appbinding.finders;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.content.pm.IPackageManager;
  import android.content.pm.ServiceInfo;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.IInterface;
  import android.util.Log;
  import android.util.Slog;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.server.appbinding.AppBindingConstants;
  import com.android.server.appbinding.AppBindingService;
  import com.android.server.appbinding.AppBindingUtils;
  
  import java.io.PrintWriter;
  import java.util.function.BiConsumer;
  
  /**
   * Baseclss that finds "persistent" service from a type of an app.
   *
   * @param <TServiceType> Type of the target service class.
   * @param <TServiceInterfaceType> Type of the IInterface class used by TServiceType.
   */
  public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType extends IInterface> {
      protected static final String TAG = AppBindingService.TAG;
      protected static final boolean DEBUG = AppBindingService.DEBUG;
  
      protected final Context mContext;
      protected final BiConsumer<AppServiceFinder, Integer> mListener;
      protected final Handler mHandler;
  
      private final Object mLock = new Object();
  
      @GuardedBy("mLock")
      private final SparseArray<String> mTargetPackages = new SparseArray(4);
  
      @GuardedBy("mLock")
      private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(4);
  
      @GuardedBy("mLock")
      private final SparseArray<String> mLastMessages = new SparseArray(4);
  
      public AppServiceFinder(Context context,
              BiConsumer<AppServiceFinder, Integer> listener,
              Handler callbackHandler) {
          mContext = context;
          mListener = listener;
          mHandler = callbackHandler;
      }
  
      /** Whether this service should really be enabled. */
      protected boolean isEnabled(AppBindingConstants constants) {
          return true;
      }
  
      /** Human readable description of the type of apps; e.g. [Default SMS app] */
      @NonNull
      public abstract String getAppDescription();
  
      /** Start monitoring apps. (e.g. Start watching the default SMS app changes.) */
      public void startMonitoring() {
      }
  
      /** Called when a user is removed. */
      public void onUserRemoved(int userId) {
          synchronized (mLock) {
              mTargetPackages.delete(userId);
              mTargetServices.delete(userId);
              mLastMessages.delete(userId);
          }
      }
  
      /**
       * Find the target service from the target app on a given user.
       */
      @Nullable
      public final ServiceInfo findService(int userId, IPackageManager ipm,
              AppBindingConstants constants) {
          synchronized (mLock) {
              mTargetPackages.put(userId, null);
              mTargetServices.put(userId, null);
              mLastMessages.put(userId, null);
  
              if (!isEnabled(constants)) {
                  final String message = "feature disabled";
                  mLastMessages.put(userId, message);
                  Slog.i(TAG, getAppDescription() + " " + message);
                  return null;
              }
  
              final String targetPackage = getTargetPackage(userId);
              if (DEBUG) {
                  Slog.d(TAG, getAppDescription() + " package=" + targetPackage);
              }
              if (targetPackage == null) {
                  final String message = "Target package not found";
                  mLastMessages.put(userId, message);
                  Slog.w(TAG, getAppDescription() + " u" + userId + " " + message);
                  return null;
              }
              mTargetPackages.put(userId, targetPackage);
  
              final StringBuilder errorMessage = new StringBuilder();
              final ServiceInfo service = AppBindingUtils.findService(
                      targetPackage,
                      userId,
                      getServiceAction(),
                      getServicePermission(),
                      getServiceClass(),
                      ipm,
                      errorMessage);
  
              if (service == null) {
                  final String message = errorMessage.toString();
                  mLastMessages.put(userId, message);
                  if (DEBUG) {
                      // This log is optional because findService() already did Log.e().
                      Slog.w(TAG, getAppDescription() + " package " + targetPackage + " u" + userId
                              + " " + message);
                  }
                  return null;
              }
              final String error = validateService(service);
              if (error != null) {
                  mLastMessages.put(userId, error);
                  Log.e(TAG, error);
                  return null;
              }
  
              final String message = "Valid service found";
              mLastMessages.put(userId, message);
              mTargetServices.put(userId, service);
              return service;
          }
      }
  
      protected abstract Class<TServiceType> getServiceClass();
  
      /**
       * Convert a binder reference to a service interface type.
       */
      public abstract TServiceInterfaceType asInterface(IBinder obj);
  
      /**
       * @return the target package on a given user.
       */
      @Nullable
      public abstract String getTargetPackage(int userId);
  
      /**
       * @return the intent action that identifies the target service in the target app.
       */
      @NonNull
      protected abstract String getServiceAction();
  
      /**
       * @return the permission that the target service must be protected with.
       */
      @NonNull
      protected abstract String getServicePermission();
  
      /**
       * Subclass can implement it to decide whether to accept a service (by returning null) or not
       * (by returning an error message.)
       */
      protected String validateService(ServiceInfo service) {
          return null;
      }
  
      /** Return the bind flags for this service. */
      public abstract int getBindFlags(AppBindingConstants constants);
  
      /** Dumpsys support. */
      public void dump(String prefix, PrintWriter pw) {
          pw.print(prefix);
          pw.print("App type: ");
          pw.print(getAppDescription());
          pw.println();
  
          synchronized (mLock) {
              for (int i = 0; i < mTargetPackages.size(); i++) {
                  final int userId = mTargetPackages.keyAt(i);
                  pw.print(prefix);
                  pw.print("  User: ");
                  pw.print(userId);
                  pw.println();
  
                  pw.print(prefix);
                  pw.print("    Package: ");
                  pw.print(mTargetPackages.get(userId));
                  pw.println();
  
                  pw.print(prefix);
                  pw.print("    Service: ");
                  pw.print(mTargetServices.get(userId));
                  pw.println();
  
                  pw.print(prefix);
                  pw.print("    Message: ");
                  pw.print(mLastMessages.get(userId));
                  pw.println();
              }
          }
      }
  
      /** Dumpys support */
      public void dumpSimple(PrintWriter pw) {
          synchronized (mLock) {
              for (int i = 0; i < mTargetPackages.size(); i++) {
                  final int userId = mTargetPackages.keyAt(i);
                  pw.print("finder,");
                  pw.print(getAppDescription());
                  pw.print(",");
                  pw.print(userId);
                  pw.print(",");
                  pw.print(mTargetPackages.get(userId));
                  pw.print(",");
                  pw.print(mTargetServices.get(userId));
                  pw.print(",");
                  pw.print(mLastMessages.get(userId));
                  pw.println();
              }
          }
      }
  }
  