/*
 * Copyright (C) 2022 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.am;

import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.media.session.MediaSessionManager;
import android.os.BatteryManagerInternal;
import android.os.BatteryStatsInternal;
import android.os.Handler;
import android.os.ServiceManager;
import android.permission.PermissionManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.app.IAppOpsService;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.am.AppRestrictionController.TrackerType;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;

import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.util.ArrayList;

/**
 * Base class to track certain state of the app, could be used to determine the restriction level.
 *
 * @param <T> A class derived from BaseAppStatePolicy.
 */
public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
    protected static final String TAG = TAG_WITH_CLASS_NAME ? "BaseAppStatePolicy" : TAG_AM;

    static final long ONE_MINUTE = 60 * 1_000L;
    static final long ONE_HOUR = 60 * ONE_MINUTE;
    static final long ONE_DAY = 24 * ONE_HOUR;

    static final int STATE_TYPE_MEDIA_SESSION = 1;
    static final int STATE_TYPE_FGS_MEDIA_PLAYBACK = 1 << 1;
    static final int STATE_TYPE_FGS_LOCATION = 1 << 2;
    static final int STATE_TYPE_FGS_WITH_NOTIFICATION = 1 << 3;
    static final int STATE_TYPE_PERMISSION = 1 << 4;
    static final int STATE_TYPE_NUM = 5;

    static final int STATE_TYPE_INDEX_MEDIA_SESSION = 0;
    static final int STATE_TYPE_INDEX_FGS_MEDIA_PLAYBACK = 1;
    static final int STATE_TYPE_INDEX_FGS_LOCATION = 2;
    static final int STATE_TYPE_INDEX_FGS_WITH_NOTIFICATION = 3;
    static final int STATE_TYPE_INDEX_PERMISSION = 4;

    protected final AppRestrictionController mAppRestrictionController;
    protected final Context mContext;
    protected final Handler mBgHandler;
    protected final Object mLock;
    protected final ArrayList<StateListener> mStateListeners = new ArrayList<>();
    final Injector<T> mInjector;

    interface StateListener {
        void onStateChange(int uid, String packageName, boolean start, long now, int stateType);
    }

    BaseAppStateTracker(Context context, AppRestrictionController controller,
            @Nullable Constructor<? extends Injector<T>> injector, Object outerContext) {
        mContext = context;
        mAppRestrictionController = controller;
        mBgHandler = controller.getBackgroundHandler();
        mLock = controller.getLock();
        if (injector == null) {
            mInjector = new Injector<>();
        } else {
            Injector<T> localInjector = null;
            try {
                localInjector = injector.newInstance(outerContext);
            } catch (Exception e) {
                Slog.w(TAG, "Unable to instantiate " + injector, e);
            }
            mInjector = (localInjector == null) ? new Injector<>() : localInjector;
        }
    }

    static int stateTypeToIndex(int stateType) {
        return Integer.numberOfTrailingZeros(stateType);
    }

    static int stateIndexToType(int stateTypeIndex) {
        return 1 << stateTypeIndex;
    }

    static String stateTypesToString(int stateTypes) {
        final StringBuilder sb = new StringBuilder("[");
        boolean needDelimiter = false;
        for (int stateType = Integer.highestOneBit(stateTypes); stateType != 0;
                stateType = Integer.highestOneBit(stateTypes)) {
            if (needDelimiter) {
                sb.append('|');
            }
            needDelimiter = true;
            switch (stateType) {
                case STATE_TYPE_MEDIA_SESSION:
                    sb.append("MEDIA_SESSION");
                    break;
                case STATE_TYPE_FGS_MEDIA_PLAYBACK:
                    sb.append("FGS_MEDIA_PLAYBACK");
                    break;
                case STATE_TYPE_FGS_LOCATION:
                    sb.append("FGS_LOCATION");
                    break;
                case STATE_TYPE_FGS_WITH_NOTIFICATION:
                    sb.append("FGS_NOTIFICATION");
                    break;
                case STATE_TYPE_PERMISSION:
                    sb.append("PERMISSION");
                    break;
                default:
                    return "[UNKNOWN(" + Integer.toHexString(stateTypes) + ")]";
            }
            stateTypes &= ~stateType;
        }
        sb.append("]");
        return sb.toString();
    }

    void registerStateListener(@NonNull StateListener listener) {
        synchronized (mLock) {
            mStateListeners.add(listener);
        }
    }

    void notifyListenersOnStateChange(int uid, String packageName,
            boolean start, long now, int stateType) {
        synchronized (mLock) {
            for (int i = 0, size = mStateListeners.size(); i < size; i++) {
                mStateListeners.get(i).onStateChange(uid, packageName, start, now, stateType);
            }
        }
    }

    /**
     * Return the type of tracker (as defined by AppRestrictionController.TrackerType)
     */
    @TrackerType int getType() {
        return AppRestrictionController.TRACKER_TYPE_UNKNOWN;
    }

    /**
     * Return the relevant info object for the tracker for the given uid, used for statsd.
     */
    byte[] getTrackerInfoForStatsd(int uid) {
        return null;
    }

    /**
     * Return the policy holder of this tracker.
     */
    T getPolicy() {
        return mInjector.getPolicy();
    }

    /**
     * Called when the system is ready to rock.
     */
    void onSystemReady() {
        mInjector.onSystemReady();
    }

    /**
     * Called when a user with the given uid is added.
     */
    void onUidAdded(final int uid) {
    }

    /**
     * Called when a user with the given uid is removed.
     */
    void onUidRemoved(final int uid) {
    }

    /**
     * Called when a user with the given userId is added.
     */
    void onUserAdded(final @UserIdInt int userId) {
    }

    /**
     * Called when a user with the given userId is started.
     */
    void onUserStarted(final @UserIdInt int userId) {
    }

    /**
     * Called when a user with the given userId is stopped.
     */
    void onUserStopped(final @UserIdInt int userId) {
    }

    /**
     * Called when a user with the given userId is removed.
     */
    void onUserRemoved(final @UserIdInt int userId) {
    }

    /**
     * Called when the system sends LOCKED_BOOT_COMPLETED.
     */
    void onLockedBootCompleted() {
    }

    /**
     * Called when a device config property in the activity manager namespace
     * has changed.
     */
    void onPropertiesChanged(@NonNull String name) {
        getPolicy().onPropertiesChanged(name);
    }

    /**
     * Called when an app has transitioned into an active state due to user interaction.
     */
    void onUserInteractionStarted(String packageName, int uid) {
    }

    /**
     * Called when the background restriction settings of the given app is changed.
     */
    void onBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
    }

    /**
     * Called when the process state of the given UID has been changed.
     *
     * <p>Note: as of now, for simplification, we're tracking the TOP state changes only.</p>
     */
    void onUidProcStateChanged(int uid, int procState) {
    }

    /**
     * Called when all the processes in the given UID have died.
     */
    void onUidGone(int uid) {
    }

    /**
     * Dump to the given printer writer.
     */
    void dump(PrintWriter pw, String prefix) {
        mInjector.getPolicy().dump(pw, "  " + prefix);
    }

    void dumpAsProto(ProtoOutputStream proto, int uid) {
    }

    static class Injector<T extends BaseAppStatePolicy> {
        T mAppStatePolicy;

        ActivityManagerInternal mActivityManagerInternal;
        BatteryManagerInternal mBatteryManagerInternal;
        BatteryStatsInternal mBatteryStatsInternal;
        DeviceIdleInternal mDeviceIdleInternal;
        UserManagerInternal mUserManagerInternal;
        PackageManager mPackageManager;
        PackageManagerInternal mPackageManagerInternal;
        PermissionManager mPermissionManager;
        PermissionManagerServiceInternal mPermissionManagerServiceInternal;
        AppOpsManager mAppOpsManager;
        MediaSessionManager mMediaSessionManager;
        RoleManager mRoleManager;
        NotificationManagerInternal mNotificationManagerInternal;
        IAppOpsService mIAppOpsService;
        Context mContext;

        void setPolicy(T policy) {
            mAppStatePolicy = policy;
        }

        void onSystemReady() {
            mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
            mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
            mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class);
            mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
            mPermissionManagerServiceInternal = LocalServices.getService(
                    PermissionManagerServiceInternal.class);
            final Context context = mAppStatePolicy.mTracker.mContext;
            mPackageManager = context.getPackageManager();
            mAppOpsManager = context.getSystemService(AppOpsManager.class);
            mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
            mPermissionManager = context.getSystemService(PermissionManager.class);
            mRoleManager = context.getSystemService(RoleManager.class);
            mNotificationManagerInternal = LocalServices.getService(
                    NotificationManagerInternal.class);
            mIAppOpsService = IAppOpsService.Stub.asInterface(
                    ServiceManager.getService(Context.APP_OPS_SERVICE));
            mContext = context;

            getPolicy().onSystemReady();
        }

        ActivityManagerInternal getActivityManagerInternal() {
            return mActivityManagerInternal;
        }

        BatteryManagerInternal getBatteryManagerInternal() {
            return mBatteryManagerInternal;
        }

        BatteryStatsInternal getBatteryStatsInternal() {
            return mBatteryStatsInternal;
        }

        T getPolicy() {
            return mAppStatePolicy;
        }

        DeviceIdleInternal getDeviceIdleInternal() {
            return mDeviceIdleInternal;
        }

        UserManagerInternal getUserManagerInternal() {
            return mUserManagerInternal;
        }

        /**
         * Equivalent to {@link java.lang.System#currentTimeMillis}.
         */
        long currentTimeMillis() {
            return System.currentTimeMillis();
        }

        PackageManager getPackageManager() {
            return mPackageManager;
        }

        PackageManagerInternal getPackageManagerInternal() {
            return mPackageManagerInternal;
        }

        PermissionManager getPermissionManager() {
            return mPermissionManager;
        }

        PermissionManagerServiceInternal getPermissionManagerServiceInternal() {
            return mPermissionManagerServiceInternal;
        }

        AppOpsManager getAppOpsManager() {
            return mAppOpsManager;
        }

        MediaSessionManager getMediaSessionManager() {
            return mMediaSessionManager;
        }

        long getServiceStartForegroundTimeout() {
            return mActivityManagerInternal.getServiceStartForegroundTimeout();
        }

        RoleManager getRoleManager() {
            return mRoleManager;
        }

        NotificationManagerInternal getNotificationManagerInternal() {
            return mNotificationManagerInternal;
        }

        IAppOpsService getIAppOpsService() {
            return mIAppOpsService;
        }

        int checkPermission(String perm, int pid, int uid) {
            return mContext.checkPermission(perm, pid, uid);
        }
    }
}
