/*
 * Copyright (C) 2020 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.car.watchdog;

import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_REBOOT;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_USER_REMOVED;

import static com.android.car.CarLog.TAG_WATCHDOG;
import static com.android.car.CarServiceUtils.assertAnyPermission;
import static com.android.car.CarServiceUtils.assertPermission;
import static com.android.car.CarServiceUtils.isEventAnyOfTypes;
import static com.android.car.CarServiceUtils.runOnMain;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import static com.android.car.internal.NotificationHelperBase.CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION;
import static com.android.car.internal.NotificationHelperBase.CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS;
import static com.android.car.internal.NotificationHelperBase.CAR_WATCHDOG_ACTION_RESOURCE_OVERUSE_DISABLE_APP;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.automotive.watchdog.internal.GarageMode;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
import android.automotive.watchdog.internal.PackageInfo;
import android.automotive.watchdog.internal.PackageIoOveruseStats;
import android.automotive.watchdog.internal.PowerCycle;
import android.automotive.watchdog.internal.ResourceStats;
import android.automotive.watchdog.internal.StateType;
import android.automotive.watchdog.internal.UserPackageIoUsageStats;
import android.automotive.watchdog.internal.UserState;
import android.car.Car;
import android.car.builtin.util.Slogf;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.CarPowerPolicy;
import android.car.hardware.power.CarPowerPolicyFilter;
import android.car.hardware.power.ICarPowerPolicyListener;
import android.car.hardware.power.ICarPowerStateListener;
import android.car.hardware.power.PowerComponent;
import android.car.user.UserLifecycleEventFilter;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.ICarWatchdogService;
import android.car.watchdog.ICarWatchdogServiceCallback;
import android.car.watchdog.IResourceOveruseListener;
import android.car.watchdog.PackageKillableState;
import android.car.watchdog.ResourceOveruseConfiguration;
import android.car.watchdog.ResourceOveruseStats;
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.dep.Trace;
import com.android.car.internal.util.ArrayUtils;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.power.CarPowerManagementService;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.lang.ref.WeakReference;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Service to implement CarWatchdogManager API.
 */
