/*
 * 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.server.rotationresolver;

import static android.provider.DeviceConfig.NAMESPACE_ROTATION_RESOLVER;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_CANCELLED;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_NOT_SUPPORTED;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_PREEMPTED;
import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_TIMED_OUT;

import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_0;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_180;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_270;
import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_90;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.rotationresolver.RotationResolverInternal;
import android.service.rotationresolver.RotationResolutionRequest;
import android.service.rotationresolver.RotationResolverService;
import android.text.TextUtils;
import android.util.Slog;
import android.view.Surface;

import com.android.internal.R;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.SystemService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.Set;

/**
 * A manager service for rotation resolver service that runs in System Server process.
 * This service publishes a LocalService and reroutes calls to a {@link
 * android.service.rotationresolver.RotationResolverService} that it manages.
 */
public class RotationResolverManagerService extends
        AbstractMasterSystemService<RotationResolverManagerService,
                RotationResolverManagerPerUserService> {

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

    private static final String KEY_SERVICE_ENABLED = "service_enabled";

    /** Default value in absence of {@link DeviceConfig} override. */
    private static final boolean DEFAULT_SERVICE_ENABLED = true;

    static final int ORIENTATION_UNKNOWN =
            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNKNOWN;
    static final int RESOLUTION_DISABLED =
            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__DISABLED;
    static final int RESOLUTION_UNAVAILABLE =
            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNAVAILABLE;
    static final int RESOLUTION_FAILURE =
            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;

    private final Context mContext;
    private final SensorPrivacyManager mPrivacyManager;
    boolean mIsServiceEnabled;

    public RotationResolverManagerService(Context context) {
        super(context,
                new FrameworkResourcesServiceNameResolver(
                        context,
                        R.string.config_defaultRotationResolverService), /*disallowProperty=*/null,
                PACKAGE_UPDATE_POLICY_REFRESH_EAGER
                        | /*To avoid high rotation latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
        mContext = context;
        mPrivacyManager = SensorPrivacyManager.getInstance(context);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ROTATION_RESOLVER,
                    getContext().getMainExecutor(),
                    (properties) -> onDeviceConfigChange(properties.getKeyset()));

            mIsServiceEnabled = DeviceConfig.getBoolean(NAMESPACE_ROTATION_RESOLVER,
                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
        }
    }

    private void onDeviceConfigChange(@NonNull Set<String> keys) {
        if (keys.contains(KEY_SERVICE_ENABLED)) {
            mIsServiceEnabled = DeviceConfig.getBoolean(NAMESPACE_ROTATION_RESOLVER,
                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
        }
    }

    @Override
    public void onStart() {
        publishBinderService(Context.ROTATION_RESOLVER_SERVICE, new BinderService());
        publishLocalService(RotationResolverInternal.class, new LocalService());
    }

    @Override
    public RotationResolverManagerPerUserService newServiceLocked(
            @UserIdInt int resolvedUserId,
            @SuppressWarnings("unused") boolean disabled) {
        return new RotationResolverManagerPerUserService(this, mLock, resolvedUserId);
    }

    @Override
    protected void onServiceRemoved(
            RotationResolverManagerPerUserService service, @UserIdInt int userId) {
        synchronized (mLock) {
            service.destroyLocked();
        }
    }

    /** Returns {@code true} if rotation resolver service is configured on this device. */
    public static boolean isServiceConfigured(Context context) {
        return !TextUtils.isEmpty(getServiceConfigPackage(context));
    }

    ComponentName getComponentNameShellCommand(@UserIdInt int userId) {
        synchronized (mLock) {
            final RotationResolverManagerPerUserService service = getServiceForUserLocked(userId);
            if (service != null) {
                return service.getComponentName();
            }
        }
        return null;
    }

    void resolveRotationShellCommand(@UserIdInt int userId,
            RotationResolverInternal.RotationResolverCallbackInternal callbackInternal,
            RotationResolutionRequest request) {
        synchronized (mLock) {
            final RotationResolverManagerPerUserService service = getServiceForUserLocked(userId);
            if (service != null) {
                service.resolveRotationLocked(callbackInternal, request, new CancellationSignal());
            } else {
                Slog.i(TAG, "service not available for user_id: " + userId);
            }
        }
    }


    static String getServiceConfigPackage(Context context) {
        return context.getPackageManager().getRotationResolverPackageName();
    }

    private final class LocalService extends RotationResolverInternal {
        @Override
        public boolean isRotationResolverSupported() {
            synchronized (mLock) {
                return mIsServiceEnabled;
            }
        }

        @Override
        public void resolveRotation(
                @NonNull RotationResolverCallbackInternal callbackInternal, String packageName,
                int proposedRotation, int currentRotation, long timeout,
                @NonNull CancellationSignal cancellationSignalInternal) {
            Objects.requireNonNull(callbackInternal);
            Objects.requireNonNull(cancellationSignalInternal);
            synchronized (mLock) {
                final boolean isCameraAvailable = !mPrivacyManager.isSensorPrivacyEnabled(
                        SensorPrivacyManager.Sensors.CAMERA);
                if (mIsServiceEnabled && isCameraAvailable) {
                    final RotationResolverManagerPerUserService service =
                            getServiceForUserLocked(UserHandle.USER_CURRENT);
                    final RotationResolutionRequest request;
                    if (packageName == null) {
                        request = new RotationResolutionRequest(/* packageName */ "",
                                currentRotation, proposedRotation, /* shouldUseCamera */ true,
                                timeout);
                    } else {
                        request = new RotationResolutionRequest(packageName, currentRotation,
                                proposedRotation, /* shouldUseCamera */ true, timeout);
                    }

                    service.resolveRotationLocked(callbackInternal, request,
                            cancellationSignalInternal);
                } else {
                    if (isCameraAvailable) {
                        Slog.w(TAG, "Rotation Resolver service is disabled.");
                    } else {
                        Slog.w(TAG, "Camera is locked by a toggle.");
                    }
                    callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
                    logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
                }
            }
        }
    }

    private final class BinderService extends Binder {
        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
                return;
            }
            synchronized (mLock) {
                dumpLocked("", pw);
            }
        }

        @Override
        public void onShellCommand(FileDescriptor in, FileDescriptor out,
                FileDescriptor err,
                String[] args, ShellCallback callback,
                ResultReceiver resultReceiver) {
            mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROTATION_RESOLVER,
                    TAG);
            new RotationResolverShellCommand(RotationResolverManagerService.this).exec(this, in,
                    out, err, args, callback,
                    resultReceiver);
        }
    }

    static void logRotationStatsWithTimeToCalculate(int proposedRotation, int currentRotation,
            int result, long timeToCalculate) {
        FrameworkStatsLog.write(FrameworkStatsLog.AUTO_ROTATE_REPORTED,
                /* previous_orientation= */ surfaceRotationToProto(currentRotation),
                /* proposed_orientation= */ surfaceRotationToProto(proposedRotation),
                result,
                /* process_duration_millis= */ timeToCalculate);
    }

    static void logRotationStats(int proposedRotation, int currentRotation,
            int result) {
        FrameworkStatsLog.write(FrameworkStatsLog.AUTO_ROTATE_REPORTED,
                /* previous_orientation= */ surfaceRotationToProto(currentRotation),
                /* proposed_orientation= */ surfaceRotationToProto(proposedRotation),
                result);
    }

    static int errorCodeToProto(@RotationResolverService.FailureCodes int error) {
        switch (error) {
            case ROTATION_RESULT_FAILURE_NOT_SUPPORTED:
                return RESOLUTION_UNAVAILABLE;
            case ROTATION_RESULT_FAILURE_TIMED_OUT:
            case ROTATION_RESULT_FAILURE_PREEMPTED:
            case ROTATION_RESULT_FAILURE_CANCELLED:
                return ORIENTATION_UNKNOWN;
            default:
                return RESOLUTION_FAILURE;
        }
    }

    static int surfaceRotationToProto(@Surface.Rotation int rotationPoseResult) {
        switch (rotationPoseResult) {
            case Surface.ROTATION_0:
                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_0;
            case Surface.ROTATION_90:
                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_90;
            case Surface.ROTATION_180:
                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_180;
            case Surface.ROTATION_270:
                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_270;
            default:
                // Should not reach here.
                return RESOLUTION_FAILURE;
        }
    }
}
