/*
 * 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.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.WorkerThread;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.UserHandle;

import com.android.internal.util.Preconditions;

import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * Abstract base class for the role controller service.
 * <p>
 * Subclass should implement the business logic for role management, including enforcing role
 * requirements and granting or revoking relevant privileges of roles. This class can only be
 * implemented by the permission controller app which is registered in {@code PackageManager}.
 *
 * @deprecated The role controller service is an internal implementation detail inside role, and it
 *             may be replaced by other mechanisms in the future and no longer be called.
 *
 * @hide
 */
@Deprecated
@SystemApi
public abstract class RoleControllerService extends Service {

    /**
     * The {@link Intent} that must be declared as handled by the service.
     */
    public static final String SERVICE_INTERFACE = "android.app.role.RoleControllerService";

    private HandlerThread mWorkerThread;
    private Handler mWorkerHandler;

    @Override
    public void onCreate() {
        super.onCreate();

        mWorkerThread = new HandlerThread(RoleControllerService.class.getSimpleName());
        mWorkerThread.start();
        mWorkerHandler = new Handler(mWorkerThread.getLooper());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        mWorkerThread.quitSafely();
    }

    @Nullable
    @Override
    public final IBinder onBind(@Nullable Intent intent) {
        return new IRoleController.Stub() {

            @Override
            public void grantDefaultRoles(RemoteCallback callback) {
                enforceCallerSystemUid("grantDefaultRoles");

                Objects.requireNonNull(callback, "callback cannot be null");

                mWorkerHandler.post(() -> RoleControllerService.this.grantDefaultRoles(callback));
            }

            @Override
            public void onAddRoleHolder(String roleName, String packageName, int flags,
                    RemoteCallback callback) {
                enforceCallerSystemUid("onAddRoleHolder");

                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
                Preconditions.checkStringNotEmpty(packageName,
                        "packageName cannot be null or empty");
                Objects.requireNonNull(callback, "callback cannot be null");

                mWorkerHandler.post(() -> RoleControllerService.this.onAddRoleHolder(roleName,
                        packageName, flags, callback));
            }

            @Override
            public void onRemoveRoleHolder(String roleName, String packageName, int flags,
                    RemoteCallback callback) {
                enforceCallerSystemUid("onRemoveRoleHolder");

                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
                Preconditions.checkStringNotEmpty(packageName,
                        "packageName cannot be null or empty");
                Objects.requireNonNull(callback, "callback cannot be null");

                mWorkerHandler.post(() -> RoleControllerService.this.onRemoveRoleHolder(roleName,
                        packageName, flags, callback));
            }

            @Override
            public void onClearRoleHolders(String roleName, int flags, RemoteCallback callback) {
                enforceCallerSystemUid("onClearRoleHolders");

                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
                Objects.requireNonNull(callback, "callback cannot be null");

                mWorkerHandler.post(() -> RoleControllerService.this.onClearRoleHolders(roleName,
                        flags, callback));
            }

            private void enforceCallerSystemUid(@NonNull String methodName) {
                if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                    throw new SecurityException("Only the system process can call " + methodName
                            + "()");
                }
            }

            @Override
            public void isApplicationQualifiedForRole(String roleName, String packageName,
                    RemoteCallback callback) {
                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);

                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
                Preconditions.checkStringNotEmpty(packageName,
                        "packageName cannot be null or empty");
                Objects.requireNonNull(callback, "callback cannot be null");

                boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
                callback.sendResult(qualified ? Bundle.EMPTY : null);
            }

            @Override
            public void isApplicationVisibleForRole(String roleName, String packageName,
                    RemoteCallback callback) {
                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);

                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
                Preconditions.checkStringNotEmpty(packageName,
                        "packageName cannot be null or empty");
                Objects.requireNonNull(callback, "callback cannot be null");

