BoolNodes.java

/*
 * Copyright 2022 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 androidx.wear.protolayout.expression.pipeline;

import android.util.Log;

import androidx.annotation.UiThread;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
import androidx.wear.protolayout.expression.proto.DynamicProto;
import androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonFloatOp;
import androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonInt32Op;
import androidx.wear.protolayout.expression.proto.DynamicProto.StateBoolSource;
import androidx.wear.protolayout.expression.proto.FixedProto.FixedBool;

/** Dynamic data nodes which yield boleans. */
class BoolNodes {
    private BoolNodes() {}

    /** Dynamic boolean node that has a fixed value. */
    static class FixedBoolNode implements DynamicDataSourceNode<Boolean> {
        private final boolean mValue;
        private final DynamicTypeValueReceiverWithPreUpdate<Boolean> mDownstream;

        FixedBoolNode(
                FixedBool protoNode, DynamicTypeValueReceiverWithPreUpdate<Boolean> downstream) {
            mValue = protoNode.getValue();
            mDownstream = downstream;
        }

        @Override
        @UiThread
        public void preInit() {
            mDownstream.onPreUpdate();
        }

        @Override
        @UiThread
        public void init() {
            mDownstream.onData(mValue);
        }

        @Override
        @UiThread
        public void destroy() {}
    }

    /** Dynamic boolean node that gets value from the state. */
    static class StateBoolNode extends StateSourceNode<Boolean> {
        StateBoolNode(
                StateStore stateStore,
                StateBoolSource protoNode,
                DynamicTypeValueReceiverWithPreUpdate<Boolean> downstream) {
            super(
                    stateStore,
                    StateSourceNode.<DynamicBool>createKey(
                            protoNode.getSourceNamespace(), protoNode.getSourceKey()),
                    se -> se.getBoolVal().getValue(),
                    downstream);
        }
    }

    /** Dynamic boolean node that gets value from comparing two integers. */
    static class ComparisonInt32Node extends DynamicDataBiTransformNode<Integer, Integer, Boolean> {
        private static final String TAG = "ComparisonInt32Node";

        ComparisonInt32Node(
                ComparisonInt32Op protoNode,
                DynamicTypeValueReceiverWithPreUpdate<Boolean> downstream) {
            super(
                    downstream,
                    (lhs, rhs) -> {
                        int unboxedLhs = lhs;
                        int unboxedRhs = rhs;

                        switch (protoNode.getOperationType()) {
                            case COMPARISON_OP_TYPE_EQUALS:
                                return unboxedLhs == unboxedRhs;
                            case COMPARISON_OP_TYPE_NOT_EQUALS:
                                return unboxedLhs != unboxedRhs;
                            case COMPARISON_OP_TYPE_LESS_THAN:
                                return unboxedLhs < unboxedRhs;
                            case COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO:
                                return unboxedLhs <= unboxedRhs;
                            case COMPARISON_OP_TYPE_GREATER_THAN:
                                return unboxedLhs > unboxedRhs;
                            case COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO:
                                return unboxedLhs >= unboxedRhs;
                            default:
                                Log.e(TAG, "Unknown operation type in ComparisonInt32Node");
                                return false;
                        }
                    });
        }
    }

    /** Dynamic boolean node that gets value from comparing two floats. */
    static class ComparisonFloatNode extends DynamicDataBiTransformNode<Float, Float, Boolean> {
        private static final String TAG = "ComparisonFloatNode";
        public static final float EPSILON = 1e-6f;

        ComparisonFloatNode(
                ComparisonFloatOp protoNode,
                DynamicTypeValueReceiverWithPreUpdate<Boolean> downstream) {
            super(
                    downstream,
                    (lhs, rhs) -> {
                        float unboxedLhs = lhs;
                        float unboxedRhs = rhs;

                        switch (protoNode.getOperationType()) {
                            case COMPARISON_OP_TYPE_EQUALS:
                                return equalFloats(unboxedLhs, unboxedRhs);
                            case COMPARISON_OP_TYPE_NOT_EQUALS:
                                return !equalFloats(unboxedLhs, unboxedRhs);
                            case COMPARISON_OP_TYPE_LESS_THAN:
                                return (unboxedLhs < unboxedRhs)
                                        && !equalFloats(unboxedLhs, unboxedRhs);
                            case COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO:
                                return (unboxedLhs < unboxedRhs)
                                        || equalFloats(unboxedLhs, unboxedRhs);
                            case COMPARISON_OP_TYPE_GREATER_THAN:
                                return (unboxedLhs > unboxedRhs)
                                        && !equalFloats(unboxedLhs, unboxedRhs);
                            case COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO:
                                return (unboxedLhs > unboxedRhs)
                                        || equalFloats(unboxedLhs, unboxedRhs);
                            default:
                                Log.e(TAG, "Unknown operation type in ComparisonInt32Node");
                                return false;
                        }
                    });
        }

        private static boolean equalFloats(float lhs, float rhs) {
            return Math.abs(lhs - rhs) < EPSILON;
        }
    }

    /** Dynamic boolean node that gets opposite value from another boolean node. */
    static class NotBoolOp extends DynamicDataTransformNode<Boolean, Boolean> {
        NotBoolOp(DynamicTypeValueReceiverWithPreUpdate<Boolean> downstream) {
            super(downstream, b -> !b);
        }
    }

    /** Dynamic boolean node that gets value from logical operation. */
    static class LogicalBoolOp extends DynamicDataBiTransformNode<Boolean, Boolean, Boolean> {
        private static final String TAG = "LogicalBooleanOp";

        LogicalBoolOp(
                DynamicProto.LogicalBoolOp protoNode,
                DynamicTypeValueReceiverWithPreUpdate<Boolean> downstream) {
            super(
                    downstream,
                    (a, b) -> {
                        switch (protoNode.getOperationType()) {
                            case LOGICAL_OP_TYPE_AND:
                                return a && b;
                            case LOGICAL_OP_TYPE_OR:
                                return a || b;
                            case LOGICAL_OP_TYPE_EQUAL:
                                return a.equals(b);
                            case LOGICAL_OP_TYPE_NOT_EQUAL:
                                return !a.equals(b);
                            default:
                                Log.e(TAG, "Unknown operation type in LogicalBoolOp");
                                return false;
                        }
                    });
        }
    }
}