/*
 * 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.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;

import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent;
import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
import android.service.contentcapture.ActivityEvent;
import android.service.contentcapture.IContentCaptureService;
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.Slog;
import android.util.StatsLog;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.DataRemovalRequest;

import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.os.IResultReceiver;

final class RemoteContentCaptureService
        extends AbstractMultiplePendingRequestsRemoteService<RemoteContentCaptureService,
        IContentCaptureService> {

    private final IBinder mServerCallback;
    private final int mIdleUnbindTimeoutMs;
    private final ContentCapturePerUserService mPerUserService;

    RemoteContentCaptureService(Context context, String serviceInterface,
            ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId,
            ContentCapturePerUserService perUserService, boolean bindInstantServiceAllowed,
            boolean verbose, int idleUnbindTimeoutMs) {
        super(context, serviceInterface, serviceComponentName, userId, perUserService,
                context.getMainThreadHandler(),
                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, verbose,
                /* initialCapacity= */ 2);
        mPerUserService = perUserService;
        mServerCallback = callback.asBinder();
        mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;

        // Bind right away, which will trigger a onConnected() on service's
        ensureBoundLocked();
    }

    @Override // from AbstractRemoteService
    protected IContentCaptureService getServiceInterface(@NonNull IBinder service) {
        return IContentCaptureService.Stub.asInterface(service);
    }

    @Override // from AbstractRemoteService
    protected long getTimeoutIdleBindMillis() {
        return mIdleUnbindTimeoutMs;
    }

    @Override // from AbstractRemoteService
    protected void handleOnConnectedStateChanged(boolean connected) {
        if (connected && getTimeoutIdleBindMillis() != PERMANENT_BOUND_TIMEOUT_MS) {
            scheduleUnbind();
        }
        try {
            if (connected) {
                try {
                    mService.onConnected(mServerCallback, sVerbose, sDebug);
                    writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED,
                            mComponentName);
                } finally {
                    // Update the system-service state, in case the service reconnected after
                    // dying
                    mPerUserService.onConnected();
                }
            } else {
                mService.onDisconnected();
                writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED,
                        mComponentName);
            }
        } catch (Exception e) {
            Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e);
        }
    }

    public void ensureBoundLocked() {
        scheduleBind();
    }

    /**
     * Called by {@link ContentCaptureServerSession} to generate a call to the
     * {@link RemoteContentCaptureService} to indicate the session was created.
     */
    public void onSessionStarted(@Nullable ContentCaptureContext context, int sessionId, int uid,
            @NonNull IResultReceiver clientReceiver, int initialState) {
        scheduleAsyncRequest(
                (s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver, initialState));
        // Metrics logging.
        writeSessionEvent(sessionId,
                StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__ON_SESSION_STARTED, initialState,
                getComponentName(), context.getActivityComponent(), /* is_child_session= */ false);
    }

    /**
     * Called by {@link ContentCaptureServerSession} to generate a call to the
     * {@link RemoteContentCaptureService} to indicate the session was finished.
     */
    public void onSessionFinished(int sessionId) {
        scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId));
        // Metrics logging.
        writeSessionEvent(sessionId,
                StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__ON_SESSION_FINISHED,
                /* flags= */ 0, getComponentName(), /* app= */ null,
                /* is_child_session= */ false);
    }

    /**
     * Called by {@link ContentCaptureServerSession} to send snapshot data to the service.
     */
    public void onActivitySnapshotRequest(int sessionId, @NonNull SnapshotData snapshotData) {
        scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
    }

    /**
     * Called by {@link ContentCaptureServerSession} to request removal of content capture data.
     */
    public void onDataRemovalRequest(@NonNull DataRemovalRequest request) {
        scheduleAsyncRequest((s) -> s.onDataRemovalRequest(request));
        writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_USER_DATA_REMOVED,
                mComponentName);
    }

    /**
     * Called by {@link ContentCaptureServerSession} to notify a high-level activity event.
     */
    public void onActivityLifecycleEvent(@NonNull ActivityEvent event) {
        scheduleAsyncRequest((s) -> s.onActivityEvent(event));
    }

    public interface ContentCaptureServiceCallbacks
            extends VultureCallback<RemoteContentCaptureService> {
        // NOTE: so far we don't need to notify the callback implementation
        // (ContentCaptureServerSession) of the request results (success, timeouts, etc..), so this
        // callback interface is empty.
    }
}