public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
    static final String TAG = CarLog.tagFor(CarWatchdogService.class);
    static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
    static final String ACTION_GARAGE_MODE_ON =
            "com.android.server.jobscheduler.GARAGE_MODE_ON";
    static final String ACTION_GARAGE_MODE_OFF =
            "com.android.server.jobscheduler.GARAGE_MODE_OFF";

    @VisibleForTesting
    static final int MISSING_ARG_VALUE = -1;

    private static final String FALLBACK_DATA_SYSTEM_CAR_DIR_PATH = "/data/system/car";
    private static final String WATCHDOG_DIR_NAME = "watchdog";

    private static final TimeSource SYSTEM_INSTANCE = new TimeSource() {
        @Override
        public Instant now() {
            return Instant.now();
        }

        @Override
        public String toString() {
            return "System time instance";
        }
    };

    private final Context mContext;
    private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
    private final PackageInfoHandler mPackageInfoHandler;
    private final WatchdogStorage mWatchdogStorage;
    private final WatchdogProcessHandler mWatchdogProcessHandler;
    private final WatchdogPerfHandler mWatchdogPerfHandler;
    private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener;

    private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;

    /*
     * TODO(b/192481350): Listen for GarageMode change notification rather than depending on the
     *  system_server broadcast when the CarService internal API for listening GarageMode change is
     *  implemented.
     */
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Trace.beginSection("CarWatchdogService-broadcast-" + action);
            switch (action) {
                case CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION:
                case CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS:
                case CAR_WATCHDOG_ACTION_RESOURCE_OVERUSE_DISABLE_APP:
                    mWatchdogPerfHandler.processUserNotificationIntent(intent);
                    break;
                case ACTION_GARAGE_MODE_ON:
                case ACTION_GARAGE_MODE_OFF:
                    int garageMode;
                    synchronized (mLock) {
                        garageMode = mCurrentGarageMode = Objects.equals(action,
                                ACTION_GARAGE_MODE_ON)
                                ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF;
                    }
                    mWatchdogPerfHandler.onGarageModeChange(garageMode);
                    if (garageMode == GarageMode.GARAGE_MODE_ON) {
                        mWatchdogStorage.shrinkDatabase();
                    }
                    notifyGarageModeChange(garageMode);
                    break;
                case ACTION_REBOOT:
                case ACTION_SHUTDOWN:
                    // FLAG_RECEIVER_FOREGROUND is checked to ignore the intent from UserController
                    // when a user is stopped.
                    if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) == 0) {
                        break;
                    }
                    int powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
                    try {
                        mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
                                powerCycle, /* arg2= */ 0);
                        if (DEBUG) {
                            Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)",
                                    powerCycle);
                        }
                    } catch (Exception e) {
                        Slogf.w(TAG, e, "Notifying power cycle state change failed");
                    }
                    break;
                case ACTION_USER_REMOVED: {
                    UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
                    int userId = userHandle.getIdentifier();
                    try {
                        mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE,
                                userId, UserState.USER_STATE_REMOVED);
                        if (DEBUG) {
                            Slogf.d(TAG, "Notified car watchdog daemon of removed user %d",
                                    userId);
                        }
                    } catch (RemoteException e) {
                        Slogf.w(TAG, e, "Failed to notify car watchdog daemon of removed user %d",
                                userId);
                    }
                    mWatchdogPerfHandler.deleteUser(userId);
                    break;
                }
                case ACTION_PACKAGE_CHANGED: {
                    mWatchdogPerfHandler.processPackageChangedIntent(intent);
                    break;
                }
                default:
                    Slogf.i(TAG, "Ignoring unknown intent %s", intent);
            }
            Trace.endSection();
        }
    };

    private final ICarPowerStateListener mCarPowerStateListener =
            new ICarPowerStateListener.Stub() {
        @Override
        public void onStateChanged(int state, long expirationTimeMs) {
            CarPowerManagementService powerService =
                    CarLocalServices.getService(CarPowerManagementService.class);
            if (powerService == null) {
                return;
            }
            int powerState = powerService.getPowerState();
            int powerCycle = carPowerStateToPowerCycle(powerState);
            if (powerCycle < 0) {
                return;
            }
            Trace.beginSection("CarWatchdogService-powerStateChanged-"
                    + CarPowerManagementService.powerStateToString(powerState));
            switch (powerCycle) {
                case PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE:
                    // Perform time consuming disk I/O operation during shutdown prepare to avoid
                    // incomplete I/O.
                    mWatchdogPerfHandler.writeMetadataFile();
                    break;
                case PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER:
                    // Watchdog service and daemon performs garage mode monitoring so delay writing
                    // to database until after shutdown enter.
                    mWatchdogPerfHandler.writeToDatabase();
                    break;
                case PowerCycle.POWER_CYCLE_SUSPEND_EXIT:
                    break;
                // ON covers resume.
                case PowerCycle.POWER_CYCLE_RESUME:
                    // There might be outdated & incorrect info. We should reset them before
                    // starting to do health check.
                    mWatchdogProcessHandler.prepareHealthCheck();
                    break;
                default:
                    return;
            }
            notifyPowerCycleChange(powerCycle);
            Trace.endSection();
        }
    };

    private final ICarPowerPolicyListener mCarDisplayPowerPolicyListener =
            new ICarPowerPolicyListener.Stub() {
                @Override
                public void onPolicyChanged(CarPowerPolicy appliedPolicy,
                        CarPowerPolicy accumulatedPolicy) {
                    Trace.beginSection("CarWatchdogService-carPowerPolicyChanged-"
                            + appliedPolicy.getPolicyId());
                    boolean isDisplayEnabled =
                            appliedPolicy.isComponentEnabled(PowerComponent.DISPLAY);
                    boolean didStateChange = false;
                    synchronized (mLock) {
                        didStateChange = mIsDisplayEnabled != isDisplayEnabled;
                        mIsDisplayEnabled = isDisplayEnabled;
                    }
                    if (didStateChange) {
                        mWatchdogPerfHandler.onDisplayStateChanged(isDisplayEnabled);
                    }
                    Trace.endSection();;
                }
            };

    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private boolean mReadyToRespond;
    @GuardedBy("mLock")
    private boolean mIsConnected;
    @GuardedBy("mLock")
    private @GarageMode int mCurrentGarageMode;
    @GuardedBy("mLock")
    private boolean mIsDisplayEnabled;

    public CarWatchdogService(Context context, Context carServiceBuiltinPackageContext) {
        this(context, carServiceBuiltinPackageContext,
                new WatchdogStorage(context, SYSTEM_INSTANCE), SYSTEM_INSTANCE);
    }

    @VisibleForTesting
    public CarWatchdogService(Context context, Context carServiceBuiltinPackageContext,
            WatchdogStorage watchdogStorage, TimeSource timeSource) {
        this(context, carServiceBuiltinPackageContext, watchdogStorage,
                timeSource, /*watchdogProcessHandler=*/ null, /*watchdogPerfHandler=*/ null);
    }

    @VisibleForTesting
    CarWatchdogService(Context context, Context carServiceBuiltinPackageContext,
            WatchdogStorage watchdogStorage, TimeSource timeSource,
            WatchdogProcessHandler watchdogProcessHandler,
            WatchdogPerfHandler watchdogPerfHandler) {
        mContext = context;
        mWatchdogStorage = watchdogStorage;
        mPackageInfoHandler = new PackageInfoHandler(mContext.getPackageManager());
        mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG);
        mWatchdogServiceForSystem = new ICarWatchdogServiceForSystemImpl(this);
        mWatchdogProcessHandler = watchdogProcessHandler != null ? watchdogProcessHandler
                : new WatchdogProcessHandler(mWatchdogServiceForSystem, mCarWatchdogDaemonHelper,
                        mPackageInfoHandler);
        mWatchdogPerfHandler =
                watchdogPerfHandler != null ? watchdogPerfHandler : new WatchdogPerfHandler(
                        mContext, carServiceBuiltinPackageContext,
                        mCarWatchdogDaemonHelper, mPackageInfoHandler, mWatchdogStorage,
                        timeSource);
        mConnectionListener = (isConnected) -> {
            mWatchdogPerfHandler.onDaemonConnectionChange(isConnected);
            synchronized (mLock) {
                mIsConnected = isConnected;
            }
            registerToDaemon();
        };
        mCurrentGarageMode = GarageMode.GARAGE_MODE_OFF;
        mIsDisplayEnabled = true;
    }

    @VisibleForTesting
    public void setCarWatchdogDaemonHelper(CarWatchdogDaemonHelper helper) {
        mCarWatchdogDaemonHelper = helper;
    }

    @Override
    public void init() {
        Trace.beginSection("CarWatchdogService.init");
        // TODO(b/266008677): The daemon reads the sendResourceUsageStatsEnabled sysprop at the
        // moment the CarWatchdogService connects to it. Therefore, the property must be set by
        // CarWatchdogService before connecting with the CarWatchdog daemon. Set the property to
        // true to enable the sending of resource usage stats from the daemon.
        mWatchdogProcessHandler.init();
        mWatchdogPerfHandler.init();
        subscribePowerManagementService();
        subscribeUserStateChange();
        subscribeBroadcastReceiver();
        mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
        mCarWatchdogDaemonHelper.connect();
        // To make sure the main handler is ready for responding to car watchdog daemon, registering
        // to the daemon is done through the main handler. Once the registration is completed, we
        // can assume that the main handler is not too busy handling other stuffs.
        postRegisterToDaemonMessage();
        if (DEBUG) {
            Slogf.d(TAG, "CarWatchdogService is initialized");
        }
        Trace.endSection();
    }

    @Override
    public void release() {
        Trace.beginSection("CarWatchdogService.release");
        mContext.unregisterReceiver(mBroadcastReceiver);
        unsubscribePowerManagementService();
        mWatchdogPerfHandler.release();
        mWatchdogStorage.release();
        unregisterFromDaemon();
        mCarWatchdogDaemonHelper.disconnect();
        Trace.endSection();
    }

    @Override
    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
    public void dump(IndentingPrintWriter writer) {
        writer.println("*" + getClass().getSimpleName() + "*");
        writer.increaseIndent();
        synchronized (mLock) {
            writer.println("Current garage mode: " + toGarageModeString(mCurrentGarageMode));
        }
        mWatchdogProcessHandler.dump(writer);
        mWatchdogPerfHandler.dump(writer);
        writer.decreaseIndent();
    }

    @Override
    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
    public void dumpProto(ProtoOutputStream proto) {
        synchronized (mLock) {
            proto.write(CarWatchdogDumpProto.CURRENT_GARAGE_MODE, mCurrentGarageMode);
        }
        mWatchdogProcessHandler.dumpProto(proto);
        mWatchdogPerfHandler.dumpProto(proto);
    }

    /**
     * Registers {@link android.car.watchdog.ICarWatchdogServiceCallback} to
     * {@link CarWatchdogService}.
     */
    @Override
    public void registerClient(ICarWatchdogServiceCallback client, int timeout) {
        assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
        mWatchdogProcessHandler.registerClient(client, timeout);
    }

    /**
     * Unregisters {@link android.car.watchdog.ICarWatchdogServiceCallback} from
     * {@link CarWatchdogService}.
     */
    @Override
    public void unregisterClient(ICarWatchdogServiceCallback client) {
        assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
        mWatchdogProcessHandler.unregisterClient(client);
    }

    /**
     * Tells {@link CarWatchdogService} that the client is alive.
     */
    @Override
    public void tellClientAlive(ICarWatchdogServiceCallback client, int sessionId) {
        assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
        mWatchdogProcessHandler.tellClientAlive(client, sessionId);
    }

    /** Returns {@link android.car.watchdog.ResourceOveruseStats} for the calling package. */
    @Override
    @NonNull
    public ResourceOveruseStats getResourceOveruseStats(
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
            @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
        return mWatchdogPerfHandler.getResourceOveruseStats(resourceOveruseFlag, maxStatsPeriod);
    }

    /**
      *  Returns {@link android.car.watchdog.ResourceOveruseStats} for all packages for the maximum
      *  specified period, and the specified resource types with stats greater than or equal to the
      *  minimum specified stats.
      */
    @Override
    @NonNull
    public List<ResourceOveruseStats> getAllResourceOveruseStats(
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
            @CarWatchdogManager.MinimumStatsFlag int minimumStatsFlag,
            @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
        assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
        return mWatchdogPerfHandler.getAllResourceOveruseStats(resourceOveruseFlag,
                minimumStatsFlag, maxStatsPeriod);
    }

    /** Returns {@link android.car.watchdog.ResourceOveruseStats} for the specified user package. */
    @Override
    @NonNull
    public ResourceOveruseStats getResourceOveruseStatsForUserPackage(
            @NonNull String packageName, @NonNull UserHandle userHandle,
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
            @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
        assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
        return mWatchdogPerfHandler.getResourceOveruseStatsForUserPackage(packageName, userHandle,
                resourceOveruseFlag, maxStatsPeriod);
    }

    /**
     * Adds {@link android.car.watchdog.IResourceOveruseListener} for the calling package's resource
     * overuse notifications.
     */
    @Override
    public void addResourceOveruseListener(
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
            @NonNull IResourceOveruseListener listener) {
        mWatchdogPerfHandler.addResourceOveruseListener(resourceOveruseFlag, listener);
    }

    /**
     * Removes the previously added {@link android.car.watchdog.IResourceOveruseListener} for the
     * calling package's resource overuse notifications.
     */
    @Override
    public void removeResourceOveruseListener(@NonNull IResourceOveruseListener listener) {
        mWatchdogPerfHandler.removeResourceOveruseListener(listener);
    }

    /**
     * Adds {@link android.car.watchdog.IResourceOveruseListener} for all packages' resource overuse
     * notifications.
     */
    @Override
    public void addResourceOveruseListenerForSystem(
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
            @NonNull IResourceOveruseListener listener) {
        assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
        mWatchdogPerfHandler.addResourceOveruseListenerForSystem(resourceOveruseFlag, listener);
    }

    /**
     * Removes the previously added {@link android.car.watchdog.IResourceOveruseListener} for all
     * packages' resource overuse notifications.
     */
    @Override
    public void removeResourceOveruseListenerForSystem(@NonNull IResourceOveruseListener listener) {
        assertPermission(mContext, Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
        mWatchdogPerfHandler.removeResourceOveruseListenerForSystem(listener);
    }

    /** Sets whether or not a user package is killable on resource overuse. */
    @Override
    public void setKillablePackageAsUser(String packageName, UserHandle userHandle,
            boolean isKillable) {
        assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
        mWatchdogPerfHandler.setKillablePackageAsUser(packageName, userHandle, isKillable);
    }

    /**
     * Returns all {@link android.car.watchdog.PackageKillableState} on resource overuse for
     * the specified user.
     */
    @Override
    @NonNull
    public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
        assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
        return mWatchdogPerfHandler.getPackageKillableStatesAsUser(userHandle);
    }

    /**
     * Sets {@link android.car.watchdog.ResourceOveruseConfiguration} for the specified resources.
     */
    @Override
    @CarWatchdogManager.ReturnCode
    public int setResourceOveruseConfigurations(
            List<ResourceOveruseConfiguration> configurations,
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag)
            throws RemoteException {
        assertPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG);
        return mWatchdogPerfHandler.setResourceOveruseConfigurations(configurations,
                resourceOveruseFlag);
    }

    /** Returns the available {@link android.car.watchdog.ResourceOveruseConfiguration}. */
    @Override
    @NonNull
    public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations(
            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
        assertAnyPermission(mContext, Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG,
                Car.PERMISSION_COLLECT_CAR_WATCHDOG_METRICS);
        return mWatchdogPerfHandler.getResourceOveruseConfigurations(resourceOveruseFlag);
    }

    /**
     * Enables/disables the watchdog daemon client health check process.
     */
    public void controlProcessHealthCheck(boolean enable) {
        assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
        mWatchdogProcessHandler.controlProcessHealthCheck(enable);
    }

    /**
     * Kills a specific package for a user due to resource overuse.
     *
     * @return whether package was killed
     */
    public boolean performResourceOveruseKill(String packageName, @UserIdInt int userId) {
        assertPermission(mContext, Car.PERMISSION_USE_CAR_WATCHDOG);
        return mWatchdogPerfHandler.disablePackageForUser(packageName, userId);
    }

    /**
     * Sets the thread priority for a specific thread.
     *
     * The thread must belong to the calling process.
     *
     * @throws IllegalArgumentException If the policy/priority is not valid.
     * @throws IllegalStateException If the provided tid does not belong to the calling process.
     * @throws RemoteException If binder error happens.
     * @throws ServiceSpecificException If car watchdog daemon failed to set the thread priority.
     * @throws UnsupportedOperationException If the current android release doesn't support the API.
     */
    public void setThreadPriority(int pid, int tid, int uid, int policy, int priority)
            throws RemoteException {
        mCarWatchdogDaemonHelper.setThreadPriority(pid, tid, uid, policy, priority);
    }

    /**
     * Gets the thread scheduling policy and priority for the specified thread.
     *
     * The thread must belong to the calling process.
     *
     * @throws IllegalStateException If the provided tid does not belong to the calling process or
     *         car watchdog daemon failed to get the priority.
     * @throws RemoteException If binder error happens.
     * @throws UnsupportedOperationException If the current android release doesn't support the API.
     */
    public int[] getThreadPriority(int pid, int tid, int uid) throws RemoteException {
        try {
            return mCarWatchdogDaemonHelper.getThreadPriority(pid, tid, uid);
        } catch (ServiceSpecificException e) {
            // Car watchdog daemon failed to get the priority.
            throw new IllegalStateException(e);
        }
    }

    @VisibleForTesting
    int getClientCount(int timeout) {
        return mWatchdogProcessHandler.getClientCount(timeout);
    }

    @VisibleForTesting
    void setOveruseHandlingDelay(long millis) {
        mWatchdogPerfHandler.setOveruseHandlingDelay(millis);
    }

    static File getWatchdogDirFile() {
        SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
        String systemCarDirPath = systemInterface == null ? FALLBACK_DATA_SYSTEM_CAR_DIR_PATH
                : systemInterface.getSystemCarDir().getAbsolutePath();
        return new File(systemCarDirPath, WATCHDOG_DIR_NAME);
    }

    private void notifyAllUserStates() {
        Trace.beginSection("CarWatchdogService.notifyAllUserStates");
        UserManager userManager = mContext.getSystemService(UserManager.class);
        List<UserHandle> users = userManager.getUserHandles(/* excludeDying= */ false);
        try {
            // TODO(b/152780162): reduce the number of RPC calls(isUserRunning).
            for (int i = 0; i < users.size(); ++i) {
                UserHandle user = users.get(i);
                int userState = userManager.isUserRunning(user)
                        ? UserState.USER_STATE_STARTED
                        : UserState.USER_STATE_STOPPED;
                mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE,
                        user.getIdentifier(), userState);
                mWatchdogProcessHandler.updateUserState(user.getIdentifier(),
                        userState == UserState.USER_STATE_STOPPED);
            }
            if (DEBUG) {
                Slogf.d(TAG, "Notified car watchdog daemon of user states");
            }
        } catch (RemoteException | RuntimeException e) {
            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
            // throws IllegalStateException. Catch the exception to avoid crashing the process.
            Slogf.w(TAG, e, "Notifying latest user states failed");
        }
        Trace.endSection();
    }

    private void notifyPowerCycleChange(@PowerCycle int powerCycle) {
        try {
            mCarWatchdogDaemonHelper.notifySystemStateChange(
                    StateType.POWER_CYCLE, powerCycle, MISSING_ARG_VALUE);
            if (DEBUG) {
                Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
            }
        } catch (RemoteException | RuntimeException e) {
            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
            // throws IllegalStateException. Catch the exception to avoid crashing the process.
            Slogf.w(TAG, e, "Notifying power cycle change to %d failed", powerCycle);
        }
    }

    private void notifyGarageModeChange(@GarageMode int garageMode) {
        try {
            mCarWatchdogDaemonHelper.notifySystemStateChange(
                    StateType.GARAGE_MODE, garageMode, MISSING_ARG_VALUE);
            if (DEBUG) {
                Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%d)", garageMode);
            }
        } catch (RemoteException | RuntimeException e) {
            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
            // throws IllegalStateException. Catch the exception to avoid crashing the process.
            Slogf.w(TAG, e, "Notifying garage mode change to %d failed", garageMode);
        }
    }

    private void postRegisterToDaemonMessage() {
        runOnMain(() -> {
            synchronized (mLock) {
                mReadyToRespond = true;
            }
            registerToDaemon();
        });
    }

    private void registerToDaemon() {
        synchronized (mLock) {
            if (!mIsConnected || !mReadyToRespond) {
                return;
            }
        }
        Trace.beginSection("CarWatchdogService.registerToDaemon");
        try {
            mCarWatchdogDaemonHelper.registerCarWatchdogService(mWatchdogServiceForSystem);
            if (DEBUG) {
                Slogf.d(TAG, "CarWatchdogService registers to car watchdog daemon");
            }
        } catch (RemoteException | RuntimeException e) {
            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
            // throws IllegalStateException. Catch the exception to avoid crashing the process.
            Slogf.w(TAG, e, "Cannot register to car watchdog daemon");
        }
        notifyAllUserStates();
        CarPowerManagementService powerService =
                CarLocalServices.getService(CarPowerManagementService.class);
        if (powerService != null) {
            int powerState = powerService.getPowerState();
            int powerCycle = carPowerStateToPowerCycle(powerState);
            if (powerCycle >= 0) {
                notifyPowerCycleChange(powerCycle);
            } else {
                Slogf.i(TAG, "Skipping notifying %d power state", powerState);
            }
        }
        int garageMode;
        synchronized (mLock) {
            // To avoid race condition, fetch {@link mCurrentGarageMode} just before
            // the {@link notifyGarageModeChange} call. For instance, if {@code mCurrentGarageMode}
            // changes before the above {@link notifyPowerCycleChange} call returns,
            // the {@link garageMode}'s value will be out of date.
            garageMode = mCurrentGarageMode;
        }
        notifyGarageModeChange(garageMode);
        Trace.endSection();
    }

    private void unregisterFromDaemon() {
        Trace.beginSection("CarWatchdogService.unregisterFromDaemon");
        try {
            mCarWatchdogDaemonHelper.unregisterCarWatchdogService(mWatchdogServiceForSystem);
            if (DEBUG) {
                Slogf.d(TAG, "CarWatchdogService unregisters from car watchdog daemon");
            }
        } catch (RemoteException | RuntimeException e) {
            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
            // throws IllegalStateException. Catch the exception to avoid crashing the process.
            Slogf.w(TAG, e, "Cannot unregister from car watchdog daemon");
        }
        Trace.endSection();
    }

    private void subscribePowerManagementService() {
        CarPowerManagementService powerService =
                CarLocalServices.getService(CarPowerManagementService.class);
        if (powerService == null) {
            Slogf.w(TAG, "Cannot get CarPowerManagementService");
            return;
        }
        powerService.registerListener(mCarPowerStateListener);
        powerService.addPowerPolicyListener(
                new CarPowerPolicyFilter.Builder().setComponents(PowerComponent.DISPLAY).build(),
                mCarDisplayPowerPolicyListener);
    }

    private void unsubscribePowerManagementService() {
        CarPowerManagementService powerService =
                CarLocalServices.getService(CarPowerManagementService.class);
        if (powerService == null) {
            Slogf.w(TAG, "Cannot get CarPowerManagementService");
            return;
        }
        powerService.unregisterListener(mCarPowerStateListener);
        powerService.removePowerPolicyListener(mCarDisplayPowerPolicyListener);
    }

    private void subscribeUserStateChange() {
        CarUserService userService = CarLocalServices.getService(CarUserService.class);
        if (userService == null) {
            Slogf.w(TAG, "Cannot get CarUserService");
            return;
        }
        UserLifecycleEventFilter userEventFilter =
                new UserLifecycleEventFilter.Builder()
                        .addEventType(USER_LIFECYCLE_EVENT_TYPE_STARTING)
                        .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
                        .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
                        .addEventType(USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED)
                        .addEventType(USER_LIFECYCLE_EVENT_TYPE_STOPPED).build();
        userService.addUserLifecycleListener(userEventFilter, (event) -> {
            if (!isEventAnyOfTypes(TAG, event, USER_LIFECYCLE_EVENT_TYPE_STARTING,
                    USER_LIFECYCLE_EVENT_TYPE_SWITCHING, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
                    USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) {
                return;
            }

            int userId = event.getUserHandle().getIdentifier();
            int userState;
            String userStateDesc;
            switch (event.getEventType()) {
                case USER_LIFECYCLE_EVENT_TYPE_STARTING:
                    mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/ false);
                    userState = UserState.USER_STATE_STARTED;
                    userStateDesc = "STARTING";
                    break;
                case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
                    userState = UserState.USER_STATE_SWITCHING;
                    userStateDesc = "SWITCHING";
                    break;
                case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
                    userState = UserState.USER_STATE_UNLOCKING;
                    userStateDesc = "UNLOCKING";
                    break;
                case USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED:
                    userState = UserState.USER_STATE_POST_UNLOCKED;
                    userStateDesc = "POST_UNLOCKED";
                    break;
                case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
                    mWatchdogProcessHandler.updateUserState(userId, /*isStopped=*/ true);
                    userState = UserState.USER_STATE_STOPPED;
                    userStateDesc = "STOPPING";
                    break;
                default:
                    return;
            }
            try {
                mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, userId,
                        userState);
                if (DEBUG) {
                    Slogf.d(TAG, "Notified car watchdog daemon user %d's user state, %s",
                            userId, userStateDesc);
                }
            } catch (RemoteException | RuntimeException e) {
                // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
                // throws IllegalStateException. Catch the exception to avoid crashing the process.
                Slogf.w(TAG, e, "Notifying user state change failed");
            }
        });
    }

    private void subscribeBroadcastReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(CAR_WATCHDOG_ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION);
        filter.addAction(ACTION_GARAGE_MODE_ON);
        filter.addAction(ACTION_GARAGE_MODE_OFF);
        filter.addAction(CAR_WATCHDOG_ACTION_LAUNCH_APP_SETTINGS);
        filter.addAction(CAR_WATCHDOG_ACTION_RESOURCE_OVERUSE_DISABLE_APP);
        filter.addAction(ACTION_USER_REMOVED);
        filter.addAction(ACTION_REBOOT);
        filter.addAction(ACTION_SHUTDOWN);

        mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter,
                Car.PERMISSION_CONTROL_CAR_WATCHDOG_CONFIG, /* scheduler= */ null,
                Context.RECEIVER_NOT_EXPORTED);

        // The package data scheme applies only for the ACTION_PACKAGE_CHANGED action. So, add a
        // filter for this action separately. Otherwise, the broadcast receiver won't receive
        // notifications for other actions.
        IntentFilter packageChangedFilter = new IntentFilter();
        packageChangedFilter.addAction(ACTION_PACKAGE_CHANGED);
        packageChangedFilter.addDataScheme("package");

        mContext.registerReceiverForAllUsers(mBroadcastReceiver, packageChangedFilter,
                /* broadcastPermission= */ null, /* scheduler= */ null,
                Context.RECEIVER_NOT_EXPORTED);
    }

    private static int carPowerStateToPowerCycle(int powerState) {
        switch (powerState) {
            // SHUTDOWN_PREPARE covers suspend and shutdown.
            case CarPowerManager.STATE_SHUTDOWN_PREPARE:
                return PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE;
            case CarPowerManager.STATE_SHUTDOWN_ENTER:
            case CarPowerManager.STATE_SUSPEND_ENTER:
            case CarPowerManager.STATE_HIBERNATION_ENTER:
                return PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
            case CarPowerManager.STATE_SUSPEND_EXIT:
            case CarPowerManager.STATE_HIBERNATION_EXIT:
                return PowerCycle.POWER_CYCLE_SUSPEND_EXIT;
            // ON covers resume.
            case CarPowerManager.STATE_ON:
                return PowerCycle.POWER_CYCLE_RESUME;
            default:
                Slogf.e(TAG, "Invalid power state: %d", powerState);
        }
        return -1;
    }

    private static String toGarageModeString(@GarageMode int garageMode) {
        switch (garageMode) {
            case GarageMode.GARAGE_MODE_OFF:
                return "GARAGE_MODE_OFF";
            case GarageMode.GARAGE_MODE_ON:
                return "GARAGE_MODE_ON";
            default:
                Slogf.e(TAG, "Invalid garage mode: %d", garageMode);
        }
        return "INVALID";
    }

    private static final class ICarWatchdogServiceForSystemImpl
            extends ICarWatchdogServiceForSystem.Stub {
        private final WeakReference<CarWatchdogService> mService;

        ICarWatchdogServiceForSystemImpl(CarWatchdogService service) {
            mService = new WeakReference<>(service);
        }

        @Override
        public void checkIfAlive(int sessionId, int timeout) {
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return;
            }
            service.mWatchdogProcessHandler.postHealthCheckMessage(sessionId);
        }

        @Override
        public void prepareProcessTermination() {
            Slogf.w(TAG, "CarWatchdogService is about to be killed by car watchdog daemon");
        }

        @Override
        public List<PackageInfo> getPackageInfosForUids(
                int[] uids, List<String> vendorPackagePrefixes) {
            if (ArrayUtils.isEmpty(uids)) {
                Slogf.w(TAG, "UID list is empty");
                return Collections.emptyList();
            }
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return Collections.emptyList();
            }
            return service.mPackageInfoHandler.getPackageInfosForUids(uids, vendorPackagePrefixes);
        }

        // TODO(b/269191275): This method was replaced by onLatestResourceStats in Android U.
        //  Make method no-op in Android W (N+2 releases).
        @Override
        public void latestIoOveruseStats(List<PackageIoOveruseStats> packageIoOveruseStats) {
            if (packageIoOveruseStats.isEmpty()) {
                Slogf.w(TAG, "Latest I/O overuse stats is empty");
                return;
            }
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return;
            }
            service.mWatchdogPerfHandler.latestIoOveruseStats(packageIoOveruseStats);
        }

        @Override
        public void onLatestResourceStats(List<ResourceStats> resourceStats) {
            // TODO(b/266008146): Handle the resourceUsageStats.
            if (resourceStats.isEmpty()) {
                Slogf.w(TAG, "Latest resource stats is empty");
                return;
            }
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return;
            }
            for (int i = 0; i < resourceStats.size(); i++) {
                ResourceStats stats = resourceStats.get(i);
                if (stats.resourceOveruseStats == null
                        || stats.resourceOveruseStats.packageIoOveruseStats.isEmpty()) {
                    Slogf.w(TAG, "Received latest I/O overuse stats is empty");
                    continue;
                }
                service.mWatchdogPerfHandler.latestIoOveruseStats(
                        stats.resourceOveruseStats.packageIoOveruseStats);
            }
        }

        @Override
        public void resetResourceOveruseStats(List<String> packageNames) {
            if (packageNames.isEmpty()) {
                Slogf.w(TAG, "Provided an empty package name to reset resource overuse stats");
                return;
            }
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return;
            }
            service.mWatchdogPerfHandler.resetResourceOveruseStats(new ArraySet<>(packageNames));
        }

        // TODO(b/273354756): This method was replaced by an async request/response pattern
        // Android U. Requests for the I/O stats are received through the requestTodayIoUsageStats
        // method. And responses are sent through the carwatchdog daemon via
        // ICarWatchdog#onTodayIoUsageStats. Make method no-op in Android W (N+2 releases).
        @Override
        public List<UserPackageIoUsageStats> getTodayIoUsageStats() {
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return Collections.emptyList();
            }
            return service.mWatchdogPerfHandler.getTodayIoUsageStats();
        }

        @Override
        public void requestAidlVhalPid() {
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return;
            }
            service.mWatchdogProcessHandler.asyncFetchAidlVhalPid();
        }

        @Override
        public void requestTodayIoUsageStats() {
            CarWatchdogService service = mService.get();
            if (service == null) {
                Slogf.w(TAG, "CarWatchdogService is not available");
                return;
            }
            service.mWatchdogPerfHandler.asyncFetchTodayIoUsageStats();
        }

        @Override
        public String getInterfaceHash() {
            return ICarWatchdogServiceForSystemImpl.HASH;
        }

        @Override
        public int getInterfaceVersion() {
            return ICarWatchdogServiceForSystemImpl.VERSION;
        }
    }
}
