/*
 * Copyright (C) 2023 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;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A service module such as MediaSessionService, VOIP, Camera, Microphone, Location can ask
 * ActivityManagerService to start a foreground service delegate on behalf of the actual app,
 * by which the client app's process state can be promoted to FOREGROUND_SERVICE process state which
 * is higher than the app's actual process state if the app is in the background. This can help to
 * keep the app in the memory and extra run-time.
 * The app does not need to define an actual service component nor add it into manifest file.
 *
 * @hide
 */
public class ForegroundServiceDelegationOptions {

    public static final int DELEGATION_SERVICE_DEFAULT = 0;
    public static final int DELEGATION_SERVICE_DATA_SYNC = 1;
    public static final int DELEGATION_SERVICE_MEDIA_PLAYBACK = 2;
    public static final int DELEGATION_SERVICE_PHONE_CALL = 3;
    public static final int DELEGATION_SERVICE_LOCATION = 4;
    public static final int DELEGATION_SERVICE_CONNECTED_DEVICE = 5;
    public static final int DELEGATION_SERVICE_MEDIA_PROJECTION = 6;
    public static final int DELEGATION_SERVICE_CAMERA = 7;
    public static final int DELEGATION_SERVICE_MICROPHONE = 8;
    public static final int DELEGATION_SERVICE_HEALTH = 9;
    public static final int DELEGATION_SERVICE_REMOTE_MESSAGING = 10;
    public static final int DELEGATION_SERVICE_SYSTEM_EXEMPTED = 11;
    public static final int DELEGATION_SERVICE_SPECIAL_USE = 12;

