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

import static android.service.contentcapture.ContentCaptureService.setClientState;
import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
import static android.view.contentcapture.ContentCaptureSession.STATE_DUPLICATED_ID;
import static android.view.contentcapture.ContentCaptureSession.STATE_INTERNAL_ERROR;
import static android.view.contentcapture.ContentCaptureSession.STATE_NOT_WHITELISTED;
import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE;

import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent;
import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent;
import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSetWhitelistEvent;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.contentcapture.ActivityEvent;
import android.service.contentcapture.ActivityEvent.ActivityEventType;
import android.service.contentcapture.ContentCaptureService;
import android.service.contentcapture.ContentCaptureServiceInfo;
import android.service.contentcapture.FlushMetrics;
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.IDataShareCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.DataRemovalRequest;
import android.view.contentcapture.DataShareRequest;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
import com.android.server.infra.AbstractPerUserSystemService;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * Per-user instance of {@link ContentCaptureManagerService}.
 */
final class ContentCapturePerUserService
        extends
        AbstractPerUserSystemService<ContentCapturePerUserService, ContentCaptureManagerService>
        implements ContentCaptureServiceCallbacks {

    private static final String TAG = ContentCapturePerUserService.class.getSimpleName();

    @GuardedBy("mLock")
    private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>();

    /**
     * Reference to the remote service.
     *
     * <p>It's set in the constructor, but it's also updated when the service's updated in the
     * master's cache (for example, because a temporary service was set).
     */
    @GuardedBy("mLock")
    @Nullable
    RemoteContentCaptureService mRemoteService;

    private final ContentCaptureServiceRemoteCallback mRemoteServiceCallback =
            new ContentCaptureServiceRemoteCallback();

    /**
     * List of conditions keyed by package.
     */
    @GuardedBy("mLock")
    private final ArrayMap<String, ArraySet<ContentCaptureCondition>> mConditionsByPkg =
            new ArrayMap<>();

    /**
     * When {@code true}, remote service died but service state is kept so it's restored after
     * the system re-binds to it.
     */
    @GuardedBy("mLock")
    private boolean mZombie;

    @GuardedBy("mLock")
    private ContentCaptureServiceInfo mInfo;

    // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's

    ContentCapturePerUserService(@NonNull ContentCaptureManagerService master,
            @NonNull Object lock, boolean disabled, @UserIdInt int userId) {
        super(master, lock, userId);
        updateRemoteServiceLocked(disabled);
    }

    /**
     * Updates the reference to the remote service.
     */
    private void updateRemoteServiceLocked(boolean disabled) {
        if (mMaster.verbose) Slog.v(TAG, "updateRemoteService(disabled=" + disabled + ")");
        if (mRemoteService != null) {
            if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service");
            mRemoteService.destroy();
            mRemoteService = null;
            resetContentCaptureWhitelistLocked();
        }

        // Updates the component name
        final ComponentName serviceComponentName = updateServiceInfoLocked();

        if (serviceComponentName == null) {
            if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): no service component name");
            return;
        }

        if (!disabled) {
            if (mMaster.debug) {
                Slog.d(TAG, "updateRemoteService(): creating new remote service for "
                        + serviceComponentName);
            }
            mRemoteService = new RemoteContentCaptureService(mMaster.getContext(),
                    ContentCaptureService.SERVICE_INTERFACE, serviceComponentName,
                    mRemoteServiceCallback, mUserId, this, mMaster.isBindInstantServiceAllowed(),
                    mMaster.verbose, mMaster.mDevCfgIdleUnbindTimeoutMs);
        }
    }

    @Override // from PerUserSystemService
    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
            throws NameNotFoundException {
        mInfo = new ContentCaptureServiceInfo(getContext(), serviceComponent,
                isTemporaryServiceSetLocked(), mUserId);
        return mInfo.getServiceInfo();
    }

    @Override // from PerUserSystemService
    @GuardedBy("mLock")
    protected boolean updateLocked(boolean disabled) {
        final boolean disabledStateChanged = super.updateLocked(disabled);
        if (disabledStateChanged) {
            // update session content capture enabled state.
            for (int i = 0; i < mSessions.size(); i++) {
                mSessions.valueAt(i).setContentCaptureEnabledLocked(!disabled);
            }
        }
        destroyLocked();
        updateRemoteServiceLocked(disabled);
        return disabledStateChanged;
    }

    @Override // from ContentCaptureServiceCallbacks
    public void onServiceDied(@NonNull RemoteContentCaptureService service) {
        // Don't do anything; eventually the system will bind to it again...
        Slog.w(TAG, "remote service died: " + service);
        synchronized (mLock) {
            mZombie = true;
        }
    }

    /**
     * Called after the remote service connected, it's used to restore state from a 'zombie'
     * service (i.e., after it died).
     */
    void onConnected() {
        synchronized (mLock) {
            if (mZombie) {
                // Sanity check - shouldn't happen
                if (mRemoteService == null) {
                    Slog.w(TAG, "Cannot ressurect sessions because remote service is null");
                    return;
                }

                mZombie = false;
                resurrectSessionsLocked();
            }
        }
    }

    private void resurrectSessionsLocked() {
        final int numSessions = mSessions.size();
        if (mMaster.debug) {
            Slog.d(TAG, "Ressurrecting remote service (" + mRemoteService + ") on "
                    + numSessions + " sessions");
        }

        for (int i = 0; i < numSessions; i++) {
            final ContentCaptureServerSession session = mSessions.valueAt(i);
            session.resurrectLocked();
        }
    }

    void onPackageUpdatingLocked() {
        final int numSessions = mSessions.size();
        if (mMaster.debug) {
            Slog.d(TAG, "Pausing " + numSessions + " sessions while package is updating");
        }
        for (int i = 0; i < numSessions; i++) {
            final ContentCaptureServerSession session = mSessions.valueAt(i);
            session.pauseLocked();
        }
    }

    void onPackageUpdatedLocked() {
        updateRemoteServiceLocked(!isEnabledLocked());
        resurrectSessionsLocked();
    }

    @GuardedBy("mLock")
    public void startSessionLocked(@NonNull IBinder activityToken,
            @NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid,
            int flags, @NonNull IResultReceiver clientReceiver) {
        if (activityPresentationInfo == null) {
            Slog.w(TAG, "basic activity info is null");
            setClientState(clientReceiver, STATE_DISABLED | STATE_INTERNAL_ERROR,
                    /* binder= */ null);
            return;
        }
        final int taskId = activityPresentationInfo.taskId;
        final int displayId = activityPresentationInfo.displayId;
        final ComponentName componentName = activityPresentationInfo.componentName;
        final boolean whiteListed = mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId,
                componentName) || mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId,
                        componentName.getPackageName());
        final ComponentName serviceComponentName = getServiceComponentName();
        final boolean enabled = isEnabledLocked();
        if (mMaster.mRequestsHistory != null) {
            final String historyItem =
                    "id=" + sessionId + " uid=" + uid
                    + " a=" + ComponentName.flattenToShortString(componentName)
                    + " t=" + taskId + " d=" + displayId
                    + " s=" + ComponentName.flattenToShortString(serviceComponentName)
                    + " u=" + mUserId + " f=" + flags + (enabled ? "" : " (disabled)")
                    + " w=" + whiteListed;
            mMaster.mRequestsHistory.log(historyItem);
        }

        if (!enabled) {
            // TODO: it would be better to split in differet reasons, like
            // STATE_DISABLED_NO and STATE_DISABLED_BY_DEVICE_POLICY
            setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
                    /* binder= */ null);
            // Log metrics.
            writeSessionEvent(sessionId,
                    FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
                    STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName,
                    componentName, /* isChildSession= */ false);
            return;
        }
        if (serviceComponentName == null) {
            // TODO(b/111276913): this happens when the system service is starting, we should
            // probably handle it in a more elegant way (like waiting for boot_complete or
            // something like that
            if (mMaster.debug) {
                Slog.d(TAG, "startSession(" + activityToken + "): hold your horses");
            }
            return;
        }

        if (!whiteListed) {
            if (mMaster.debug) {
                Slog.d(TAG, "startSession(" + componentName + "): package or component "
                        + "not whitelisted");
            }
            setClientState(clientReceiver, STATE_DISABLED | STATE_NOT_WHITELISTED,
                    /* binder= */ null);
            // Log metrics.
            writeSessionEvent(sessionId,
                    FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
                    STATE_DISABLED | STATE_NOT_WHITELISTED, serviceComponentName,
                    componentName, /* isChildSession= */ false);
            return;
        }

        final ContentCaptureServerSession existingSession = mSessions.get(sessionId);
        if (existingSession != null) {
            Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
                    + ": ignoring because it already exists for " + existingSession.mActivityToken);
            setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID,
                    /* binder=*/ null);
            // Log metrics.
            writeSessionEvent(sessionId,
                    FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
                    STATE_DISABLED | STATE_DUPLICATED_ID,
                    serviceComponentName, componentName, /* isChildSession= */ false);
            return;
        }

        if (mRemoteService == null) {
            updateRemoteServiceLocked(/* disabled= */ false); // already checked for isEnabled
        }

        if (mRemoteService == null) {
            Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
                    + ": ignoring because service is not set");
            setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
                    /* binder= */ null);
            // Log metrics.
            writeSessionEvent(sessionId,
                    FrameworkStatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
                    STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName,
                    componentName, /* isChildSession= */ false);
            return;
        }

        // Make sure service is bound, just in case the initial connection failed somehow
        mRemoteService.ensureBoundLocked();

        final ContentCaptureServerSession newSession = new ContentCaptureServerSession(mLock,
                activityToken, this, componentName, clientReceiver, taskId, displayId, sessionId,
                uid, flags);
        if (mMaster.verbose) {
            Slog.v(TAG, "startSession(): new session for "
                    + ComponentName.flattenToShortString(componentName) + " and id " + sessionId);
        }
        mSessions.put(sessionId, newSession);
        newSession.notifySessionStartedLocked(clientReceiver);
    }

    @GuardedBy("mLock")
    public void finishSessionLocked(int sessionId) {
        if (!isEnabledLocked()) {
            return;
        }

        final ContentCaptureServerSession session = mSessions.get(sessionId);
        if (session == null) {
            if (mMaster.debug) {
                Slog.d(TAG, "finishSession(): no session with id" + sessionId);
            }
            return;
        }
        if (mMaster.verbose) Slog.v(TAG, "finishSession(): id=" + sessionId);
        session.removeSelfLocked(/* notifyRemoteService= */ true);
    }

    @GuardedBy("mLock")
    public void removeDataLocked(@NonNull DataRemovalRequest request) {
        if (!isEnabledLocked()) {
            return;
        }
        assertCallerLocked(request.getPackageName());
        mRemoteService.onDataRemovalRequest(request);
    }

    @GuardedBy("mLock")
    public void onDataSharedLocked(@NonNull DataShareRequest request,
            IDataShareCallback.Stub dataShareCallback) {
        if (!isEnabledLocked()) {
            return;
        }
        assertCallerLocked(request.getPackageName());
        mRemoteService.onDataShareRequest(request, dataShareCallback);
    }

    @GuardedBy("mLock")
    @Nullable
    public ComponentName getServiceSettingsActivityLocked() {
        if (mInfo == null) return null;

        final String activityName = mInfo.getSettingsActivity();
        if (activityName == null) return null;

        final String packageName = mInfo.getServiceInfo().packageName;
        return new ComponentName(packageName, activityName);
    }

    /**
     * Asserts the component is owned by the caller.
     */
    @GuardedBy("mLock")
    private void assertCallerLocked(@NonNull String packageName) {
        final PackageManager pm = getContext().getPackageManager();
        final int callingUid = Binder.getCallingUid();
        final int packageUid;
        try {
            packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
        } catch (NameNotFoundException e) {
            throw new SecurityException("Could not verify UID for " + packageName);
        }
        if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
                .hasRunningActivity(callingUid, packageName)) {
            final String[] packages = pm.getPackagesForUid(callingUid);
            final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
            Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
                    + ") passed package (" + packageName + ") owned by UID " + packageUid);

            throw new SecurityException("Invalid package: " + packageName);
        }
    }

    @GuardedBy("mLock")
    public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
            @NonNull Bundle data) {
        final int id = getSessionId(activityToken);
        final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
        final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
        final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT);
        final SnapshotData snapshotData = new SnapshotData(assistData,
                assistStructure, assistContent);
        if (id != NO_SESSION_ID) {
            final ContentCaptureServerSession session = mSessions.get(id);
            session.sendActivitySnapshotLocked(snapshotData);
            return true;
        }

        // We want to send an activity snapshot regardless of whether a content capture session is
        // present or not since a content capture session is not required for this functionality
        if (mRemoteService != null) {
            mRemoteService.onActivitySnapshotRequest(NO_SESSION_ID, snapshotData);
            Slog.d(TAG, "Notified activity assist data for activity: "
                    + activityToken + " without a session Id");
            return true;
        }

        return false;
    }

    @GuardedBy("mLock")
    public void removeSessionLocked(int sessionId) {
        mSessions.remove(sessionId);
    }

    @GuardedBy("mLock")
    public boolean isContentCaptureServiceForUserLocked(int uid) {
        return uid == getServiceUidLocked();
    }

    @GuardedBy("mLock")
    private ContentCaptureServerSession getSession(@NonNull IBinder activityToken) {
        for (int i = 0; i < mSessions.size(); i++) {
            final ContentCaptureServerSession session = mSessions.valueAt(i);
            if (session.mActivityToken.equals(activityToken)) {
                return session;
            }
        }
        return null;
    }

    /**
     * Destroys the service and all state associated with it.
     *
     * <p>Called when the service was disabled (for example, if the settings change).
     */
    @GuardedBy("mLock")
    public void destroyLocked() {
        if (mMaster.debug) Slog.d(TAG, "destroyLocked()");
        if (mRemoteService != null) {
            mRemoteService.destroy();
        }
        destroySessionsLocked();
    }

    @GuardedBy("mLock")
    void destroySessionsLocked() {
        final int numSessions = mSessions.size();
        for (int i = 0; i < numSessions; i++) {
            final ContentCaptureServerSession session = mSessions.valueAt(i);
            session.destroyLocked(/* notifyRemoteService= */ true);
        }
        mSessions.clear();
    }

    @GuardedBy("mLock")
    void listSessionsLocked(ArrayList<String> output) {
        final int numSessions = mSessions.size();
        for (int i = 0; i < numSessions; i++) {
            final ContentCaptureServerSession session = mSessions.valueAt(i);
            output.add(session.toShortString());
        }
    }

    @GuardedBy("mLock")
    @Nullable
    ArraySet<ContentCaptureCondition> getContentCaptureConditionsLocked(
            @NonNull String packageName) {
        return mConditionsByPkg.get(packageName);
    }

    @GuardedBy("mLock")
    void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) {
        if (mRemoteService == null) {
            if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
            return;
        }
        final ActivityEvent event = new ActivityEvent(componentName, type);

        if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event);

        mRemoteService.onActivityLifecycleEvent(event);
    }

    @Override
    protected void dumpLocked(String prefix, PrintWriter pw) {
        super.dumpLocked(prefix, pw);

        final String prefix2 = prefix + "  ";
        pw.print(prefix); pw.print("Service Info: ");
        if (mInfo == null) {
            pw.println("N/A");
        } else {
            pw.println();
            mInfo.dump(prefix2, pw);
        }
        pw.print(prefix); pw.print("Zombie: "); pw.println(mZombie);

        if (mRemoteService != null) {
            pw.print(prefix); pw.println("remote service:");
            mRemoteService.dump(prefix2, pw);
        }

        if (mSessions.size() == 0) {
            pw.print(prefix); pw.println("no sessions");
        } else {
            final int sessionsSize = mSessions.size();
            pw.print(prefix); pw.print("number sessions: "); pw.println(sessionsSize);
            for (int i = 0; i < sessionsSize; i++) {
                pw.print(prefix); pw.print("#"); pw.println(i);
                final ContentCaptureServerSession session = mSessions.valueAt(i);
                session.dumpLocked(prefix2, pw);
                pw.println();
            }
        }
    }

    /**
     * Returns the session id associated with the given activity.
     */
    @GuardedBy("mLock")
    private int getSessionId(@NonNull IBinder activityToken) {
        for (int i = 0; i < mSessions.size(); i++) {
            ContentCaptureServerSession session = mSessions.valueAt(i);
            if (session.isActivitySession(activityToken)) {
                return mSessions.keyAt(i);
            }
        }
        return NO_SESSION_ID;
    }

    /**
     * Resets the content capture whitelist.
     */
    @GuardedBy("mLock")
    private void resetContentCaptureWhitelistLocked() {
        if (mMaster.verbose) {
            Slog.v(TAG, "resetting content capture whitelist");
        }
        mMaster.mGlobalContentCaptureOptions.resetWhitelist(mUserId);
    }

    private final class ContentCaptureServiceRemoteCallback extends
            IContentCaptureServiceCallback.Stub {

        @Override
        public void setContentCaptureWhitelist(List<String> packages,
                List<ComponentName> activities) {
            // TODO(b/122595322): add CTS test for when it's null
            if (mMaster.verbose) {
                Slog.v(TAG, "setContentCaptureWhitelist(" + (packages == null
                        ? "null_packages" : packages.size() + " packages")
                        + ", " + (activities == null
                        ? "null_activities" : activities.size() + " activities") + ")"
                        + " for user " + mUserId);
            }
            mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
            writeSetWhitelistEvent(getServiceComponentName(), packages, activities);

            // Must disable session that are not the whitelist anymore...
            final int numSessions = mSessions.size();
            if (numSessions <= 0) return;

            // ...but without holding the lock on mGlobalContentCaptureOptions
            final SparseBooleanArray blacklistedSessions = new SparseBooleanArray(numSessions);

            for (int i = 0; i < numSessions; i++) {
                final ContentCaptureServerSession session = mSessions.valueAt(i);
                final boolean whitelisted = mMaster.mGlobalContentCaptureOptions
                        .isWhitelisted(mUserId, session.appComponentName);
                if (!whitelisted) {
                    final int sessionId = mSessions.keyAt(i);
                    if (mMaster.debug) {
                        Slog.d(TAG, "marking session " + sessionId + " (" + session.appComponentName
                                + ") for un-whitelisting");
                    }
                    blacklistedSessions.append(sessionId, true);
                }
            }
            final int numBlacklisted = blacklistedSessions.size();

            if (numBlacklisted <= 0) return;

            synchronized (mLock) {
                for (int i = 0; i < numBlacklisted; i++) {
                    final int sessionId = blacklistedSessions.keyAt(i);
                    if (mMaster.debug) Slog.d(TAG, "un-whitelisting " + sessionId);
                    final ContentCaptureServerSession session = mSessions.get(sessionId);
                    session.setContentCaptureEnabledLocked(false);
                }
            }
        }

        @Override
        public void setContentCaptureConditions(String packageName,
                List<ContentCaptureCondition> conditions) {
            if (mMaster.verbose) {
                Slog.v(TAG, "setContentCaptureConditions(" + packageName + "): "
                        + (conditions == null ? "null" : conditions.size() + " conditions"));
            }
            synchronized (mLock) {
                if (conditions == null) {
                    mConditionsByPkg.remove(packageName);
                } else {
                    mConditionsByPkg.put(packageName, new ArraySet<>(conditions));
                }
            }
        }

        @Override
        public void disableSelf() {
            if (mMaster.verbose) Slog.v(TAG, "disableSelf()");

            final long token = Binder.clearCallingIdentity();
            try {
                Settings.Secure.putStringForUser(getContext().getContentResolver(),
                        Settings.Secure.CONTENT_CAPTURE_ENABLED, "0", mUserId);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
            writeServiceEvent(FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__SET_DISABLED,
                    getServiceComponentName());
        }

        @Override
        public void writeSessionFlush(int sessionId, ComponentName app, FlushMetrics flushMetrics,
                ContentCaptureOptions options, int flushReason) {
            ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app,
                    flushMetrics, options, flushReason);
        }
    }
}