                boolean visible = onIsApplicationVisibleForRole(roleName, packageName);
                callback.sendResult(visible ? Bundle.EMPTY : null);
            }

            @Override
            public void isRoleVisible(String roleName, RemoteCallback callback) {
                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);

                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
                Objects.requireNonNull(callback, "callback cannot be null");

                boolean visible = onIsRoleVisible(roleName);
                callback.sendResult(visible ? Bundle.EMPTY : null);
            }
        };
    }

    private void grantDefaultRoles(@NonNull RemoteCallback callback) {
        boolean successful = onGrantDefaultRoles();
        callback.sendResult(successful ? Bundle.EMPTY : null);
    }

    private void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
        boolean successful = onAddRoleHolder(roleName, packageName, flags);
        callback.sendResult(successful ? Bundle.EMPTY : null);
    }

    private void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
        boolean successful = onRemoveRoleHolder(roleName, packageName, flags);
        callback.sendResult(successful ? Bundle.EMPTY : null);
    }

    private void onClearRoleHolders(@NonNull String roleName,
            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
        boolean successful = onClearRoleHolders(roleName, flags);
        callback.sendResult(successful ? Bundle.EMPTY : null);
    }

    /**
     * Called by system to grant default permissions and roles.
     * <p>
     * This is typically when creating a new user or upgrading either system or
     * permission controller package
     *
     * @return whether this call was successful
     */
    @WorkerThread
    public abstract boolean onGrantDefaultRoles();

    /**
     * Add a specific application to the holders of a role. If the role is exclusive, the previous
     * holder will be replaced.
     * <p>
     * Implementation should enforce the role requirements and grant or revoke the relevant
     * privileges of roles.
     *
     * @param roleName the name of the role to add the role holder for
     * @param packageName the package name of the application to add to the role holders
     * @param flags optional behavior flags
     *
     * @return whether this call was successful
     *
     * @see RoleManager#addRoleHolderAsUser(String, String, int, UserHandle, Executor,
     *      RemoteCallback)
     */
    @WorkerThread
    public abstract boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
            @RoleManager.ManageHoldersFlags int flags);

    /**
     * Remove a specific application from the holders of a role.
     *
     * @param roleName the name of the role to remove the role holder for
     * @param packageName the package name of the application to remove from the role holders
     * @param flags optional behavior flags
     *
     * @return whether this call was successful
     *
     * @see RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, Executor,
     *      RemoteCallback)
     */
    @WorkerThread
    public abstract boolean onRemoveRoleHolder(@NonNull String roleName,
            @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags);

    /**
     * Remove all holders of a role.
     *
     * @param roleName the name of the role to remove role holders for
     * @param flags optional behavior flags
     *
     * @return whether this call was successful
     *
     * @see RoleManager#clearRoleHoldersAsUser(String, int, UserHandle, Executor, RemoteCallback)
     */
    @WorkerThread
    public abstract boolean onClearRoleHolders(@NonNull String roleName,
            @RoleManager.ManageHoldersFlags int flags);

    /**
     * Check whether an application is qualified for a role.
     *
     * @param roleName name of the role to check for
     * @param packageName package name of the application to check for
     *
     * @return whether the application is qualified for the role
     *
     * @deprecated Implement {@link #onIsApplicationVisibleForRole(String, String)} instead.
     */
    @Deprecated
    public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
            @NonNull String packageName);

    /**
     * Check whether an application is visible for a role.
     *
     * While an application can be qualified for a role, it can still stay hidden from user (thus
     * not visible). If an application is visible for a role, we may show things related to the role
     * for it, e.g. showing an entry pointing to the role settings in its application info page.
     *
     * @param roleName name of the role to check for
     * @param packageName package name of the application to check for
     *
     * @return whether the application is visible for the role
     */
    public boolean onIsApplicationVisibleForRole(@NonNull String roleName,
            @NonNull String packageName) {
        return onIsApplicationQualifiedForRole(roleName, packageName);
    }

    /**
     * Check whether a role should be visible to user.
     *
     * @param roleName name of the role to check for
     *
     * @return whether the role should be visible to user
     */
    public abstract boolean onIsRoleVisible(@NonNull String roleName);
}