content: /*
   * Copyright (C) 2019 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 android.app.role;
  
  import android.Manifest;
  import android.annotation.CallbackExecutor;
  import android.annotation.NonNull;
  import android.annotation.RequiresPermission;
  import android.annotation.SystemService;
  import android.annotation.UserIdInt;
  import android.app.ActivityThread;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.PackageManager;
  import android.content.pm.ResolveInfo;
  import android.os.Binder;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.RemoteCallback;
  import android.os.RemoteException;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
  import com.android.internal.infra.AbstractRemoteService;
  
  import java.util.concurrent.Executor;
  import java.util.function.Consumer;
  
  /**
   * Interface for communicating with the role controller.
   *
   * @hide
   */
  @SystemService(Context.ROLE_CONTROLLER_SERVICE)
  public class RoleControllerManager {
  
      private static final String LOG_TAG = RoleControllerManager.class.getSimpleName();
  
      private static volatile ComponentName sRemoteServiceComponentName;
  
      private static final Object sRemoteServicesLock = new Object();
  
      /**
       * Global remote services (per user) used by all {@link RoleControllerManager managers}.
       */
      @GuardedBy("sRemoteServicesLock")
      private static final SparseArray<RemoteService> sRemoteServices = new SparseArray<>();
  
      @NonNull
      private final RemoteService mRemoteService;
  
      /**
       * Initialize the remote service component name once so that we can avoid acquiring the
       * PackageManagerService lock in constructor.
       *
       * @see #createWithInitializedRemoteServiceComponentName(Handler, Context)
       */
      public static void initializeRemoteServiceComponentName(@NonNull Context context) {
          sRemoteServiceComponentName = getRemoteServiceComponentName(context);
      }
  
      /**
       * Create a {@link RoleControllerManager} instance with the initialized remote service component
       * name so that we can avoid acquiring the PackageManagerService lock in constructor.
       *
       * @see #initializeRemoteServiceComponentName(Context)
       */
      @NonNull
      public static RoleControllerManager createWithInitializedRemoteServiceComponentName(
              @NonNull Handler handler, @NonNull Context context) {
          return new RoleControllerManager(sRemoteServiceComponentName, handler, context);
      }
  
      private RoleControllerManager(@NonNull ComponentName remoteServiceComponentName,
              @NonNull Handler handler, @NonNull Context context) {
          synchronized (sRemoteServicesLock) {
              int userId = context.getUserId();
              RemoteService remoteService = sRemoteServices.get(userId);
              if (remoteService == null) {
                  remoteService = new RemoteService(ActivityThread.currentApplication(),
                          remoteServiceComponentName, handler, userId);
                  sRemoteServices.put(userId, remoteService);
              }
              mRemoteService = remoteService;
          }
      }
  
      public RoleControllerManager(@NonNull Context context) {
          this(getRemoteServiceComponentName(context), context.getMainThreadHandler(), context);
      }
  
      @NonNull
      private static ComponentName getRemoteServiceComponentName(@NonNull Context context) {
          Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE);
          PackageManager packageManager = context.getPackageManager();
          intent.setPackage(packageManager.getPermissionControllerPackageName());
          ResolveInfo resolveInfo = packageManager.resolveService(intent, 0);
          return resolveInfo.getComponentInfo().getComponentName();
      }
  
      /**
       * @see RoleControllerService#onGrantDefaultRoles()
       */
      public void grantDefaultRoles(@NonNull @CallbackExecutor Executor executor,
              @NonNull Consumer<Boolean> callback) {
          mRemoteService.scheduleRequest(new GrantDefaultRolesRequest(mRemoteService, executor,
                  callback));
      }
  
      /**
       * @see RoleControllerService#onAddRoleHolder(String, String, int)
       */
      public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
              @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
          mRemoteService.scheduleRequest(new OnAddRoleHolderRequest(mRemoteService, roleName,
                  packageName, flags, callback));
      }
  
      /**
       * @see RoleControllerService#onRemoveRoleHolder(String, String, int)
       */
      public void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
              @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
          mRemoteService.scheduleRequest(new OnRemoveRoleHolderRequest(mRemoteService, roleName,
                  packageName, flags, callback));
      }
  
      /**
       * @see RoleControllerService#onClearRoleHolders(String, int)
       */
      public void onClearRoleHolders(@NonNull String roleName,
              @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
          mRemoteService.scheduleRequest(new OnClearRoleHoldersRequest(mRemoteService, roleName,
                  flags, callback));
      }
  
      /**
       * @see RoleControllerService#onIsApplicationQualifiedForRole(String, String)
       */
      @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
      public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName,
              @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
          mRemoteService.scheduleRequest(new IsApplicationQualifiedForRoleRequest(mRemoteService,
                  roleName, packageName, executor, callback));
      }
  
      /**
       * @see RoleControllerService#onIsRoleVisible(String)
       */
      @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
      public void isRoleVisible(@NonNull String roleName,
              @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
          mRemoteService.scheduleRequest(new IsRoleVisibleRequest(mRemoteService, roleName, executor,
                  callback));
      }
  
      /**
       * Connection to the remote service.
       */
      private static final class RemoteService extends AbstractMultiplePendingRequestsRemoteService<
              RemoteService, IRoleController> {
  
          private static final long UNBIND_DELAY_MILLIS = 15 * 1000;
          private static final long REQUEST_TIMEOUT_MILLIS = 15 * 1000;
  
          /**
           * Create a connection to the remote service
           *
           * @param context the context to use
           * @param componentName the component of the service to connect to
           * @param handler the handler for binding service and callbacks
           * @param userId the user whom remote service should be connected as
           */
          RemoteService(@NonNull Context context, @NonNull ComponentName componentName,
                  @NonNull Handler handler, @UserIdInt int userId) {
              super(context, RoleControllerService.SERVICE_INTERFACE, componentName, userId,
                      service -> Log.e(LOG_TAG, "RemoteService " + service + " died"), handler, 0,
                      false, 1);
          }
  
          /**
           * @return The default handler used by this service.
           */
          @NonNull
          public Handler getHandler() {
              return mHandler;
          }
  
          @Override
          protected @NonNull IRoleController getServiceInterface(@NonNull IBinder binder) {
              return IRoleController.Stub.asInterface(binder);
          }
  
          @Override
          protected long getTimeoutIdleBindMillis() {
              return UNBIND_DELAY_MILLIS;
          }
  
          @Override
          protected long getRemoteRequestMillis() {
              return REQUEST_TIMEOUT_MILLIS;
          }
  
          @Override
          public void scheduleRequest(
                  @NonNull BasePendingRequest<RemoteService, IRoleController> pendingRequest) {
              super.scheduleRequest(pendingRequest);
          }
  
          @Override
          public void scheduleAsyncRequest(@NonNull AsyncRequest<IRoleController> request) {
              super.scheduleAsyncRequest(request);
          }
      }
  
      /**
       * Request for {@link #grantDefaultRoles(Executor, Consumer)}.
       */
      private static final class GrantDefaultRolesRequest
              extends AbstractRemoteService.PendingRequest<RemoteService, IRoleController> {
  
          @NonNull
          private final Executor mExecutor;
          @NonNull
          private final Consumer<Boolean> mCallback;
  
          @NonNull
          private final RemoteCallback mRemoteCallback;
  
          private GrantDefaultRolesRequest(@NonNull RemoteService service,
                  @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
              super(service);
  
              mExecutor = executor;
              mCallback = callback;
  
              mRemoteCallback = new RemoteCallback(result -> mExecutor.execute(() -> {
                  long token = Binder.clearCallingIdentity();
                  try {
                      boolean successful = result != null;
                      mCallback.accept(successful);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                      finish();
                  }
              }));
          }
  
          @Override
          protected void onTimeout(@NonNull RemoteService remoteService) {
              mExecutor.execute(() -> mCallback.accept(false));
          }
  
          @Override
          public void run() {
              try {
                  getService().getServiceInterface().grantDefaultRoles(mRemoteCallback);
              } catch (RemoteException e) {
                  Log.e(LOG_TAG, "Error calling grantDefaultRoles()", e);
              }
          }
  
          @Override
          protected void onFailed() {
              mRemoteCallback.sendResult(null);
          }
      }
  
      /**
       * Request for {@link #onAddRoleHolder(String, String, int, RemoteCallback)}.
       */
      private static final class OnAddRoleHolderRequest
              extends AbstractRemoteService.PendingRequest<RemoteService, IRoleController> {
  
          @NonNull
          private final String mRoleName;
          @NonNull
          private final String mPackageName;
          @RoleManager.ManageHoldersFlags
          private final int mFlags;
          @NonNull
          private final RemoteCallback mCallback;
  
          @NonNull
          private final RemoteCallback mRemoteCallback;
  
          private OnAddRoleHolderRequest(@NonNull RemoteService service, @NonNull String roleName,
                  @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags,
                  @NonNull RemoteCallback callback) {
              super(service);
  
              mRoleName = roleName;
              mPackageName = packageName;
              mFlags = flags;
              mCallback = callback;
  
              mRemoteCallback = new RemoteCallback(result -> {
                  long token = Binder.clearCallingIdentity();
                  try {
                      mCallback.sendResult(result);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                      finish();
                  }
              });
          }
  
          @Override
          protected void onTimeout(@NonNull RemoteService remoteService) {
              mCallback.sendResult(null);
          }
  
          @Override
          public void run() {
              try {
                  getService().getServiceInterface().onAddRoleHolder(mRoleName, mPackageName, mFlags,
                          mRemoteCallback);
              } catch (RemoteException e) {
                  Log.e(LOG_TAG, "Error calling onAddRoleHolder()", e);
              }
          }
      }
  
      /**
       * Request for {@link #onRemoveRoleHolder(String, String, int, RemoteCallback)}.
       */
      private static final class OnRemoveRoleHolderRequest
              extends AbstractRemoteService.PendingRequest<RemoteService, IRoleController> {
  
          @NonNull
          private final String mRoleName;
          @NonNull
          private final String mPackageName;
          @RoleManager.ManageHoldersFlags
          private final int mFlags;
          @NonNull
          private final RemoteCallback mCallback;
  
          @NonNull
          private final RemoteCallback mRemoteCallback;
  
          private OnRemoveRoleHolderRequest(@NonNull RemoteService service, @NonNull String roleName,
                  @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags,
                  @NonNull RemoteCallback callback) {
              super(service);
  
              mRoleName = roleName;
              mPackageName = packageName;
              mFlags = flags;
              mCallback = callback;
  
              mRemoteCallback = new RemoteCallback(result -> {
                  long token = Binder.clearCallingIdentity();
                  try {
                      mCallback.sendResult(result);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                      finish();
                  }
              });
          }
  
          @Override
          protected void onTimeout(@NonNull RemoteService remoteService) {
              mCallback.sendResult(null);
          }
  
          @Override
          public void run() {
              try {
                  getService().getServiceInterface().onRemoveRoleHolder(mRoleName, mPackageName,
                          mFlags, mRemoteCallback);
              } catch (RemoteException e) {
                  Log.e(LOG_TAG, "Error calling onRemoveRoleHolder()", e);
              }
          }
      }
  
      /**
       * Request for {@link #onClearRoleHolders(String, int, RemoteCallback)}.
       */
      private static final class OnClearRoleHoldersRequest
              extends AbstractRemoteService.PendingRequest<RemoteService, IRoleController> {
  
          @NonNull
          private final String mRoleName;
          @RoleManager.ManageHoldersFlags
          private final int mFlags;
          @NonNull
          private final RemoteCallback mCallback;
  
          @NonNull
          private final RemoteCallback mRemoteCallback;
  
          private OnClearRoleHoldersRequest(@NonNull RemoteService service, @NonNull String roleName,
                  @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
              super(service);
  
              mRoleName = roleName;
              mFlags = flags;
              mCallback = callback;
  
              mRemoteCallback = new RemoteCallback(result -> {
                  long token = Binder.clearCallingIdentity();
                  try {
                      mCallback.sendResult(result);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                      finish();
                  }
              });
          }
  
          @Override
          protected void onTimeout(@NonNull RemoteService remoteService) {
              mCallback.sendResult(null);
          }
  
          @Override
          public void run() {
              try {
                  getService().getServiceInterface().onClearRoleHolders(mRoleName, mFlags,
                          mRemoteCallback);
              } catch (RemoteException e) {
                  Log.e(LOG_TAG, "Error calling onClearRoleHolders()", e);
              }
          }
      }
  
      /**
       * Request for {@link #isApplicationQualifiedForRole(String, String, Executor, Consumer)}
       */
      private static final class IsApplicationQualifiedForRoleRequest extends
              AbstractRemoteService.PendingRequest<RemoteService, IRoleController> {
  
          @NonNull
          private final String mRoleName;
          @NonNull
          private final String mPackageName;
          @NonNull
          private final Executor mExecutor;
          @NonNull
          private final Consumer<Boolean> mCallback;
  
          @NonNull
          private final RemoteCallback mRemoteCallback;
  
          private IsApplicationQualifiedForRoleRequest(@NonNull RemoteService service,
                  @NonNull String roleName, @NonNull String packageName,
                  @CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
              super(service);
  
              mRoleName = roleName;
              mPackageName = packageName;
              mExecutor = executor;
              mCallback = callback;
  
              mRemoteCallback = new RemoteCallback(result -> mExecutor.execute(() -> {
                  long token = Binder.clearCallingIdentity();
                  try {
                      boolean qualified = result != null;
                      mCallback.accept(qualified);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                      finish();
                  }
              }));
          }
  
          @Override
          protected void onTimeout(RemoteService remoteService) {
              mExecutor.execute(() -> mCallback.accept(false));
          }
  
          @Override
          public void run() {
              try {
                  getService().getServiceInterface().isApplicationQualifiedForRole(mRoleName,
                          mPackageName, mRemoteCallback);
              } catch (RemoteException e) {
                  Log.e(LOG_TAG, "Error calling isApplicationQualifiedForRole()", e);
              }
          }
      }
  
      /**
       * Request for {@link #isRoleVisible(String, Executor, Consumer)}
       */
      private static final class IsRoleVisibleRequest
              extends AbstractRemoteService.PendingRequest<RemoteService, IRoleController> {
  
          @NonNull
          private final String mRoleName;
          @NonNull
          private final Executor mExecutor;
          @NonNull
          private final Consumer<Boolean> mCallback;
  
          @NonNull
          private final RemoteCallback mRemoteCallback;
  
          private IsRoleVisibleRequest(@NonNull RemoteService service, @NonNull String roleName,
                  @CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
              super(service);
  
              mRoleName = roleName;
              mExecutor = executor;
              mCallback = callback;
  
              mRemoteCallback = new RemoteCallback(result -> mExecutor.execute(() -> {
                  long token = Binder.clearCallingIdentity();
                  try {
                      boolean visible = result != null;
                      mCallback.accept(visible);
                  } finally {
                      Binder.restoreCallingIdentity(token);
                      finish();
                  }
              }));
          }
  
          @Override
          protected void onTimeout(RemoteService remoteService) {
              mExecutor.execute(() -> mCallback.accept(false));
          }
  
          @Override
          public void run() {
              try {
                  getService().getServiceInterface().isRoleVisible(mRoleName, mRemoteCallback);
              } catch (RemoteException e) {
                  Log.e(LOG_TAG, "Error calling isRoleVisible()", e);
              }
          }
      }
  }
  