    @IntDef(flag = false, prefix = { "DELEGATION_SERVICE_" }, value = {
            DELEGATION_SERVICE_DEFAULT,
            DELEGATION_SERVICE_DATA_SYNC,
            DELEGATION_SERVICE_MEDIA_PLAYBACK,
            DELEGATION_SERVICE_PHONE_CALL,
            DELEGATION_SERVICE_LOCATION,
            DELEGATION_SERVICE_CONNECTED_DEVICE,
            DELEGATION_SERVICE_MEDIA_PROJECTION,
            DELEGATION_SERVICE_CAMERA,
            DELEGATION_SERVICE_MICROPHONE,
            DELEGATION_SERVICE_HEALTH,
            DELEGATION_SERVICE_REMOTE_MESSAGING,
            DELEGATION_SERVICE_SYSTEM_EXEMPTED,
            DELEGATION_SERVICE_SPECIAL_USE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DelegationService {}

    // The actual app's PID
    public final int mClientPid;
    // The actual app's UID
    public final int mClientUid;
    // The actual app's package name
    @NonNull
    public final String mClientPackageName;
    // The actual app's app thread
    @Nullable
    public final IApplicationThread mClientAppThread;
    public final boolean mSticky; // Is it a sticky service

    // The delegation service's instance name which is to identify the delegate.
    @NonNull
    public String mClientInstanceName;
    // The foreground service types it consists of.
    public final int mForegroundServiceTypes;
    /**
     * The service's name such as MediaSessionService, VOIP, Camera, Microphone, Location. This is
     * the internal module's name which actually starts the FGS delegate on behalf of the client
     * app.
     */
    public final @DelegationService int mDelegationService;

    /**
     * The optional notification Id of the foreground service delegation.
     */
    public final int mClientNotificationId;

    /**
     * The optional notification of the foreground service delegation.
     */
    public final @Nullable Notification mClientNotification;

    public ForegroundServiceDelegationOptions(int clientPid,
            int clientUid,
            @NonNull String clientPackageName,
            @NonNull IApplicationThread clientAppThread,
            boolean isSticky,
            @NonNull String clientInstanceName,
            int foregroundServiceTypes,
            @DelegationService int delegationService) {
        this(clientPid, clientUid, clientPackageName, clientAppThread, isSticky,
                clientInstanceName, foregroundServiceTypes, delegationService,
                0 /* notificationId */, null /* notification */);
    }

    public ForegroundServiceDelegationOptions(int clientPid,
            int clientUid,
            @NonNull String clientPackageName,
            @NonNull IApplicationThread clientAppThread,
            boolean isSticky,
            @NonNull String clientInstanceName,
            int foregroundServiceTypes,
            @DelegationService int delegationService,
            int clientNotificationId,
            @Nullable Notification clientNotification) {
        mClientPid = clientPid;
        mClientUid = clientUid;
        mClientPackageName = clientPackageName;
        mClientAppThread = clientAppThread;
        mSticky = isSticky;
        mClientInstanceName = clientInstanceName;
        mForegroundServiceTypes = foregroundServiceTypes;
        mDelegationService = delegationService;
        mClientNotificationId = clientNotificationId;
        mClientNotification = clientNotification;
    }

    /**
     * A service delegates a foreground service state to a clientUID using a instanceName.
     * This delegation is uniquely identified by
     * mDelegationService/mClientUid/mClientPid/mClientInstanceName
     */
    public boolean isSameDelegate(ForegroundServiceDelegationOptions that) {
        return this.mDelegationService == that.mDelegationService
                && this.mClientUid == that.mClientUid
                && this.mClientPid == that.mClientPid
                && this.mClientInstanceName.equals(that.mClientInstanceName);
    }

    /**
     * Construct a component name for this delegate.
     */
    public ComponentName getComponentName() {
        return new ComponentName(mClientPackageName, serviceCodeToString(mDelegationService)
                + ":" + mClientInstanceName);
    }

    /**
     * Get string description of this delegate options.
     */
    public String getDescription() {
        StringBuilder sb = new StringBuilder(128);
        sb.append("ForegroundServiceDelegate{")
                .append("package:")
                .append(mClientPackageName)
                .append(",")
                .append("service:")
                .append(serviceCodeToString(mDelegationService))
                .append(",")
                .append("uid:")
                .append(mClientUid)
                .append(",")
                .append("pid:")
                .append(mClientPid)
                .append(",")
                .append("instance:")
                .append(mClientInstanceName)
                .append("}");
        return sb.toString();
    }

    /**
     * Map the integer service code to string name.
     * @param serviceCode
     * @return
     */
    public static String serviceCodeToString(@DelegationService int serviceCode) {
        switch (serviceCode) {
            case DELEGATION_SERVICE_DEFAULT:
                return "DEFAULT";
            case DELEGATION_SERVICE_DATA_SYNC:
                return "DATA_SYNC";
            case DELEGATION_SERVICE_MEDIA_PLAYBACK:
                return "MEDIA_PLAYBACK";
            case DELEGATION_SERVICE_PHONE_CALL:
                return "PHONE_CALL";
            case DELEGATION_SERVICE_LOCATION:
                return "LOCATION";
            case DELEGATION_SERVICE_CONNECTED_DEVICE:
                return "CONNECTED_DEVICE";
            case DELEGATION_SERVICE_MEDIA_PROJECTION:
                return "MEDIA_PROJECTION";
            case DELEGATION_SERVICE_CAMERA:
                return "CAMERA";
            case DELEGATION_SERVICE_MICROPHONE:
                return "MICROPHONE";
            case DELEGATION_SERVICE_HEALTH:
                return "HEALTH";
            case DELEGATION_SERVICE_REMOTE_MESSAGING:
                return "REMOTE_MESSAGING";
            case DELEGATION_SERVICE_SYSTEM_EXEMPTED:
                return "SYSTEM_EXEMPTED";
            case DELEGATION_SERVICE_SPECIAL_USE:
                return "SPECIAL_USE";
            default:
                return "(unknown:" + serviceCode + ")";
        }
    }

    /**
     * The helper class to build the instance of {@link ForegroundServiceDelegate}.
     *
     * @hide
     */
    public static class Builder {
        int mClientPid; // The actual app PID
        int mClientUid; // The actual app UID
        String mClientPackageName; // The actual app's package name
        int mClientNotificationId; // The actual app's notification id
        Notification mClientNotification; // The actual app's notification
        IApplicationThread mClientAppThread; // The actual app's app thread
        boolean mSticky; // Is it a sticky service
        String mClientInstanceName; // The delegation service instance name
        int mForegroundServiceTypes; // The foreground service types it consists of
        @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP

        /**
         * Set the client app's PID.
         */
        public Builder setClientPid(int clientPid) {
            mClientPid = clientPid;
            return this;
        }

        /**
         * Set the client app's UID.
         */
        public Builder setClientUid(int clientUid) {
            mClientUid = clientUid;
            return this;
        }

        /**
         * Set the client app's package name.
         */
        public Builder setClientPackageName(@NonNull String clientPackageName) {
            mClientPackageName = clientPackageName;
            return this;
        }

        /**
         * Set the notification from the client app.
         */
        public Builder setClientNotification(int clientNotificationId,
                @Nullable Notification clientNotification) {
            mClientNotificationId = clientNotificationId;
            mClientNotification = clientNotification;
            return this;
        }

        /**
         * Set the client app's application thread.
         */
        public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
            mClientAppThread = clientAppThread;
            return this;
        }

        /**
         * Set the client instance of this service.
         */
        public Builder setClientInstanceName(@NonNull String clientInstanceName) {
            mClientInstanceName = clientInstanceName;
            return this;
        }

        /**
         * Set stickiness of this service.
         */
        public Builder setSticky(boolean isSticky) {
            mSticky = isSticky;
            return this;
        }

        /**
         * Set the foreground service type.
         */
        public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
            mForegroundServiceTypes = foregroundServiceTypes;
            return this;
        }

        /**
         * Set the delegation service type.
         */
        public Builder setDelegationService(@DelegationService int delegationService) {
            mDelegationService = delegationService;
            return this;
        }

        /**
         * @return An instance of {@link ForegroundServiceDelegationOptions}.
         */
        public ForegroundServiceDelegationOptions build() {
            return new ForegroundServiceDelegationOptions(mClientPid,
                mClientUid,
                mClientPackageName,
                mClientAppThread,
                mSticky,
                mClientInstanceName,
                mForegroundServiceTypes,
                mDelegationService,
                mClientNotificationId,
                mClientNotification
            );
        }
    }
}
