/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.hardware.camera2.extension;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.AppOpsManager;
import android.app.Service;
import android.content.Intent;
import android.hardware.camera2.CameraExtensionCharacteristics.Extension;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.camera.flags.Flags;

interface CameraUsageTracker {
    void startCameraOperation();
    void finishCameraOperation();
}

/**
 * Base service class that extension service implementations must extend.
 *
 * @hide
 */
@SystemApi
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract class CameraExtensionService extends Service {
    private static final String TAG = "CameraExtensionService";
    private CameraUsageTracker mCameraUsageTracker;
    private static Object mLock = new Object();

    private final class CameraTracker implements CameraUsageTracker {

        private final AppOpsManager mAppOpsService = getApplicationContext().getSystemService(
                AppOpsManager.class);
        private final String mPackageName = getPackageName();
        private final String mAttributionTag = getAttributionTag();
        private int mUid = getApplicationInfo().uid;

        @Override
        public void startCameraOperation() {
            if (mAppOpsService != null) {
                mAppOpsService.startOp(AppOpsManager.OPSTR_CAMERA, mUid, mPackageName,
                        mAttributionTag, "Camera extensions");
            }
        }

        @Override
        public void finishCameraOperation() {
            if (mAppOpsService != null) {
                mAppOpsService.finishOp(AppOpsManager.OPSTR_CAMERA, mUid, mPackageName,
                        mAttributionTag);
            }
        }
    }
    @GuardedBy("mLock")
    private static IInitializeSessionCallback mInitializeCb = null;

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            synchronized (mLock) {
                mInitializeCb = null;
            }
            if (mCameraUsageTracker != null) {
                mCameraUsageTracker.finishCameraOperation();
            }
        }
    };

    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
    protected CameraExtensionService() { }

    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
    @Override
    @NonNull
    public final IBinder onBind(@Nullable Intent intent) {
        if (mCameraUsageTracker == null) {
            mCameraUsageTracker = new CameraTracker();
        }
        return new CameraExtensionServiceImpl();
    }

    private class CameraExtensionServiceImpl extends ICameraExtensionsProxyService.Stub {
        @Override
        public boolean registerClient(IBinder token) throws RemoteException {
            return CameraExtensionService.this.onRegisterClient(token);
        }

        @Override
        public void unregisterClient(IBinder token) throws RemoteException {
            CameraExtensionService.this.onUnregisterClient(token);
        }

        @Override
        public boolean advancedExtensionsSupported() throws RemoteException {
            return true;
        }

        @Override
        public void initializeSession(IInitializeSessionCallback cb) {
            boolean ret = false;
            synchronized (mLock) {
                if (mInitializeCb == null) {
                    mInitializeCb = cb;
                    try {
                        mInitializeCb.asBinder().linkToDeath(mDeathRecipient, 0);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Failure to register binder death notifier!");
                    }
                    ret = true;
                }
            }

            try {
                if (ret) {
                    cb.onSuccess();
                } else {
                    cb.onFailure();
                }
            } catch (RemoteException e) {

                Log.e(TAG, "Client doesn't respond!");
            }
        }

        @Override
        public void releaseSession() {
            synchronized (mLock) {
                if (mInitializeCb != null) {
                    mInitializeCb.asBinder().unlinkToDeath(mDeathRecipient, 0);
                    mInitializeCb = null;
                }
            }
        }

        @Override
        public IPreviewExtenderImpl initializePreviewExtension(@Extension int extensionType)
                throws RemoteException {
            // Basic Extension API is not supported
            return null;
        }

        @Override
        public IImageCaptureExtenderImpl initializeImageExtension(@Extension int extensionType)
                throws RemoteException {
            // Basic Extension API is not supported
            return null;
        }

        @Override
        public IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType)
                throws RemoteException {
            AdvancedExtender extender =  CameraExtensionService.this.onInitializeAdvancedExtension(
                    extensionType);
            extender.setCameraUsageTracker(mCameraUsageTracker);
            return extender.getAdvancedExtenderBinder();
        }
    }

    /**
     * Register an extension client. The client must call this method
     * after successfully binding to the service.
     *
     * @param token              Binder token that can be used for adding
     *                           death notifier in case the client exits
     *                           unexpectedly.
     * @return true if the registration is successful, false otherwise
     */
    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
    public abstract boolean onRegisterClient(@NonNull IBinder token);

    /**
     * Unregister an extension client.
     *
     * @param token              Binder token
     */
    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
    public abstract void onUnregisterClient(@NonNull IBinder token);

    /**
     * Initialize and return an advanced extension.
     *
     * @param extensionType {@link android.hardware.camera2.CameraExtensionCharacteristics}
     *                      extension type
     * @return Valid advanced extender of the requested type
     */
    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
    @NonNull
    public abstract AdvancedExtender onInitializeAdvancedExtension(@Extension int extensionType);
}
