for (Iterator<Unit> i = containerUnits.iterator(containerUnits.getSuccOf(toInline), containerUnits.getPredOf(exitPoint)); i.hasNext(); ) {
            newStmts.add(i.next());
        }

package soot.jimple.toolkits.invoke;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * %%
 * #L%
 */
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import soot.Body;
import soot.Local;
import soot.PhaseOptions;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.Trap;
import soot.TrapManager;
import soot.Unit;
import soot.UnitBox;
import soot.Value;
import soot.ValueBox;
import soot.jimple.AssignStmt;
import soot.jimple.CaughtExceptionRef;
import soot.jimple.IdentityRef;
import soot.jimple.IdentityStmt;
import soot.jimple.IfStmt;
import soot.jimple.InstanceInvokeExpr;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.NullConstant;
import soot.jimple.ParameterRef;
import soot.jimple.ReturnStmt;
import soot.jimple.ReturnVoidStmt;
import soot.jimple.Stmt;
import soot.jimple.ThisRef;
import soot.jimple.toolkits.scalar.LocalNameStandardizer;
import soot.tagkit.Host;
import soot.util.Chain;

/**
 * Provides methods to inline a given invoke site.
 */
public class SiteInliner {

    public String getDefaultOptions() {
        return "insert-null-checks insert-redundant-casts";
    }

    /**
     * Iterates over a list of sites, inlining them in order. Each site is given as a 3-element list (inlinee, toInline,
     * container).
     */
    public static void inlineSites(List<List<Host>> sites) {
        inlineSites(sites, Collections.emptyMap());
    }

    /**
     * Iterates over a list of sites, inlining them in order. Each site is given as a 3-element list (inlinee, toInline,
     * container).
     */
    public static void inlineSites(List<List<Host>> sites, Map<String, String> options) {
        for (List<Host> l : sites) {
            assert (l.size() == 3);
            SootMethod inlinee = (SootMethod) l.get(0);
            Stmt toInline = (Stmt) l.get(1);
            SootMethod container = (SootMethod) l.get(2);
            inlineSite(inlinee, toInline, container, options);
        }
    }

    /**
     * Inlines the method inlinee into the container at the point toInline.
     */
    public static List<Unit> inlineSite(SootMethod inlinee, Stmt toInline, SootMethod container) {
        return inlineSite(inlinee, toInline, container, Collections.emptyMap());
    }

    /**
     * Inlines the given site. Note that this method does not actually check if it's safe (with respect to access modifiers and
     * special invokes) for it to be inlined. That functionality is handled by the InlinerSafetyManager.
     */
    public static List<Unit> inlineSite(SootMethod inlinee, Stmt toInline, SootMethod container, Map<String, String> options) {
        final SootClass declaringClass = inlinee.getDeclaringClass();
        if (!declaringClass.isApplicationClass() && !declaringClass.isLibraryClass()) {
            return null;
        }
        final Body containerB = container.getActiveBody();
        final Chain<Unit> containerUnits = containerB.getUnits();
        assert (containerUnits.contains(toInline)) : toInline + " is not in body " + containerB;
        final InvokeExpr ie = toInline.getInvokeExpr();
        Value thisToAdd = (ie instanceof InstanceInvokeExpr) ? ((InstanceInvokeExpr) ie).getBase() : null;
        if (ie instanceof InstanceInvokeExpr) {
            // Insert casts to please the verifier.
            if (PhaseOptions.getBoolean(options, "insert-redundant-casts")) {
                // The verifier will complain if the argument passed to the method is not the correct type.
                // For instance, Bottle.price_static takes a cost.
                // Cost is an interface implemented by Bottle.
                Value base = ((InstanceInvokeExpr) ie).getBase();
                SootClass localType = ((RefType) base.getType()).getSootClass();
                if (localType.isInterface() || Scene.v().getActiveHierarchy().isClassSuperclassOf(localType, declaringClass)) {
                    final Jimple jimp = Jimple.v();
                    RefType type = declaringClass.getType();
                    Local castee = jimp.newLocal("__castee", type);
                    containerB.getLocals().add(castee);
                    containerUnits.insertBefore(jimp.newAssignStmt(castee, jimp.newCastExpr(base, type)), toInline);
                    thisToAdd = castee;
                }
            }
            // (If enabled), add a null pointer check.
            if (PhaseOptions.getBoolean(options, "insert-null-checks")) {
                final Jimple jimp = Jimple.v();
                /* Ah ha. Caught again! */
                if (TrapManager.isExceptionCaughtAt(Scene.v().getSootClass("java.lang.NullPointerException"), toInline, containerB)) {
                    // In this case, we don't use throwPoint. Instead, put the code right there.
                    IfStmt insertee = jimp.newIfStmt(jimp.newNeExpr(((InstanceInvokeExpr) ie).getBase(), NullConstant.v()), toInline);
                    containerUnits.insertBefore(insertee, toInline);
                    // This sucks (but less than before).
                    insertee.setTarget(toInline);
                    ThrowManager.addThrowAfter(containerB.getLocals(), containerUnits, insertee);
                } else {
                    containerUnits.insertBefore(jimp.newIfStmt(jimp.newEqExpr(((InstanceInvokeExpr) ie).getBase(), NullConstant.v()), ThrowManager.getNullPointerExceptionThrower(containerB)), toInline);
                }
            }
        }
        // Add synchronizing stuff.
        if (inlinee.isSynchronized()) {
            // Need to get the class object if ie is a static invoke.
            if (ie instanceof InstanceInvokeExpr) {
                Local base = (Local) ((InstanceInvokeExpr) ie).getBase();
                SynchronizerManager.v().synchronizeStmtOn(toInline, containerB, base);
            } else if (!container.getDeclaringClass().isInterface()) {
                // If we're in an interface, we must be in a <clinit> method,
                // which surely needs no synchronization.
                final SynchronizerManager mgr = SynchronizerManager.v();
                mgr.synchronizeStmtOn(toInline, containerB, mgr.addStmtsToFetchClassBefore(containerB, toInline));
            }
        }
        final Body inlineeB = inlinee.getActiveBody();
        final Chain<Unit> inlineeUnits = inlineeB.getUnits();
        final Unit exitPoint = containerUnits.getSuccOf(toInline);
        // First, clone all of the inlinee's units & locals.
        HashMap<Local, Local> oldLocalsToNew = new HashMap<Local, Local>();
        HashMap<Unit, Unit> oldUnitsToNew = new HashMap<Unit, Unit>();
        {
            Unit cursor = toInline;
            for (Unit u : inlineeUnits) {
                Unit currPrime = (Unit) u.clone();
                if (currPrime == null) {
                    throw new RuntimeException("getting null from clone!");
                }
                currPrime.addAllTagsOf(u);
                containerUnits.insertAfter(currPrime, cursor);
                oldUnitsToNew.put(u, currPrime);
                cursor = currPrime;
            }
            for (Local l : inlineeB.getLocals()) {
                Local lPrime = (Local) l.clone();
                if (lPrime == null) {
                    throw new RuntimeException("getting null from local clone!");
                }
                containerB.getLocals().add(lPrime);
                oldLocalsToNew.put(l, lPrime);
            }
        }
        // Backpatch the newly-inserted units using newly-constructed maps.
        for (Iterator<Unit> it = containerUnits.iterator(containerUnits.getSuccOf(toInline), containerUnits.getPredOf(exitPoint)); it.hasNext(); ) {
            Unit patchee = it.next();
            for (ValueBox box : patchee.getUseAndDefBoxes()) {
                Value value = box.getValue();
                if (value instanceof Local) {
                    Local lPrime = oldLocalsToNew.get((Local) value);
                    if (lPrime == null) {
                        throw new RuntimeException("local has no clone!");
                    }
                    box.setValue(lPrime);
                }
            }
            for (UnitBox box : patchee.getUnitBoxes()) {
                Unit uPrime = oldUnitsToNew.get(box.getUnit());
                if (uPrime == null) {
                    throw new RuntimeException("inlined stmt has no clone!");
                }
                box.setUnit(uPrime);
            }
        }
        // Copy & backpatch the traps; preserve their same order.
        {
            final Chain<Trap> traps = containerB.getTraps();
            Trap prevTrap = null;
            for (Trap t : inlineeB.getTraps()) {
                Unit newBegin = oldUnitsToNew.get(t.getBeginUnit());
                Unit newEnd = oldUnitsToNew.get(t.getEndUnit());
                Unit newHandler = oldUnitsToNew.get(t.getHandlerUnit());
                if (newBegin == null || newEnd == null || newHandler == null) {
                    throw new RuntimeException("couldn't map trap!");
                }
                Trap trap = Jimple.v().newTrap(t.getException(), newBegin, newEnd, newHandler);
                if (prevTrap == null) {
                    traps.addFirst(trap);
                } else {
                    traps.insertAfter(trap, prevTrap);
                }
                prevTrap = trap;
            }
        }
        // Handle identity stmt's and returns.
        {
            ArrayList<Unit> cuCopy = new ArrayList<Unit>();
            for (Iterator<Unit> it = containerUnits.iterator(containerUnits.getSuccOf(toInline), containerUnits.getPredOf(exitPoint)); it.hasNext(); ) {
                cuCopy.add(it.next());
            }
            for (Unit u : cuCopy) {
                if (u instanceof IdentityStmt) {
                    IdentityStmt idStmt = (IdentityStmt) u;
                    IdentityRef rhs = (IdentityRef) idStmt.getRightOp();
                    if (rhs instanceof CaughtExceptionRef) {
                        continue;
                    } else if (rhs instanceof ThisRef) {
                        if (!(ie instanceof InstanceInvokeExpr)) {
                            throw new RuntimeException("thisref with no receiver!");
                        }
                        containerUnits.swapWith(u, Jimple.v().newAssignStmt(idStmt.getLeftOp(), thisToAdd));
                    } else if (rhs instanceof ParameterRef) {
                        ParameterRef pref = (ParameterRef) rhs;
                        containerUnits.swapWith(u, Jimple.v().newAssignStmt(idStmt.getLeftOp(), ie.getArg(pref.getIndex())));
                    }
                } else if (u instanceof ReturnStmt) {
                    if (toInline instanceof InvokeStmt) {
                        containerUnits.swapWith(u, Jimple.v().newGotoStmt(exitPoint));
                    } else if (toInline instanceof AssignStmt) {
                        final Jimple jimp = Jimple.v();
                        AssignStmt as = jimp.newAssignStmt(((AssignStmt) toInline).getLeftOp(), ((ReturnStmt) u).getOp());
                        containerUnits.insertBefore(as, u);
                        containerUnits.swapWith(u, jimp.newGotoStmt(exitPoint));
                    } else {
                        throw new RuntimeException("invoking stmt neither InvokeStmt nor AssignStmt!??!?!");
                    }
                } else if (u instanceof ReturnVoidStmt) {
                    containerUnits.swapWith(u, Jimple.v().newGotoStmt(exitPoint));
                }
            }
        }
        List<Unit> newStmts = new ArrayList<Unit>();

        // Remove the original statement toInline.
        containerUnits.remove(toInline);
        // Resolve name collisions.
        LocalNameStandardizer.v().transform(containerB, "ji.lns");
        return newStmts;
    }
}


public Thing getThing() {
        return thing;
    }

package org.eclipse.ditto.things.model.signals.commands.modify;

import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
import org.eclipse.ditto.json.JsonFieldDefinition;
import org.eclipse.ditto.json.JsonMissingFieldException;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonObjectBuilder;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.base.model.common.Placeholders;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.FieldType;
import org.eclipse.ditto.base.model.json.JsonParsableCommand;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.policies.model.PolicyId;
import org.eclipse.ditto.things.model.Thing;
import org.eclipse.ditto.things.model.ThingId;
import org.eclipse.ditto.things.model.ThingsModelFactory;
import org.eclipse.ditto.base.model.signals.commands.AbstractCommand;
import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer;
import org.eclipse.ditto.things.model.signals.commands.ThingCommand;
import org.eclipse.ditto.things.model.signals.commands.ThingCommandSizeValidator;
import org.eclipse.ditto.things.model.signals.commands.exceptions.PoliciesConflictingException;

/**
 * This command modifies an existing Thing. It contains the full {@link org.eclipse.ditto.things.model.Thing} including the Thing ID which should be
 * used for modification.
 */
@JsonParsableCommand(typePrefix = ThingCommand.TYPE_PREFIX, name = ModifyThing.NAME)
public final class ModifyThing extends AbstractCommand<ModifyThing> implements ThingModifyCommand<ModifyThing> {

    /**
     * Name of the "Modify Thing" command.
     */
    public static final String NAME = "modifyThing";

    public static final String TYPE = ThingCommand.TYPE_PREFIX + NAME;

    static final JsonFieldDefinition<JsonObject> JSON_THING = JsonFactory.newJsonObjectFieldDefinition("thing", FieldType.REGULAR, JsonSchemaVersion.V_2);

    /**
     * Json Field definition for the optional initial "inline" policy when creating a Thing.
     */
    public static final JsonFieldDefinition<JsonObject> JSON_INITIAL_POLICY = JsonFactory.newJsonObjectFieldDefinition("initialPolicy", FieldType.REGULAR, JsonSchemaVersion.V_2);

    /**
     * Json Field definition for the optional feature to copy an existing policy.
     */
    public static final JsonFieldDefinition<String> JSON_POLICY_ID_OR_PLACEHOLDER = JsonFactory.newStringFieldDefinition("policyIdOrPlaceholder", FieldType.REGULAR, JsonSchemaVersion.V_2);

    /**
     * Json Field definition for the optional feature to copy an existing policy.
     */
    public static final JsonFieldDefinition<String> JSON_COPY_POLICY_FROM = JsonFactory.newStringFieldDefinition("_copyPolicyFrom", FieldType.REGULAR, JsonSchemaVersion.V_2);

    /**
     * Json Field definition for the optional initial "inline" policy for usage in getEntity().
     */
    public static final JsonFieldDefinition<JsonObject> JSON_INLINE_POLICY = JsonFactory.newJsonObjectFieldDefinition("_policy", FieldType.REGULAR, JsonSchemaVersion.V_2);

    private final ThingId thingId;

    private final Thing thing;

    @Nullable
    private final JsonObject initialPolicy;

    @Nullable
    private final String policyIdOrPlaceholder;

    private ModifyThing(final ThingId thingId, final Thing thing, @Nullable final JsonObject initialPolicy, @Nullable final String policyIdOrPlaceholder, final DittoHeaders dittoHeaders) {
        super(TYPE, dittoHeaders);
        if (policyIdOrPlaceholder != null && initialPolicy != null) {
            throw PoliciesConflictingException.newBuilder(thingId).dittoHeaders(dittoHeaders).build();
        }
        if (policyIdOrPlaceholder != null && !Placeholders.containsAnyPlaceholder(policyIdOrPlaceholder)) {
            // validates
            PolicyId.of(policyIdOrPlaceholder);
        }
        this.thingId = checkNotNull(thingId, "Thing ID");
        this.thing = checkNotNull(thing, "Thing");
        this.initialPolicy = initialPolicy;
        this.policyIdOrPlaceholder = policyIdOrPlaceholder;
        final JsonObject thingJsonObject = thing.toJson();
        ThingCommandSizeValidator.getInstance().ensureValidSize(thingJsonObject::getUpperBoundForStringSize, () -> thingJsonObject.toString().length(), () -> dittoHeaders);
    }

    /**
     * Returns a command for modifying a thing which is passed as argument.
     *
     * @param thingId the Thing's ID.
     * @param thing the {@link org.eclipse.ditto.things.model.Thing} to modify.
     * @param initialPolicy the initial {@code Policy} to set for the Thing when creating it - may be null.
     * @param dittoHeaders the headers of the command.
     * @return the command.
     * @throws NullPointerException if any argument is {@code null}.
     */
    public static ModifyThing of(final ThingId thingId, final Thing thing, @Nullable final JsonObject initialPolicy, final DittoHeaders dittoHeaders) {
        return new ModifyThing(thingId, thing, initialPolicy, null, dittoHeaders);
    }

    /**
     * Returns a command for modifying a thing which is passed as argument. The thing will have a policy copied from
     * a policy with the given policyIdOrPlaceholder.
     *
     * @param thingId the Thing's ID.
     * @param thing the {@link org.eclipse.ditto.things.model.Thing} to modify.
     * @param policyIdOrPlaceholder the policy id of the {@code Policy} to copy and set for the Thing when creating it.
     * If its a placeholder it will be resolved to a policy id.
     * Placeholder must be of the syntax: {@code {{ ref:things/<theThingId>/policyId }} }.
     * @param dittoHeaders the headers of the command.
     * @return the command.
     * @throws NullPointerException if any argument is {@code null}.
     */
    public static ModifyThing withCopiedPolicy(final ThingId thingId, final Thing thing, final String policyIdOrPlaceholder, final DittoHeaders dittoHeaders) {
        return new ModifyThing(thingId, thing, null, policyIdOrPlaceholder, dittoHeaders);
    }

    /**
     * Returns a command for modifying a thing which is passed as argument.
     * Only one of the arguments initialPolicy and policyIdOrPlaceholder must not be null. They are both allowed to be
     * null, but not both to not be null at the same time.
     *
     * @param thingId the Thing's ID.
     * @param thing the {@link org.eclipse.ditto.things.model.Thing} to modify.
     * @param policyIdOrPlaceholder the policy id of the {@code Policy} to copy and set for the Thing when creating it.
     * If its a placeholder it will be resolved to a policy id.
     * Placeholder must be of the syntax: {@code {{ ref:things/<theThingId>/policyId }} }.
     * @param initialPolicy the initial {@code Policy} to set for the Thing when creating it - may be null.
     * @param dittoHeaders the headers of the command.
     * @return the command.
     * @throws NullPointerException if any argument is {@code null}.
     * @throws org.eclipse.ditto.things.model.signals.commands.exceptions.PoliciesConflictingException if the passed {@code initialPolicy} is not null and the passed
     * {@code policyIdOrPlaceholder} is not null.
     */
    public static ModifyThing of(final ThingId thingId, final Thing thing, @Nullable final JsonObject initialPolicy, @Nullable final String policyIdOrPlaceholder, final DittoHeaders dittoHeaders) {
        return new ModifyThing(thingId, thing, initialPolicy, policyIdOrPlaceholder, dittoHeaders);
    }

    /**
     * Creates a new {@code ModifyThing} from a JSON string.
     *
     * @param jsonString the JSON string of which the command is to be created.
     * @param dittoHeaders the headers of the command.
     * @return the command.
     * @throws NullPointerException if {@code jsonString} is {@code null}.
     * @throws IllegalArgumentException if {@code jsonString} is empty.
     * @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonString} was not in the expected
     * format.
     */
    public static ModifyThing fromJson(final String jsonString, final DittoHeaders dittoHeaders) {
        return fromJson(JsonFactory.newObject(jsonString), dittoHeaders);
    }

    /**
     * Creates a new {@code ModifyThing} from a JSON object.
     *
     * @param jsonObject the JSON object of which the command is to be created.
     * @param dittoHeaders the headers of the command.
     * @return the command.
     * @throws NullPointerException if {@code jsonObject} is {@code null}.
     * @throws org.eclipse.ditto.json.JsonParseException if the passed in {@code jsonObject} was not in the expected
     * format.
     * @throws org.eclipse.ditto.json.JsonMissingFieldException if {@code jsonObject} did not contain a field for {@link
     * org.eclipse.ditto.things.model.signals.commands.ThingCommand.JsonFields#JSON_THING_ID} or {@link #JSON_THING}.
     */
    public static ModifyThing fromJson(final JsonObject jsonObject, final DittoHeaders dittoHeaders) {
        return new CommandJsonDeserializer<ModifyThing>(TYPE, jsonObject).deserialize(() -> {
            final JsonObject thingJsonObject = jsonObject.getValueOrThrow(JSON_THING);
            final Thing extractedThing = ThingsModelFactory.newThing(thingJsonObject);
            final JsonObject initialPolicyObject = jsonObject.getValue(JSON_INITIAL_POLICY).orElse(null);
            final String policyIdOrPlaceholder = jsonObject.getValue(JSON_POLICY_ID_OR_PLACEHOLDER).orElse(null);
            final Optional<String> optionalThingId = jsonObject.getValue(ThingCommand.JsonFields.JSON_THING_ID);
            final ThingId thingId = optionalThingId.map(ThingId::of).orElseGet(() -> extractedThing.getEntityId().orElseThrow(() -> new JsonMissingFieldException(ThingCommand.JsonFields.JSON_THING_ID)));
            return of(thingId, extractedThing, initialPolicyObject, policyIdOrPlaceholder, dittoHeaders);
        });
    }

    /**
     * Returns the {@code Thing} to modify.
     *
     * @return the Thing to modify.
     */

    @Override
    public ThingId getEntityId() {
        return thingId;
    }

    /**
     * @return the initial {@code Policy} if there should be one applied when creating the Thing.
     */
    public Optional<JsonObject> getInitialPolicy() {
        return Optional.ofNullable(initialPolicy);
    }

    /**
     * @return The policy id of the {@code Policy} to copy and set for the Thing when creating it.
     * Could also be a placeholder like: {{ ref:things/theThingId/policyId }}.
     */
    public Optional<String> getPolicyIdOrPlaceholder() {
        return Optional.ofNullable(policyIdOrPlaceholder);
    }

    @Override
    public JsonPointer getResourcePath() {
        return JsonPointer.empty();
    }

    @Override
    public Optional<JsonValue> getEntity(final JsonSchemaVersion schemaVersion) {
        final JsonObject thingJson = thing.toJson(schemaVersion, FieldType.regularOrSpecial());
        final JsonObject withInlinePolicyThingJson = getInitialPolicy().map(ip -> thingJson.set(JSON_INLINE_POLICY, ip)).orElse(thingJson);
        final JsonObject fullThingJson = getPolicyIdOrPlaceholder().map(containedPolicyIdOrPlaceholder -> withInlinePolicyThingJson.set(JSON_COPY_POLICY_FROM, containedPolicyIdOrPlaceholder)).orElse(withInlinePolicyThingJson);
        return Optional.of(fullThingJson);
    }

    @Override
    protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final JsonSchemaVersion schemaVersion, final Predicate<JsonField> thePredicate) {
        final Predicate<JsonField> predicate = schemaVersion.and(thePredicate);
        jsonObjectBuilder.set(ThingCommand.JsonFields.JSON_THING_ID, thingId.toString(), predicate);
        jsonObjectBuilder.set(JSON_THING, thing.toJson(schemaVersion, thePredicate), predicate);
        if (initialPolicy != null) {
            jsonObjectBuilder.set(JSON_INITIAL_POLICY, initialPolicy, predicate);
        }
        if (policyIdOrPlaceholder != null) {
            jsonObjectBuilder.set(JSON_POLICY_ID_OR_PLACEHOLDER, policyIdOrPlaceholder, predicate);
        }
    }

    @Override
    public Category getCategory() {
        return Category.MODIFY;
    }

    @Override
    public ModifyThing setDittoHeaders(final DittoHeaders dittoHeaders) {
        return of(thingId, thing, initialPolicy, policyIdOrPlaceholder, dittoHeaders);
    }

    @Override
    public boolean changesAuthorization() {
        return thing.getPolicyEntityId().isPresent();
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), thingId, thing, initialPolicy, policyIdOrPlaceholder);
    }

    @SuppressWarnings("squid:MethodCyclomaticComplexity")
    @Override
    public boolean equals(@Nullable final Object obj) {
        if (this == obj) {
            return true;
        }
        if (null == obj || getClass() != obj.getClass()) {
            return false;
        }
        final ModifyThing that = (ModifyThing) obj;
        return that.canEqual(this) && Objects.equals(thingId, that.thingId) && Objects.equals(thing, that.thing) && Objects.equals(initialPolicy, that.initialPolicy) && Objects.equals(policyIdOrPlaceholder, that.policyIdOrPlaceholder) && super.equals(obj);
    }

    @Override
    protected boolean canEqual(@Nullable final Object other) {
        return (other instanceof ModifyThing);
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + " [" + super.toString() + ", thingId=" + thingId + ", thing=" + thing + ", initialPolicy=" + initialPolicy + ", policyIdOrPlaceholder=" + policyIdOrPlaceholder + "]";
    }
}


for (int i = 0; i < tmpSC.length; i++) {
            input[2 * i] = (byte) (tmpSC[i] & 0xff);
            input[2 * i + 1] = (byte) (tmpSC[i] >> 8);
        }

/*
 *
 *
 * @author Stephen Uhler
 *
 * 2014 Eddy Xiao <example@nnthink.com>
 * GUI extensively modified.
 * Add some naive auto refresh rate control logic.
 */
package github.bewantbe.audio_analyzer_for_android;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Custom view to draw the FFT graph
 */
public class AnalyzerGraphic extends View {

    private final String TAG = "AnalyzerGraphic:";

    private Context context;

    // horizontal and vertical scaling
    private double xZoom, yZoom;

    // horizontal and vertical translation, in unit 1 unit
    private double xShift, yShift;

    // hard lower bound for dB
    static final double minDB = -144f;

    // hard upper bound for dB
    static final double maxDB = 12f;

    // size of my canvas
    private int canvasWidth, canvasHeight;

    // window location on screen
    private int[] myLocation = { 0, 0 };

    private volatile static boolean isBusy = false;

    private double freq_lower_bound_for_log = 0f;

    private double[] savedDBSpectrum = new double[0];

    static final int VIEW_RANGE_DATA_LENGTH = 6;

    SpectrumPlot spectrumPlot;

    SpectrogramPlot spectrogramPlot;

    private PlotMode showMode = PlotMode.SPECTRUM;

    enum // java's enum type is inconvenient
    PlotMode {

        SPECTRUM(0), SPECTROGRAM(1);

        private final int value;

        PlotMode(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    public AnalyzerGraphic(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    public AnalyzerGraphic(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public AnalyzerGraphic(Context context) {
        super(context);
        setup(context);
    }

    private void setup(Context _context) {
        context = _context;
        Log.i(TAG, "in setup()");
        xZoom = 1f;
        xShift = 0f;
        yZoom = 1f;
        yShift = 0f;
        canvasWidth = canvasHeight = 0;
        // Demo of full initialization
        spectrumPlot = new SpectrumPlot(context);
        spectrogramPlot = new SpectrogramPlot(context);
        spectrumPlot.setCanvas(canvasWidth, canvasHeight, null);
        spectrogramPlot.setCanvas(canvasWidth, canvasHeight, null);
        spectrumPlot.setZooms(xZoom, xShift, yZoom, yShift);
        spectrogramPlot.setZooms(xZoom, xShift, yZoom, yShift);
        Resources res = _context.getResources();
        spectrumPlot.axisY.vLowerBound = Float.parseFloat(res.getString(R.string.max_DB_range));
    }

    AnalyzerParameters analyzerParamCache;

    void setupAxes(AnalyzerParameters analyzerParam) {
        int sampleRate = analyzerParam.sampleRate;
        int fftLen = analyzerParam.fftLen;
        int nAve = analyzerParam.nFFTAverage;
        double timeDurationE = analyzerParam.spectrogramDuration;
        freq_lower_bound_for_log = (double) sampleRate / fftLen;
        double freq_lower_bound_local = 0;
        if (spectrumPlot.axisX.mapType == ScreenPhysicalMapping.Type.LOG) {
            freq_lower_bound_local = freq_lower_bound_for_log;
        }
        // Spectrum
        double[] axisBounds = new double[] { freq_lower_bound_local, 0.0, sampleRate / 2.0, spectrumPlot.axisY.vUpperBound };
        Log.i(TAG, "setupAxes(): W=" + canvasWidth + "  H=" + canvasHeight + "  dB=" + spectrumPlot.axisY.vUpperBound);
        spectrumPlot.setCanvas(canvasWidth, canvasHeight, axisBounds);
        // Spectrogram
        freq_lower_bound_local = 0;
        if (spectrogramPlot.axisFreq.mapType == ScreenPhysicalMapping.Type.LOG) {
            freq_lower_bound_local = freq_lower_bound_for_log;
        }
        if (spectrogramPlot.showFreqAlongX) {
            axisBounds = new double[] { freq_lower_bound_local, 0.0, sampleRate / 2.0, timeDurationE * nAve };
        } else {
            axisBounds = new double[] { 0.0, sampleRate / 2.0, timeDurationE * nAve, freq_lower_bound_local };
        }
        spectrogramPlot.setCanvas(canvasWidth, canvasHeight, axisBounds);
        analyzerParamCache = analyzerParam;
    }

    // Call this when settings changed.
    void setupPlot(AnalyzerParameters analyzerParam) {
        setupAxes(analyzerParam);
        spectrogramPlot.setupSpectrogram(analyzerParam);
    }

    void setAxisModeLinear(String mode) {
        // see also layout/*.xml
        boolean b = mode.equals("linear");
        ScreenPhysicalMapping.Type mapType;
        GridLabel.Type gridType;
        if (b) {
            mapType = ScreenPhysicalMapping.Type.LINEAR;
            gridType = GridLabel.Type.FREQ;
        } else {
            mapType = ScreenPhysicalMapping.Type.LOG;
            gridType = mode.equals("note") ? GridLabel.Type.FREQ_NOTE : GridLabel.Type.FREQ_LOG;
        }
        spectrumPlot.setFreqAxisMode(mapType, freq_lower_bound_for_log, gridType);
        spectrogramPlot.setFreqAxisMode(mapType, freq_lower_bound_for_log, gridType);
        if (showMode == PlotMode.SPECTRUM) {
            xZoom = spectrumPlot.axisX.getZoom();
            xShift = spectrumPlot.axisX.getShift();
        } else if (showMode == PlotMode.SPECTROGRAM) {
            if (spectrogramPlot.showFreqAlongX) {
                xZoom = spectrogramPlot.axisFreq.getZoom();
                xShift = spectrogramPlot.axisFreq.getShift();
            } else {
                yZoom = spectrogramPlot.axisFreq.getZoom();
                yShift = spectrogramPlot.axisFreq.getShift();
            }
        }
    }

    public void setShowFreqAlongX(boolean b) {
        spectrogramPlot.setShowFreqAlongX(b);
        if (showMode == PlotMode.SPECTRUM)
            return;
        if (spectrogramPlot.showFreqAlongX) {
            xZoom = spectrogramPlot.axisFreq.getZoom();
            xShift = spectrogramPlot.axisFreq.getShift();
            yZoom = spectrogramPlot.axisTime.getZoom();
            yShift = spectrogramPlot.axisTime.getShift();
        } else {
            xZoom = spectrogramPlot.axisTime.getZoom();
            xShift = spectrogramPlot.axisTime.getShift();
            yZoom = spectrogramPlot.axisFreq.getZoom();
            yShift = spectrogramPlot.axisFreq.getShift();
        }
    }

    // Note: Assume setupPlot() was called once.
    public void switch2Spectrum() {
        if (showMode == PlotMode.SPECTRUM) {
            return;
        }
        Log.v(TAG, "switch2Spectrum()");
        // execute when switch from Spectrogram mode to Spectrum mode
        showMode = PlotMode.SPECTRUM;
        xZoom = spectrogramPlot.axisFreq.getZoom();
        xShift = spectrogramPlot.axisFreq.getShift();
        if (!spectrogramPlot.showFreqAlongX) {
            xShift = 1 - 1 / xZoom - xShift;
        }
        spectrumPlot.axisX.setZoomShift(xZoom, xShift);
        yZoom = spectrumPlot.axisY.getZoom();
        yShift = spectrumPlot.axisY.getShift();
    }

    // Note: Assume setupPlot() was called once.
    public void switch2Spectrogram() {
        if (showMode == PlotMode.SPECTRUM && canvasHeight > 0) {
            // canvasHeight==0 means the program is just start
            Log.v(TAG, "switch2Spectrogram()");
            if (spectrogramPlot.showFreqAlongX) {
                // no need to change x scaling
                yZoom = spectrogramPlot.axisTime.getZoom();
                yShift = spectrogramPlot.axisTime.getShift();
                spectrogramPlot.axisFreq.setZoomShift(xZoom, xShift);
            } else {
                // noinspection SuspiciousNameCombination
                yZoom = xZoom;
                // axisFreq is reverted
                yShift = 1 - 1 / xZoom - xShift;
                xZoom = spectrogramPlot.axisTime.getZoom();
                xShift = spectrogramPlot.axisTime.getShift();
                spectrogramPlot.axisFreq.setZoomShift(yZoom, yShift);
            }
        }
        spectrogramPlot.spectrogramBMP.updateAxis(spectrogramPlot.axisFreq);
        spectrogramPlot.prepare();
        showMode = PlotMode.SPECTROGRAM;
    }

    double[] setViewRange(double[] _ranges, double[] rangesDefault) {
        if (_ranges.length < VIEW_RANGE_DATA_LENGTH) {
            Log.i(TAG, "setViewRange(): invalid input.");
            return null;
        }
        // do not modify input parameter
        double[] ranges = new double[VIEW_RANGE_DATA_LENGTH];
        System.arraycopy(_ranges, 0, ranges, 0, VIEW_RANGE_DATA_LENGTH);
        if (rangesDefault != null) {
            // Sanity check
            if (rangesDefault.length != 2 * VIEW_RANGE_DATA_LENGTH) {
                Log.i(TAG, "setViewRange(): invalid input.");
                return null;
            }
            for (int i = 0; i < 6; i += 2) {
                if (ranges[i] > ranges[i + 1]) {
                    // order reversed
                    double t = ranges[i];
                    ranges[i] = ranges[i + 1];
                    ranges[i + 1] = t;
                }
                // lower  than lower bound
                if (ranges[i] < rangesDefault[i + 6])
                    ranges[i] = rangesDefault[i + 6];
                // all lower  than lower bound?
                if (ranges[i + 1] < rangesDefault[i + 6])
                    ranges[i + 1] = rangesDefault[i + 7];
                // all higher than upper bound?
                if (ranges[i] > rangesDefault[i + 7])
                    ranges[i] = rangesDefault[i + 6];
                // higher than upper bound
                if (ranges[i + 1] > rangesDefault[i + 7])
                    ranges[i + 1] = rangesDefault[i + 7];
                if (ranges[i] == ranges[i + 1] || Double.isNaN(ranges[i]) || Double.isNaN(ranges[i + 1])) {
                    // invalid input value
                    ranges[i] = rangesDefault[i];
                    ranges[i + 1] = rangesDefault[i + 1];
                }
            }
        }
        // Set range
        if (showMode == PlotMode.SPECTRUM) {
            spectrumPlot.axisX.setViewBounds(ranges[0], ranges[1]);
            // reversed
            spectrumPlot.axisY.setViewBounds(ranges[3], ranges[2]);
        } else if (showMode == PlotMode.SPECTROGRAM) {
            if (spectrogramPlot.getSpectrogramMode() == SpectrogramPlot.TimeAxisMode.SHIFT) {
                spectrogramPlot.axisTime.setViewBounds(ranges[5], ranges[4]);
            } else {
                spectrogramPlot.axisTime.setViewBounds(ranges[4], ranges[5]);
            }
            if (spectrogramPlot.showFreqAlongX) {
                spectrogramPlot.axisFreq.setViewBounds(ranges[0], ranges[1]);
            } else {
                spectrogramPlot.axisFreq.setViewBounds(ranges[1], ranges[0]);
            }
            spectrogramPlot.spectrogramBMP.updateAxis(spectrogramPlot.axisFreq);
        }
        // Set zoom shift for view
        if (showMode == PlotMode.SPECTRUM) {
            xZoom = spectrumPlot.axisX.getZoom();
            xShift = spectrumPlot.axisX.getShift();
            yZoom = spectrumPlot.axisY.getZoom();
            yShift = spectrumPlot.axisY.getShift();
        } else if (showMode == PlotMode.SPECTROGRAM) {
            if (spectrogramPlot.showFreqAlongX) {
                xZoom = spectrogramPlot.axisFreq.getZoom();
                xShift = spectrogramPlot.axisFreq.getShift();
                yZoom = spectrogramPlot.axisTime.getZoom();
                yShift = spectrogramPlot.axisTime.getShift();
            } else {
                yZoom = spectrogramPlot.axisFreq.getZoom();
                yShift = spectrogramPlot.axisFreq.getShift();
                xZoom = spectrogramPlot.axisTime.getZoom();
                xShift = spectrogramPlot.axisTime.getShift();
            }
        }
        return ranges;
    }

    private void updateAxisZoomShift() {
        if (showMode == PlotMode.SPECTRUM) {
            spectrumPlot.setZooms(xZoom, xShift, yZoom, yShift);
        } else {
            spectrogramPlot.setZooms(xZoom, xShift, yZoom, yShift);
        }
    }

    public void setSmoothRender(boolean b) {
        spectrogramPlot.setSmoothRender(b);
    }

    public PlotMode getShowMode() {
        return showMode;
    }

    public void setTimeMultiplier(int nAve) {
        spectrogramPlot.setTimeMultiplier(nAve);
    }

    public void setShowTimeAxis(boolean bSTA) {
        spectrogramPlot.setShowTimeAxis(bSTA);
    }

    public void setSpectrogramModeShifting(boolean b) {
        spectrogramPlot.setSpectrogramModeShifting(b);
    }

    void setLogAxisMode(boolean b) {
        SpectrogramBMP.LogAxisPlotMode mode = SpectrogramBMP.LogAxisPlotMode.REPLOT;
        if (!b) {
            mode = SpectrogramBMP.LogAxisPlotMode.SEGMENT;
        }
        spectrogramPlot.spectrogramBMP.setLogAxisMode(mode);
    }

    void setColorMap(String colorMapName) {
        spectrogramPlot.setColorMap(colorMapName);
    }

    static boolean isBusy() {
        return isBusy;
    }

    static void setIsBusy(boolean b) {
        isBusy = b;
    }

    private final FPSCounter fpsCounter = new FPSCounter("AnalyzerGraphic");

    @Override
    protected void onDraw(Canvas c) {
        fpsCounter.inc();
        isBusy = true;
        if (showMode == PlotMode.SPECTRUM) {
            spectrumPlot.addCalibCurve(analyzerParamCache.micGainDB, null, analyzerParamCache.calibName);
            spectrumPlot.drawSpectrumPlot(c, savedDBSpectrum);
        } else {
            spectrogramPlot.drawSpectrogramPlot(c);
        }
        isBusy = false;
    }

    // All FFT data will enter this view through this interface
    // Will be called in another thread (SamplingLoop)
    public void saveSpectrum(double[] db) {
        synchronized (savedDBSpectrum) {
            // TODO: need lock on savedDBSpectrum, but how?
            if (savedDBSpectrum == null || savedDBSpectrum.length != db.length) {
                savedDBSpectrum = new double[db.length];
            }
            System.arraycopy(db, 0, savedDBSpectrum, 0, db.length);
        }
        // TODO: Run on another thread? Lock on data ? Or use CompletionService?
        if (showMode == PlotMode.SPECTROGRAM) {
            spectrogramPlot.saveRowSpectrumAsColor(savedDBSpectrum);
        }
    }

    void setSpectrumDBLowerBound(double b) {
        spectrumPlot.axisY.vUpperBound = b;
    }

    void setSpectrogramDBLowerBound(double b) {
        spectrogramPlot.setSpectrogramDBLowerBound(b);
    }

    public void setShowLines(boolean b) {
        spectrumPlot.showLines = b;
    }

    private boolean intersects(float x, float y) {
        getLocationOnScreen(myLocation);
        return x >= myLocation[0] && y >= myLocation[1] && x < myLocation[0] + getWidth() && y < myLocation[1] + getHeight();
    }

    // return true if the coordinate (x,y) is inside graphView
    public boolean setCursor(float x, float y) {
        if (intersects(x, y)) {
            x = x - myLocation[0];
            y = y - myLocation[1];
            // Convert to coordinate in axis
            if (showMode == PlotMode.SPECTRUM) {
                spectrumPlot.setCursor(x, y);
            } else {
                spectrogramPlot.setCursor(x, y);
            }
            return true;
        } else {
            return false;
        }
    }

    public double getCursorFreq() {
        if (showMode == PlotMode.SPECTRUM) {
            return spectrumPlot.getCursorFreq();
        } else {
            return spectrogramPlot.getCursorFreq();
        }
    }

    public double getCursorDB() {
        if (showMode == PlotMode.SPECTRUM) {
            return spectrumPlot.getCursorDB();
        } else {
            return 0;
        }
    }

    public void hideCursor() {
        spectrumPlot.hideCursor();
        spectrogramPlot.hideCursor();
    }

    double[] getViewPhysicalRange() {
        double[] r = new double[12];
        if (getShowMode() == AnalyzerGraphic.PlotMode.SPECTRUM) {
            // fL, fU, dBL dBU, time L, time U
            r[0] = spectrumPlot.axisX.vMinInView();
            r[1] = spectrumPlot.axisX.vMaxInView();
            // reversed
            r[2] = spectrumPlot.axisY.vMaxInView();
            r[3] = spectrumPlot.axisY.vMinInView();
            r[4] = 0;
            r[5] = 0;
            // Limits of fL, fU, dBL dBU, time L, time U
            r[6] = spectrumPlot.axisX.vLowerBound;
            r[7] = spectrumPlot.axisX.vUpperBound;
            r[8] = AnalyzerGraphic.minDB;
            r[9] = AnalyzerGraphic.maxDB;
            r[10] = 0;
            r[11] = 0;
        } else {
            r[0] = spectrogramPlot.axisFreq.vMinInView();
            r[1] = spectrogramPlot.axisFreq.vMaxInView();
            if (r[0] > r[1]) {
                double t = r[0];
                r[0] = r[1];
                r[1] = t;
            }
            r[2] = spectrogramPlot.spectrogramBMP.dBLowerBound;
            r[3] = spectrogramPlot.spectrogramBMP.dBUpperBound;
            r[4] = spectrogramPlot.axisTime.vMinInView();
            r[5] = spectrogramPlot.axisTime.vMaxInView();
            r[6] = spectrogramPlot.axisFreq.vLowerBound;
            r[7] = spectrogramPlot.axisFreq.vUpperBound;
            if (r[6] > r[7]) {
                double t = r[6];
                r[6] = r[7];
                r[7] = t;
            }
            r[8] = AnalyzerGraphic.minDB;
            r[9] = AnalyzerGraphic.maxDB;
            r[10] = spectrogramPlot.axisTime.vLowerBound;
            r[11] = spectrogramPlot.axisTime.vUpperBound;
        }
        for (int i = 6; i < r.length; i += 2) {
            if (r[i] > r[i + 1]) {
                double t = r[i];
                r[i] = r[i + 1];
                r[i + 1] = t;
            }
        }
        return r;
    }

    public double getXZoom() {
        return xZoom;
    }

    public double getYZoom() {
        return yZoom;
    }

    public double getXShift() {
        return xShift;
    }

    public double getYShift() {
        return yShift;
    }

    public double getCanvasWidth() {
        if (showMode == PlotMode.SPECTRUM) {
            return canvasWidth;
        } else {
            return canvasWidth - spectrogramPlot.labelBeginX;
        }
    }

    public double getCanvasHeight() {
        if (showMode == PlotMode.SPECTRUM) {
            return canvasHeight;
        } else {
            return spectrogramPlot.labelBeginY;
        }
    }

    private double clamp(double x, double min, double max) {
        if (x > max) {
            return max;
        } else if (x < min) {
            return min;
        } else {
            return x;
        }
    }

    private double clampXShift(double offset) {
        return clamp(offset, 0f, 1 - 1 / xZoom);
    }

    private double clampYShift(double offset) {
        if (showMode == PlotMode.SPECTRUM) {
            // limit view to minDB ~ maxDB, assume linear in dB scale
            return clamp(offset, (maxDB - spectrumPlot.axisY.vLowerBound) / spectrumPlot.axisY.diffVBounds(), (minDB - spectrumPlot.axisY.vLowerBound) / spectrumPlot.axisY.diffVBounds() - 1 / yZoom);
        } else {
            // strict restrict, y can be frequency or time.
            return clamp(offset, 0f, 1 - 1 / yZoom);
        }
    }

    public void setXShift(double offset) {
        xShift = clampXShift(offset);
        updateAxisZoomShift();
    }

    public void setYShift(double offset) {
        yShift = clampYShift(offset);
        updateAxisZoomShift();
    }

    public void resetViewScale() {
        xShift = 0;
        xZoom = 1;
        yShift = 0;
        yZoom = 1;
        updateAxisZoomShift();
    }

    private double xMidOld = 100;

    private double xDiffOld = 100;

    private double xZoomOld = 1;

    private double xShiftOld = 0;

    private double yMidOld = 100;

    private double yDiffOld = 100;

    private double yZoomOld = 1;

    private double yShiftOld = 0;

    // record the coordinate frame state when starting scaling
    public void setShiftScaleBegin(double x1, double y1, double x2, double y2) {
        xMidOld = (x1 + x2) / 2f;
        xDiffOld = Math.abs(x1 - x2);
        xZoomOld = xZoom;
        xShiftOld = xShift;
        yMidOld = (y1 + y2) / 2f;
        yDiffOld = Math.abs(y1 - y2);
        yZoomOld = yZoom;
        yShiftOld = yShift;
    }

    // Do the scaling according to the motion event getX() and getY() (getPointerCount()==2)
    public void setShiftScale(double x1, double y1, double x2, double y2) {
        double limitXZoom;
        double limitYZoom;
        if (showMode == PlotMode.SPECTRUM) {
            // limit to 200 Hz a screen
            limitXZoom = spectrumPlot.axisX.diffVBounds() / 200f;
            // limit to 6 dB a screen
            limitYZoom = -spectrumPlot.axisY.diffVBounds() / 6f;
        } else {
            int nTimePoints = spectrogramPlot.nTimePoints;
            if (spectrogramPlot.showFreqAlongX) {
                limitXZoom = spectrogramPlot.axisFreq.diffVBounds() / 200f;
                limitYZoom = nTimePoints > 10 ? nTimePoints / 10 : 1;
            } else {
                limitXZoom = nTimePoints > 10 ? nTimePoints / 10 : 1;
                limitYZoom = spectrogramPlot.axisFreq.diffVBounds() / 200f;
            }
        }
        limitXZoom = Math.abs(limitXZoom);
        limitYZoom = Math.abs(limitYZoom);
        if (canvasWidth * 0.13f < xDiffOld) {
            // if fingers are not very close in x direction, do scale in x direction
            xZoom = clamp(xZoomOld * Math.abs(x1 - x2) / xDiffOld, 1f, limitXZoom);
        }
        xShift = clampXShift(xShiftOld + (xMidOld / xZoomOld - (x1 + x2) / 2f / xZoom) / canvasWidth);
        if (canvasHeight * 0.13f < yDiffOld) {
            // if fingers are not very close in y direction, do scale in y direction
            yZoom = clamp(yZoomOld * Math.abs(y1 - y2) / yDiffOld, 1f, limitYZoom);
        }
        yShift = clampYShift(yShiftOld + (yMidOld / yZoomOld - (y1 + y2) / 2f / yZoom) / canvasHeight);
        updateAxisZoomShift();
    }

    // callback to caller when rendering is complete
    private Ready readyCallback = null;

    public void setReady(Ready ready) {
        this.readyCallback = ready;
    }

    interface Ready {

        void ready();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.i(TAG, "onSizeChanged(): canvas (" + oldw + "," + oldh + ") -> (" + w + "," + h + ")");
        isBusy = true;
        this.canvasWidth = w;
        this.canvasHeight = h;
        spectrumPlot.setCanvas(w, h, null);
        spectrogramPlot.setCanvas(w, h, null);
        if (h > 0 && readyCallback != null) {
            readyCallback.ready();
        }
        isBusy = false;
    }

    /*
   * Save freqAxisAlongX, cursors, zooms, and current spectrogram/spectrum.
   * All other properties will be set in onCreate() and onResume().
   * Will be called after onPause() or between onCreat() and on onResume()
   * Ref. http://example.com
   */
    @Override
    protected Parcelable onSaveInstanceState() {
        Log.i(TAG, "onSaveInstanceState(): xShift = " + xShift + "  xZoom = " + xZoom + "  yShift = " + yShift + "  yZoom = " + yZoom);
        Parcelable parentState = super.onSaveInstanceState();
        SavedState state = new SavedState(parentState);
        state.freqAxisAlongX = spectrogramPlot.showFreqAlongX ? 1 : 0;
        state.cFreqSpum = spectrumPlot.cursorFreq;
        state.cFreqSpam = spectrogramPlot.cursorFreq;
        state.cDb = spectrumPlot.cursorDB;
        state.xZ = xZoom;
        state.xS = xShift;
        state.yZ = yZoom;
        state.yS = yShift;
        state.SpumXZ = spectrumPlot.axisX.getZoom();
        state.SpumXS = spectrumPlot.axisX.getShift();
        state.SpumYZ = spectrumPlot.axisY.getZoom();
        state.SpumYS = spectrumPlot.axisY.getShift();
        state.SpamFZ = spectrogramPlot.axisFreq.getZoom();
        state.SpamFS = spectrogramPlot.axisFreq.getShift();
        state.SpamTZ = spectrogramPlot.axisTime.getZoom();
        state.SpamTS = spectrogramPlot.axisTime.getShift();
        state.tmpS = savedDBSpectrum;
        state.nFreq = spectrogramPlot.nFreqPoints;
        state.nTime = spectrogramPlot.nTimePoints;
        state.iTimePinter = spectrogramPlot.spectrogramBMP.spectrumStore.iTimePointer;
        final short[] tmpSC = spectrogramPlot.spectrogramBMP.spectrumStore.dbShortArray;
        // Note no ByteBuffer view of ShortBuffer, see
        byte[] input = new byte[tmpSC.length * 2];

        // Save spectrumStore.dbShortArray to a file.
        File tmpSCPath = new File(context.getCacheDir(), "spectrogram_short.raw");
        try {
            OutputStream fout = new FileOutputStream(tmpSCPath);
            fout.write(input);
            fout.close();
        } catch (IOException e) {
            Log.w("SavedState:", "writeToParcel(): Fail to save state to file.");
        }
        return state;
    }

    // Will be called after setup(), during AnalyzerActivity.onCreate()?
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof SavedState) {
            SavedState s = (SavedState) state;
            super.onRestoreInstanceState(s.getSuperState());
            spectrogramPlot.showFreqAlongX = s.freqAxisAlongX == 1;
            spectrumPlot.cursorFreq = s.cFreqSpum;
            spectrogramPlot.cursorFreq = s.cFreqSpam;
            spectrumPlot.cursorDB = s.cDb;
            xZoom = s.xZ;
            xShift = s.xS;
            yZoom = s.yZ;
            yShift = s.yS;
            spectrumPlot.axisX.setZoomShift(s.SpumXZ, s.SpumXS);
            spectrumPlot.axisY.setZoomShift(s.SpumYZ, s.SpumYS);
            spectrogramPlot.axisFreq.setZoomShift(s.SpamFZ, s.SpamFS);
            spectrogramPlot.axisTime.setZoomShift(s.SpamTZ, s.SpamTS);
            savedDBSpectrum = s.tmpS;
            spectrogramPlot.nFreqPoints = s.nFreq;
            spectrogramPlot.nTimePoints = s.nTime;
            final SpectrogramBMP sBMP = spectrogramPlot.spectrogramBMP;
            final SpectrogramBMP.SpectrumCompressStore sBMPS = sBMP.spectrumStore;
            // prevent reinitialize of LogFreqSpectrogramBMP
            sBMPS.nFreq = s.nFreq;
            sBMPS.nTime = s.nTime;
            sBMPS.iTimePointer = s.iTimePinter;
            // Read temporary saved spectrogram
            // length of spectrumStore.dbShortArray
            byte[] input = new byte[(s.nFreq + 1) * s.nTime * 2];
            int bytesRead = -1;
            File tmpSCPath = new File(context.getCacheDir(), "spectrogram_short.raw");
            try {
                InputStream fin = new FileInputStream(tmpSCPath);
                bytesRead = fin.read(input);
                fin.close();
            } catch (IOException e) {
                Log.w("SavedState:", "writeToParcel(): Fail to save state to file.");
            }
            if (bytesRead != input.length) {
                // fail to get saved spectrogram, have a new start
                sBMPS.nFreq = 0;
                sBMPS.nTime = 0;
                sBMPS.iTimePointer = 0;
            } else {
                // we have data!
                short[] tmpSC = new short[input.length / 2];
                for (int i = 0; i < tmpSC.length; i++) {
                    tmpSC[i] = (short) (input[2 * i] + (input[2 * i + 1] << 8));
                }
                sBMPS.dbShortArray = tmpSC;
                sBMP.rebuildLinearBMP();
            }
            Log.i(TAG, "onRestoreInstanceState(): xShift = " + xShift + "  xZoom = " + xZoom + "  yShift = " + yShift + "  yZoom = " + yZoom);
        } else {
            Log.i(TAG, "onRestoreInstanceState(): not SavedState?!");
            super.onRestoreInstanceState(state);
        }
    }

    private static class SavedState extends BaseSavedState {

        int freqAxisAlongX;

        double cFreqSpum;

        double cFreqSpam;

        double cDb;

        double xZ;

        double xS;

        double yZ;

        double yS;

        double SpumXZ;

        double SpumXS;

        double SpumYZ;

        double SpumYS;

        double SpamFZ;

        double SpamFS;

        double SpamTZ;

        double SpamTS;

        double[] tmpS;

        int nFreq;

        int nTime;

        int iTimePinter;

        SavedState(Parcelable state) {
            super(state);
        }

        private SavedState(Parcel in) {
            super(in);
            freqAxisAlongX = in.readInt();
            cFreqSpum = in.readDouble();
            cFreqSpam = in.readDouble();
            cDb = in.readDouble();
            xZ = in.readDouble();
            xS = in.readDouble();
            yZ = in.readDouble();
            yS = in.readDouble();
            SpumXZ = in.readDouble();
            SpumXS = in.readDouble();
            SpumYZ = in.readDouble();
            SpumYS = in.readDouble();
            SpamFZ = in.readDouble();
            SpamFS = in.readDouble();
            SpamTZ = in.readDouble();
            SpamTS = in.readDouble();
            tmpS = in.createDoubleArray();
            nFreq = in.readInt();
            nTime = in.readInt();
            iTimePinter = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(freqAxisAlongX);
            out.writeDouble(cFreqSpum);
            out.writeDouble(cFreqSpam);
            out.writeDouble(cDb);
            out.writeDouble(xZ);
            out.writeDouble(xS);
            out.writeDouble(yZ);
            out.writeDouble(yS);
            out.writeDouble(SpumXZ);
            out.writeDouble(SpumXS);
            out.writeDouble(SpumYZ);
            out.writeDouble(SpumYS);
            out.writeDouble(SpamFZ);
            out.writeDouble(SpamFS);
            out.writeDouble(SpamTZ);
            out.writeDouble(SpamTS);
            out.writeDoubleArray(tmpS);
            out.writeInt(nFreq);
            out.writeInt(nTime);
            out.writeInt(iTimePinter);
        }

        // Standard creator object using an instance of this class
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {

            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}


try {
                InputStream fin = new FileInputStream(tmpSCPath);
                bytesRead = fin.read(input);
                fin.close();
            } catch (IOException e) {
                Log.w("SavedState:", "writeToParcel(): Fail to save state to file.");
            }

/*
 *
 *
 * @author Stephen Uhler
 *
 * 2014 Eddy Xiao <example@nnthink.com>
 * GUI extensively modified.
 * Add some naive auto refresh rate control logic.
 */
package github.bewantbe.audio_analyzer_for_android;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Custom view to draw the FFT graph
 */
public class AnalyzerGraphic extends View {

    private final String TAG = "AnalyzerGraphic:";

    private Context context;

    // horizontal and vertical scaling
    private double xZoom, yZoom;

    // horizontal and vertical translation, in unit 1 unit
    private double xShift, yShift;

    // hard lower bound for dB
    static final double minDB = -144f;

    // hard upper bound for dB
    static final double maxDB = 12f;

    // size of my canvas
    private int canvasWidth, canvasHeight;

    // window location on screen
    private int[] myLocation = { 0, 0 };

    private volatile static boolean isBusy = false;

    private double freq_lower_bound_for_log = 0f;

    private double[] savedDBSpectrum = new double[0];

    static final int VIEW_RANGE_DATA_LENGTH = 6;

    SpectrumPlot spectrumPlot;

    SpectrogramPlot spectrogramPlot;

    private PlotMode showMode = PlotMode.SPECTRUM;

    enum // java's enum type is inconvenient
    PlotMode {

        SPECTRUM(0), SPECTROGRAM(1);

        private final int value;

        PlotMode(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    public AnalyzerGraphic(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup(context);
    }

    public AnalyzerGraphic(Context context, AttributeSet attrs) {
        super(context, attrs);
        setup(context);
    }

    public AnalyzerGraphic(Context context) {
        super(context);
        setup(context);
    }

    private void setup(Context _context) {
        context = _context;
        Log.i(TAG, "in setup()");
        xZoom = 1f;
        xShift = 0f;
        yZoom = 1f;
        yShift = 0f;
        canvasWidth = canvasHeight = 0;
        // Demo of full initialization
        spectrumPlot = new SpectrumPlot(context);
        spectrogramPlot = new SpectrogramPlot(context);
        spectrumPlot.setCanvas(canvasWidth, canvasHeight, null);
        spectrogramPlot.setCanvas(canvasWidth, canvasHeight, null);
        spectrumPlot.setZooms(xZoom, xShift, yZoom, yShift);
        spectrogramPlot.setZooms(xZoom, xShift, yZoom, yShift);
        Resources res = _context.getResources();
        spectrumPlot.axisY.vLowerBound = Float.parseFloat(res.getString(R.string.max_DB_range));
    }

    AnalyzerParameters analyzerParamCache;

    void setupAxes(AnalyzerParameters analyzerParam) {
        int sampleRate = analyzerParam.sampleRate;
        int fftLen = analyzerParam.fftLen;
        int nAve = analyzerParam.nFFTAverage;
        double timeDurationE = analyzerParam.spectrogramDuration;
        freq_lower_bound_for_log = (double) sampleRate / fftLen;
        double freq_lower_bound_local = 0;
        if (spectrumPlot.axisX.mapType == ScreenPhysicalMapping.Type.LOG) {
            freq_lower_bound_local = freq_lower_bound_for_log;
        }
        // Spectrum
        double[] axisBounds = new double[] { freq_lower_bound_local, 0.0, sampleRate / 2.0, spectrumPlot.axisY.vUpperBound };
        Log.i(TAG, "setupAxes(): W=" + canvasWidth + "  H=" + canvasHeight + "  dB=" + spectrumPlot.axisY.vUpperBound);
        spectrumPlot.setCanvas(canvasWidth, canvasHeight, axisBounds);
        // Spectrogram
        freq_lower_bound_local = 0;
        if (spectrogramPlot.axisFreq.mapType == ScreenPhysicalMapping.Type.LOG) {
            freq_lower_bound_local = freq_lower_bound_for_log;
        }
        if (spectrogramPlot.showFreqAlongX) {
            axisBounds = new double[] { freq_lower_bound_local, 0.0, sampleRate / 2.0, timeDurationE * nAve };
        } else {
            axisBounds = new double[] { 0.0, sampleRate / 2.0, timeDurationE * nAve, freq_lower_bound_local };
        }
        spectrogramPlot.setCanvas(canvasWidth, canvasHeight, axisBounds);
        analyzerParamCache = analyzerParam;
    }

    // Call this when settings changed.
    void setupPlot(AnalyzerParameters analyzerParam) {
        setupAxes(analyzerParam);
        spectrogramPlot.setupSpectrogram(analyzerParam);
    }

    void setAxisModeLinear(String mode) {
        // see also layout/*.xml
        boolean b = mode.equals("linear");
        ScreenPhysicalMapping.Type mapType;
        GridLabel.Type gridType;
        if (b) {
            mapType = ScreenPhysicalMapping.Type.LINEAR;
            gridType = GridLabel.Type.FREQ;
        } else {
            mapType = ScreenPhysicalMapping.Type.LOG;
            gridType = mode.equals("note") ? GridLabel.Type.FREQ_NOTE : GridLabel.Type.FREQ_LOG;
        }
        spectrumPlot.setFreqAxisMode(mapType, freq_lower_bound_for_log, gridType);
        spectrogramPlot.setFreqAxisMode(mapType, freq_lower_bound_for_log, gridType);
        if (showMode == PlotMode.SPECTRUM) {
            xZoom = spectrumPlot.axisX.getZoom();
            xShift = spectrumPlot.axisX.getShift();
        } else if (showMode == PlotMode.SPECTROGRAM) {
            if (spectrogramPlot.showFreqAlongX) {
                xZoom = spectrogramPlot.axisFreq.getZoom();
                xShift = spectrogramPlot.axisFreq.getShift();
            } else {
                yZoom = spectrogramPlot.axisFreq.getZoom();
                yShift = spectrogramPlot.axisFreq.getShift();
            }
        }
    }

    public void setShowFreqAlongX(boolean b) {
        spectrogramPlot.setShowFreqAlongX(b);
        if (showMode == PlotMode.SPECTRUM)
            return;
        if (spectrogramPlot.showFreqAlongX) {
            xZoom = spectrogramPlot.axisFreq.getZoom();
            xShift = spectrogramPlot.axisFreq.getShift();
            yZoom = spectrogramPlot.axisTime.getZoom();
            yShift = spectrogramPlot.axisTime.getShift();
        } else {
            xZoom = spectrogramPlot.axisTime.getZoom();
            xShift = spectrogramPlot.axisTime.getShift();
            yZoom = spectrogramPlot.axisFreq.getZoom();
            yShift = spectrogramPlot.axisFreq.getShift();
        }
    }

    // Note: Assume setupPlot() was called once.
    public void switch2Spectrum() {
        if (showMode == PlotMode.SPECTRUM) {
            return;
        }
        Log.v(TAG, "switch2Spectrum()");
        // execute when switch from Spectrogram mode to Spectrum mode
        showMode = PlotMode.SPECTRUM;
        xZoom = spectrogramPlot.axisFreq.getZoom();
        xShift = spectrogramPlot.axisFreq.getShift();
        if (!spectrogramPlot.showFreqAlongX) {
            xShift = 1 - 1 / xZoom - xShift;
        }
        spectrumPlot.axisX.setZoomShift(xZoom, xShift);
        yZoom = spectrumPlot.axisY.getZoom();
        yShift = spectrumPlot.axisY.getShift();
    }

    // Note: Assume setupPlot() was called once.
    public void switch2Spectrogram() {
        if (showMode == PlotMode.SPECTRUM && canvasHeight > 0) {
            // canvasHeight==0 means the program is just start
            Log.v(TAG, "switch2Spectrogram()");
            if (spectrogramPlot.showFreqAlongX) {
                // no need to change x scaling
                yZoom = spectrogramPlot.axisTime.getZoom();
                yShift = spectrogramPlot.axisTime.getShift();
                spectrogramPlot.axisFreq.setZoomShift(xZoom, xShift);
            } else {
                // noinspection SuspiciousNameCombination
                yZoom = xZoom;
                // axisFreq is reverted
                yShift = 1 - 1 / xZoom - xShift;
                xZoom = spectrogramPlot.axisTime.getZoom();
                xShift = spectrogramPlot.axisTime.getShift();
                spectrogramPlot.axisFreq.setZoomShift(yZoom, yShift);
            }
        }
        spectrogramPlot.spectrogramBMP.updateAxis(spectrogramPlot.axisFreq);
        spectrogramPlot.prepare();
        showMode = PlotMode.SPECTROGRAM;
    }

    double[] setViewRange(double[] _ranges, double[] rangesDefault) {
        if (_ranges.length < VIEW_RANGE_DATA_LENGTH) {
            Log.i(TAG, "setViewRange(): invalid input.");
            return null;
        }
        // do not modify input parameter
        double[] ranges = new double[VIEW_RANGE_DATA_LENGTH];
        System.arraycopy(_ranges, 0, ranges, 0, VIEW_RANGE_DATA_LENGTH);
        if (rangesDefault != null) {
            // Sanity check
            if (rangesDefault.length != 2 * VIEW_RANGE_DATA_LENGTH) {
                Log.i(TAG, "setViewRange(): invalid input.");
                return null;
            }
            for (int i = 0; i < 6; i += 2) {
                if (ranges[i] > ranges[i + 1]) {
                    // order reversed
                    double t = ranges[i];
                    ranges[i] = ranges[i + 1];
                    ranges[i + 1] = t;
                }
                // lower  than lower bound
                if (ranges[i] < rangesDefault[i + 6])
                    ranges[i] = rangesDefault[i + 6];
                // all lower  than lower bound?
                if (ranges[i + 1] < rangesDefault[i + 6])
                    ranges[i + 1] = rangesDefault[i + 7];
                // all higher than upper bound?
                if (ranges[i] > rangesDefault[i + 7])
                    ranges[i] = rangesDefault[i + 6];
                // higher than upper bound
                if (ranges[i + 1] > rangesDefault[i + 7])
                    ranges[i + 1] = rangesDefault[i + 7];
                if (ranges[i] == ranges[i + 1] || Double.isNaN(ranges[i]) || Double.isNaN(ranges[i + 1])) {
                    // invalid input value
                    ranges[i] = rangesDefault[i];
                    ranges[i + 1] = rangesDefault[i + 1];
                }
            }
        }
        // Set range
        if (showMode == PlotMode.SPECTRUM) {
            spectrumPlot.axisX.setViewBounds(ranges[0], ranges[1]);
            // reversed
            spectrumPlot.axisY.setViewBounds(ranges[3], ranges[2]);
        } else if (showMode == PlotMode.SPECTROGRAM) {
            if (spectrogramPlot.getSpectrogramMode() == SpectrogramPlot.TimeAxisMode.SHIFT) {
                spectrogramPlot.axisTime.setViewBounds(ranges[5], ranges[4]);
            } else {
                spectrogramPlot.axisTime.setViewBounds(ranges[4], ranges[5]);
            }
            if (spectrogramPlot.showFreqAlongX) {
                spectrogramPlot.axisFreq.setViewBounds(ranges[0], ranges[1]);
            } else {
                spectrogramPlot.axisFreq.setViewBounds(ranges[1], ranges[0]);
            }
            spectrogramPlot.spectrogramBMP.updateAxis(spectrogramPlot.axisFreq);
        }
        // Set zoom shift for view
        if (showMode == PlotMode.SPECTRUM) {
            xZoom = spectrumPlot.axisX.getZoom();
            xShift = spectrumPlot.axisX.getShift();
            yZoom = spectrumPlot.axisY.getZoom();
            yShift = spectrumPlot.axisY.getShift();
        } else if (showMode == PlotMode.SPECTROGRAM) {
            if (spectrogramPlot.showFreqAlongX) {
                xZoom = spectrogramPlot.axisFreq.getZoom();
                xShift = spectrogramPlot.axisFreq.getShift();
                yZoom = spectrogramPlot.axisTime.getZoom();
                yShift = spectrogramPlot.axisTime.getShift();
            } else {
                yZoom = spectrogramPlot.axisFreq.getZoom();
                yShift = spectrogramPlot.axisFreq.getShift();
                xZoom = spectrogramPlot.axisTime.getZoom();
                xShift = spectrogramPlot.axisTime.getShift();
            }
        }
        return ranges;
    }

    private void updateAxisZoomShift() {
        if (showMode == PlotMode.SPECTRUM) {
            spectrumPlot.setZooms(xZoom, xShift, yZoom, yShift);
        } else {
            spectrogramPlot.setZooms(xZoom, xShift, yZoom, yShift);
        }
    }

    public void setSmoothRender(boolean b) {
        spectrogramPlot.setSmoothRender(b);
    }

    public PlotMode getShowMode() {
        return showMode;
    }

    public void setTimeMultiplier(int nAve) {
        spectrogramPlot.setTimeMultiplier(nAve);
    }

    public void setShowTimeAxis(boolean bSTA) {
        spectrogramPlot.setShowTimeAxis(bSTA);
    }

    public void setSpectrogramModeShifting(boolean b) {
        spectrogramPlot.setSpectrogramModeShifting(b);
    }

    void setLogAxisMode(boolean b) {
        SpectrogramBMP.LogAxisPlotMode mode = SpectrogramBMP.LogAxisPlotMode.REPLOT;
        if (!b) {
            mode = SpectrogramBMP.LogAxisPlotMode.SEGMENT;
        }
        spectrogramPlot.spectrogramBMP.setLogAxisMode(mode);
    }

    void setColorMap(String colorMapName) {
        spectrogramPlot.setColorMap(colorMapName);
    }

    static boolean isBusy() {
        return isBusy;
    }

    static void setIsBusy(boolean b) {
        isBusy = b;
    }

    private final FPSCounter fpsCounter = new FPSCounter("AnalyzerGraphic");

    @Override
    protected void onDraw(Canvas c) {
        fpsCounter.inc();
        isBusy = true;
        if (showMode == PlotMode.SPECTRUM) {
            spectrumPlot.addCalibCurve(analyzerParamCache.micGainDB, null, analyzerParamCache.calibName);
            spectrumPlot.drawSpectrumPlot(c, savedDBSpectrum);
        } else {
            spectrogramPlot.drawSpectrogramPlot(c);
        }
        isBusy = false;
    }

    // All FFT data will enter this view through this interface
    // Will be called in another thread (SamplingLoop)
    public void saveSpectrum(double[] db) {
        synchronized (savedDBSpectrum) {
            // TODO: need lock on savedDBSpectrum, but how?
            if (savedDBSpectrum == null || savedDBSpectrum.length != db.length) {
                savedDBSpectrum = new double[db.length];
            }
            System.arraycopy(db, 0, savedDBSpectrum, 0, db.length);
        }
        // TODO: Run on another thread? Lock on data ? Or use CompletionService?
        if (showMode == PlotMode.SPECTROGRAM) {
            spectrogramPlot.saveRowSpectrumAsColor(savedDBSpectrum);
        }
    }

    void setSpectrumDBLowerBound(double b) {
        spectrumPlot.axisY.vUpperBound = b;
    }

    void setSpectrogramDBLowerBound(double b) {
        spectrogramPlot.setSpectrogramDBLowerBound(b);
    }

    public void setShowLines(boolean b) {
        spectrumPlot.showLines = b;
    }

    private boolean intersects(float x, float y) {
        getLocationOnScreen(myLocation);
        return x >= myLocation[0] && y >= myLocation[1] && x < myLocation[0] + getWidth() && y < myLocation[1] + getHeight();
    }

    // return true if the coordinate (x,y) is inside graphView
    public boolean setCursor(float x, float y) {
        if (intersects(x, y)) {
            x = x - myLocation[0];
            y = y - myLocation[1];
            // Convert to coordinate in axis
            if (showMode == PlotMode.SPECTRUM) {
                spectrumPlot.setCursor(x, y);
            } else {
                spectrogramPlot.setCursor(x, y);
            }
            return true;
        } else {
            return false;
        }
    }

    public double getCursorFreq() {
        if (showMode == PlotMode.SPECTRUM) {
            return spectrumPlot.getCursorFreq();
        } else {
            return spectrogramPlot.getCursorFreq();
        }
    }

    public double getCursorDB() {
        if (showMode == PlotMode.SPECTRUM) {
            return spectrumPlot.getCursorDB();
        } else {
            return 0;
        }
    }

    public void hideCursor() {
        spectrumPlot.hideCursor();
        spectrogramPlot.hideCursor();
    }

    double[] getViewPhysicalRange() {
        double[] r = new double[12];
        if (getShowMode() == AnalyzerGraphic.PlotMode.SPECTRUM) {
            // fL, fU, dBL dBU, time L, time U
            r[0] = spectrumPlot.axisX.vMinInView();
            r[1] = spectrumPlot.axisX.vMaxInView();
            // reversed
            r[2] = spectrumPlot.axisY.vMaxInView();
            r[3] = spectrumPlot.axisY.vMinInView();
            r[4] = 0;
            r[5] = 0;
            // Limits of fL, fU, dBL dBU, time L, time U
            r[6] = spectrumPlot.axisX.vLowerBound;
            r[7] = spectrumPlot.axisX.vUpperBound;
            r[8] = AnalyzerGraphic.minDB;
            r[9] = AnalyzerGraphic.maxDB;
            r[10] = 0;
            r[11] = 0;
        } else {
            r[0] = spectrogramPlot.axisFreq.vMinInView();
            r[1] = spectrogramPlot.axisFreq.vMaxInView();
            if (r[0] > r[1]) {
                double t = r[0];
                r[0] = r[1];
                r[1] = t;
            }
            r[2] = spectrogramPlot.spectrogramBMP.dBLowerBound;
            r[3] = spectrogramPlot.spectrogramBMP.dBUpperBound;
            r[4] = spectrogramPlot.axisTime.vMinInView();
            r[5] = spectrogramPlot.axisTime.vMaxInView();
            r[6] = spectrogramPlot.axisFreq.vLowerBound;
            r[7] = spectrogramPlot.axisFreq.vUpperBound;
            if (r[6] > r[7]) {
                double t = r[6];
                r[6] = r[7];
                r[7] = t;
            }
            r[8] = AnalyzerGraphic.minDB;
            r[9] = AnalyzerGraphic.maxDB;
            r[10] = spectrogramPlot.axisTime.vLowerBound;
            r[11] = spectrogramPlot.axisTime.vUpperBound;
        }
        for (int i = 6; i < r.length; i += 2) {
            if (r[i] > r[i + 1]) {
                double t = r[i];
                r[i] = r[i + 1];
                r[i + 1] = t;
            }
        }
        return r;
    }

    public double getXZoom() {
        return xZoom;
    }

    public double getYZoom() {
        return yZoom;
    }

    public double getXShift() {
        return xShift;
    }

    public double getYShift() {
        return yShift;
    }

    public double getCanvasWidth() {
        if (showMode == PlotMode.SPECTRUM) {
            return canvasWidth;
        } else {
            return canvasWidth - spectrogramPlot.labelBeginX;
        }
    }

    public double getCanvasHeight() {
        if (showMode == PlotMode.SPECTRUM) {
            return canvasHeight;
        } else {
            return spectrogramPlot.labelBeginY;
        }
    }

    private double clamp(double x, double min, double max) {
        if (x > max) {
            return max;
        } else if (x < min) {
            return min;
        } else {
            return x;
        }
    }

    private double clampXShift(double offset) {
        return clamp(offset, 0f, 1 - 1 / xZoom);
    }

    private double clampYShift(double offset) {
        if (showMode == PlotMode.SPECTRUM) {
            // limit view to minDB ~ maxDB, assume linear in dB scale
            return clamp(offset, (maxDB - spectrumPlot.axisY.vLowerBound) / spectrumPlot.axisY.diffVBounds(), (minDB - spectrumPlot.axisY.vLowerBound) / spectrumPlot.axisY.diffVBounds() - 1 / yZoom);
        } else {
            // strict restrict, y can be frequency or time.
            return clamp(offset, 0f, 1 - 1 / yZoom);
        }
    }

    public void setXShift(double offset) {
        xShift = clampXShift(offset);
        updateAxisZoomShift();
    }

    public void setYShift(double offset) {
        yShift = clampYShift(offset);
        updateAxisZoomShift();
    }

    public void resetViewScale() {
        xShift = 0;
        xZoom = 1;
        yShift = 0;
        yZoom = 1;
        updateAxisZoomShift();
    }

    private double xMidOld = 100;

    private double xDiffOld = 100;

    private double xZoomOld = 1;

    private double xShiftOld = 0;

    private double yMidOld = 100;

    private double yDiffOld = 100;

    private double yZoomOld = 1;

    private double yShiftOld = 0;

    // record the coordinate frame state when starting scaling
    public void setShiftScaleBegin(double x1, double y1, double x2, double y2) {
        xMidOld = (x1 + x2) / 2f;
        xDiffOld = Math.abs(x1 - x2);
        xZoomOld = xZoom;
        xShiftOld = xShift;
        yMidOld = (y1 + y2) / 2f;
        yDiffOld = Math.abs(y1 - y2);
        yZoomOld = yZoom;
        yShiftOld = yShift;
    }

    // Do the scaling according to the motion event getX() and getY() (getPointerCount()==2)
    public void setShiftScale(double x1, double y1, double x2, double y2) {
        double limitXZoom;
        double limitYZoom;
        if (showMode == PlotMode.SPECTRUM) {
            // limit to 200 Hz a screen
            limitXZoom = spectrumPlot.axisX.diffVBounds() / 200f;
            // limit to 6 dB a screen
            limitYZoom = -spectrumPlot.axisY.diffVBounds() / 6f;
        } else {
            int nTimePoints = spectrogramPlot.nTimePoints;
            if (spectrogramPlot.showFreqAlongX) {
                limitXZoom = spectrogramPlot.axisFreq.diffVBounds() / 200f;
                limitYZoom = nTimePoints > 10 ? nTimePoints / 10 : 1;
            } else {
                limitXZoom = nTimePoints > 10 ? nTimePoints / 10 : 1;
                limitYZoom = spectrogramPlot.axisFreq.diffVBounds() / 200f;
            }
        }
        limitXZoom = Math.abs(limitXZoom);
        limitYZoom = Math.abs(limitYZoom);
        if (canvasWidth * 0.13f < xDiffOld) {
            // if fingers are not very close in x direction, do scale in x direction
            xZoom = clamp(xZoomOld * Math.abs(x1 - x2) / xDiffOld, 1f, limitXZoom);
        }
        xShift = clampXShift(xShiftOld + (xMidOld / xZoomOld - (x1 + x2) / 2f / xZoom) / canvasWidth);
        if (canvasHeight * 0.13f < yDiffOld) {
            // if fingers are not very close in y direction, do scale in y direction
            yZoom = clamp(yZoomOld * Math.abs(y1 - y2) / yDiffOld, 1f, limitYZoom);
        }
        yShift = clampYShift(yShiftOld + (yMidOld / yZoomOld - (y1 + y2) / 2f / yZoom) / canvasHeight);
        updateAxisZoomShift();
    }

    // callback to caller when rendering is complete
    private Ready readyCallback = null;

    public void setReady(Ready ready) {
        this.readyCallback = ready;
    }

    interface Ready {

        void ready();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.i(TAG, "onSizeChanged(): canvas (" + oldw + "," + oldh + ") -> (" + w + "," + h + ")");
        isBusy = true;
        this.canvasWidth = w;
        this.canvasHeight = h;
        spectrumPlot.setCanvas(w, h, null);
        spectrogramPlot.setCanvas(w, h, null);
        if (h > 0 && readyCallback != null) {
            readyCallback.ready();
        }
        isBusy = false;
    }

    /*
   * Save freqAxisAlongX, cursors, zooms, and current spectrogram/spectrum.
   * All other properties will be set in onCreate() and onResume().
   * Will be called after onPause() or between onCreat() and on onResume()
   * Ref. http://example.com
   */
    @Override
    protected Parcelable onSaveInstanceState() {
        Log.i(TAG, "onSaveInstanceState(): xShift = " + xShift + "  xZoom = " + xZoom + "  yShift = " + yShift + "  yZoom = " + yZoom);
        Parcelable parentState = super.onSaveInstanceState();
        SavedState state = new SavedState(parentState);
        state.freqAxisAlongX = spectrogramPlot.showFreqAlongX ? 1 : 0;
        state.cFreqSpum = spectrumPlot.cursorFreq;
        state.cFreqSpam = spectrogramPlot.cursorFreq;
        state.cDb = spectrumPlot.cursorDB;
        state.xZ = xZoom;
        state.xS = xShift;
        state.yZ = yZoom;
        state.yS = yShift;
        state.SpumXZ = spectrumPlot.axisX.getZoom();
        state.SpumXS = spectrumPlot.axisX.getShift();
        state.SpumYZ = spectrumPlot.axisY.getZoom();
        state.SpumYS = spectrumPlot.axisY.getShift();
        state.SpamFZ = spectrogramPlot.axisFreq.getZoom();
        state.SpamFS = spectrogramPlot.axisFreq.getShift();
        state.SpamTZ = spectrogramPlot.axisTime.getZoom();
        state.SpamTS = spectrogramPlot.axisTime.getShift();
        state.tmpS = savedDBSpectrum;
        state.nFreq = spectrogramPlot.nFreqPoints;
        state.nTime = spectrogramPlot.nTimePoints;
        state.iTimePinter = spectrogramPlot.spectrogramBMP.spectrumStore.iTimePointer;
        final short[] tmpSC = spectrogramPlot.spectrogramBMP.spectrumStore.dbShortArray;
        // Note no ByteBuffer view of ShortBuffer, see
        byte[] input = new byte[tmpSC.length * 2];
        for (int i = 0; i < tmpSC.length; i++) {
            input[2 * i] = (byte) (tmpSC[i] & 0xff);
            input[2 * i + 1] = (byte) (tmpSC[i] >> 8);
        }
        // Save spectrumStore.dbShortArray to a file.
        File tmpSCPath = new File(context.getCacheDir(), "spectrogram_short.raw");
        try {
            OutputStream fout = new FileOutputStream(tmpSCPath);
            fout.write(input);
            fout.close();
        } catch (IOException e) {
            Log.w("SavedState:", "writeToParcel(): Fail to save state to file.");
        }
        return state;
    }

    // Will be called after setup(), during AnalyzerActivity.onCreate()?
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof SavedState) {
            SavedState s = (SavedState) state;
            super.onRestoreInstanceState(s.getSuperState());
            spectrogramPlot.showFreqAlongX = s.freqAxisAlongX == 1;
            spectrumPlot.cursorFreq = s.cFreqSpum;
            spectrogramPlot.cursorFreq = s.cFreqSpam;
            spectrumPlot.cursorDB = s.cDb;
            xZoom = s.xZ;
            xShift = s.xS;
            yZoom = s.yZ;
            yShift = s.yS;
            spectrumPlot.axisX.setZoomShift(s.SpumXZ, s.SpumXS);
            spectrumPlot.axisY.setZoomShift(s.SpumYZ, s.SpumYS);
            spectrogramPlot.axisFreq.setZoomShift(s.SpamFZ, s.SpamFS);
            spectrogramPlot.axisTime.setZoomShift(s.SpamTZ, s.SpamTS);
            savedDBSpectrum = s.tmpS;
            spectrogramPlot.nFreqPoints = s.nFreq;
            spectrogramPlot.nTimePoints = s.nTime;
            final SpectrogramBMP sBMP = spectrogramPlot.spectrogramBMP;
            final SpectrogramBMP.SpectrumCompressStore sBMPS = sBMP.spectrumStore;
            // prevent reinitialize of LogFreqSpectrogramBMP
            sBMPS.nFreq = s.nFreq;
            sBMPS.nTime = s.nTime;
            sBMPS.iTimePointer = s.iTimePinter;
            // Read temporary saved spectrogram
            // length of spectrumStore.dbShortArray
            byte[] input = new byte[(s.nFreq + 1) * s.nTime * 2];
            int bytesRead = -1;
            File tmpSCPath = new File(context.getCacheDir(), "spectrogram_short.raw");

            if (bytesRead != input.length) {
                // fail to get saved spectrogram, have a new start
                sBMPS.nFreq = 0;
                sBMPS.nTime = 0;
                sBMPS.iTimePointer = 0;
            } else {
                // we have data!
                short[] tmpSC = new short[input.length / 2];
                for (int i = 0; i < tmpSC.length; i++) {
                    tmpSC[i] = (short) (input[2 * i] + (input[2 * i + 1] << 8));
                }
                sBMPS.dbShortArray = tmpSC;
                sBMP.rebuildLinearBMP();
            }
            Log.i(TAG, "onRestoreInstanceState(): xShift = " + xShift + "  xZoom = " + xZoom + "  yShift = " + yShift + "  yZoom = " + yZoom);
        } else {
            Log.i(TAG, "onRestoreInstanceState(): not SavedState?!");
            super.onRestoreInstanceState(state);
        }
    }

    private static class SavedState extends BaseSavedState {

        int freqAxisAlongX;

        double cFreqSpum;

        double cFreqSpam;

        double cDb;

        double xZ;

        double xS;

        double yZ;

        double yS;

        double SpumXZ;

        double SpumXS;

        double SpumYZ;

        double SpumYS;

        double SpamFZ;

        double SpamFS;

        double SpamTZ;

        double SpamTS;

        double[] tmpS;

        int nFreq;

        int nTime;

        int iTimePinter;

        SavedState(Parcelable state) {
            super(state);
        }

        private SavedState(Parcel in) {
            super(in);
            freqAxisAlongX = in.readInt();
            cFreqSpum = in.readDouble();
            cFreqSpam = in.readDouble();
            cDb = in.readDouble();
            xZ = in.readDouble();
            xS = in.readDouble();
            yZ = in.readDouble();
            yS = in.readDouble();
            SpumXZ = in.readDouble();
            SpumXS = in.readDouble();
            SpumYZ = in.readDouble();
            SpumYS = in.readDouble();
            SpamFZ = in.readDouble();
            SpamFS = in.readDouble();
            SpamTZ = in.readDouble();
            SpamTS = in.readDouble();
            tmpS = in.createDoubleArray();
            nFreq = in.readInt();
            nTime = in.readInt();
            iTimePinter = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(freqAxisAlongX);
            out.writeDouble(cFreqSpum);
            out.writeDouble(cFreqSpam);
            out.writeDouble(cDb);
            out.writeDouble(xZ);
            out.writeDouble(xS);
            out.writeDouble(yZ);
            out.writeDouble(yS);
            out.writeDouble(SpumXZ);
            out.writeDouble(SpumXS);
            out.writeDouble(SpumYZ);
            out.writeDouble(SpumYS);
            out.writeDouble(SpamFZ);
            out.writeDouble(SpamFS);
            out.writeDouble(SpamTZ);
            out.writeDouble(SpamTS);
            out.writeDoubleArray(tmpS);
            out.writeInt(nFreq);
            out.writeInt(nTime);
            out.writeInt(iTimePinter);
        }

        // Standard creator object using an instance of this class
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {

            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}


public int deleteRoleByIds(Long[] roleIds);

package com.lei.project.system.service;

import java.util.List;
import java.util.Set;
import com.lei.project.system.domain.SysRole;

/**
 * 角色业务层
 *
 * @author lei
 */
public interface ISysRoleService {

    /**
     * 根据条件分页查询角色数据
     *
     * @param role 角色信息
     * @return 角色数据集合信息
     */
    public List<SysRole> selectRoleList(SysRole role);

    /**
     * 根据用户ID查询角色
     *
     * @param userId 用户ID
     * @return 权限列表
     */
    public Set<String> selectRolePermissionByUserId(Long userId);

    public List<SysRole> selectRoleAll();

    /**
     * 根据用户ID获取角色选择框列表
     *
     * @param userId 用户ID
     * @return 选中角色ID列表
     */
    public List<Integer> selectRoleListByUserId(Long userId);

    /**
     * 通过角色ID查询角色
     *
     * @param roleId 角色ID
     * @return 角色对象信息
     */
    public SysRole selectRoleById(Long roleId);

    /**
     * 校验角色名称是否唯一
     *
     * @param role 角色信息
     * @return 结果
     */
    public String checkRoleNameUnique(SysRole role);

    /**
     * 校验角色权限是否唯一
     *
     * @param role 角色信息
     * @return 结果
     */
    public String checkRoleKeyUnique(SysRole role);

    /**
     * 校验角色是否允许操作
     *
     * @param role 角色信息
     */
    public void checkRoleAllowed(SysRole role);

    /**
     * 通过角色ID查询角色使用数量
     *
     * @param roleId 角色ID
     * @return 结果
     */
    public int countUserRoleByRoleId(Long roleId);

    /**
     * 新增保存角色信息
     *
     * @param role 角色信息
     * @return 结果
     */
    public int insertRole(SysRole role);

    /**
     * 修改保存角色信息
     *
     * @param role 角色信息
     * @return 结果
     */
    public int updateRole(SysRole role);

    /**
     * 修改角色状态
     *
     * @param role 角色信息
     * @return 结果
     */
    public int updateRoleStatus(SysRole role);

    /**
     * 修改数据权限信息
     *
     * @param role 角色信息
     * @return 结果
     */
    public int authDataScope(SysRole role);

    /**
     * 通过角色ID删除角色
     *
     * @param roleId 角色ID
     * @return 结果
     */
    public int deleteRoleById(Long roleId);

    /**
     * 批量删除角色信息
     *
     * @param roleIds 需要删除的角色ID
     * @return 结果
     */


public final /*@pure@*/
    double getBig() {
        return big;
    }

/**
 * Blind of the hand.
 */
public class Blind {

    /**
     * The big.
     */
    private double /*@spec_public@*/
    big;

    /**
     * The small.
     */
    private double /*@spec_public@*/
    small;

    /**
     * The ante.
     */
    private double /*@spec_public@*/
    ante;

    /**
     * Creates a new blind.
     * @param smallBlind
     *          the size of the small blind.
     * @param bigBlind
     *          the size of the big blind.
     * @param anteSize
     *          the size of ante.
     */
    /*@ public normal_behavior
      requires smallBlind >= 0;
      requires bigBlind >= 0;
      requires anteSize >=0;
      assignable small, big, ante;
      ensures small == smallBlind;
      ensures big == bigBlind;
      ensures ante == anteSize;
    */
    public Blind(final double smallBlind, final double bigBlind, final double anteSize) {
        this.small = smallBlind;
        this.big = bigBlind;
        this.ante = anteSize;
    }

    /**
     * @return the ante
     */
    /*@ public normal_behavior
      requires ante >= 0;
      ensures 0 <= \result;
      ensures ante == \result;
   */
    public final /*@pure@*/
    double getAnte() {
        return ante;
    }

    /**
     * @param newAnte the new ante
     */
    public final void setAnte(final double newAnte) {
        this.ante = newAnte;
    }

    /**
     * @return the big blind
     */
    /*@ public normal_behavior
      requires big >= 0;
      ensures 0 <= \result;
      ensures big == \result;
   */

    /**
     * @param newBig the new value of big blind.
     */
    public final void setBig(final double newBig) {
        this.big = newBig;
    }

    /**
     * @return the small
     */
    /*@ public normal_behavior
      requires small >= 0;
      ensures 0 <= \result;
      ensures small == \result;
   */
    public final /*@pure@*/
    double getSmall() {
        return small;
    }

    /**
     * @param newSmall the new small blind value
     */
    public final void setSmall(final double newSmall) {
        this.small = newSmall;
    }
}


for (int i = 0; i < 4; i++) {
            double sensorHeading = AngleUtils.normalizeRadians(headingRadians + i * piOver2);
            distanceSensors[i].updateDistance(x - halfBotWidth * Math.sin(sensorHeading), y + halfBotWidth * Math.cos(sensorHeading), sensorHeading);
        }

package virtual_robot.robots.classes;

import com.qualcomm.hardware.bosch.BNO055IMUImpl;
import com.qualcomm.robotcore.hardware.DcMotorEx;
import com.qualcomm.robotcore.hardware.DcMotorExImpl;
import com.qualcomm.robotcore.hardware.HardwareMap;
import com.qualcomm.robotcore.hardware.configuration.MotorType;
import virtual_robot.controller.VirtualBot;
import virtual_robot.controller.VirtualRobotController;
import virtual_robot.util.AngleUtils;

/**
 * For internal use only. Represents a robot with four mechanum wheels, color sensor, four distance sensors,
 * a BNO055IMU, and a Servo-controlled arm on the back.
 */
public class MechanumBase extends VirtualBot {

    public final MotorType MOTOR_TYPE;

    private DcMotorExImpl[] motors = null;

    private BNO055IMUImpl imu = null;

    private VirtualRobotController.ColorSensorImpl colorSensor = null;

    private VirtualRobotController.DistanceSensorImpl[] distanceSensors = null;

    private double wheelCircumference;

    protected double gearRatioWheel = 1.0;

    private double interWheelWidth;

    private double interWheelLength;

    private double wlAverage;

    // Transform from wheel motion to robot motion
    private double[][] tWR;

    /**
     * No-param constructor. Uses the default motor type of Neverest 40
     */
    public MechanumBase() {
        super();
        MOTOR_TYPE = MotorType.Neverest40;
    }

    public MechanumBase(MotorType driveMotorType) {
        MOTOR_TYPE = driveMotorType;
    }

    public void initialize() {
        super.initialize();
        hardwareMap.setActive(true);
        motors = new DcMotorExImpl[] { (DcMotorExImpl) hardwareMap.get(DcMotorEx.class, "back_left_motor"), (DcMotorExImpl) hardwareMap.get(DcMotorEx.class, "front_left_motor"), (DcMotorExImpl) hardwareMap.get(DcMotorEx.class, "front_right_motor"), (DcMotorExImpl) hardwareMap.get(DcMotorEx.class, "back_right_motor") };
        distanceSensors = new VirtualRobotController.DistanceSensorImpl[] { hardwareMap.get(VirtualRobotController.DistanceSensorImpl.class, "front_distance"), hardwareMap.get(VirtualRobotController.DistanceSensorImpl.class, "left_distance"), hardwareMap.get(VirtualRobotController.DistanceSensorImpl.class, "back_distance"), hardwareMap.get(VirtualRobotController.DistanceSensorImpl.class, "right_distance") };
        imu = hardwareMap.get(BNO055IMUImpl.class, "imu");
        colorSensor = (VirtualRobotController.ColorSensorImpl) hardwareMap.colorSensor.get("color_sensor");
        wheelCircumference = Math.PI * botWidth / 4.5;
        interWheelWidth = botWidth * 8.0 / 9.0;
        interWheelLength = botWidth * 7.0 / 9.0;
        wlAverage = (interWheelLength + interWheelWidth) / 2.0;
        tWR = new double[][] { { -0.25, 0.25, -0.25, 0.25 }, { 0.25, 0.25, 0.25, 0.25 }, { -0.25 / wlAverage, -0.25 / wlAverage, 0.25 / wlAverage, 0.25 / wlAverage }, { -0.25, 0.25, 0.25, -0.25 } };
        hardwareMap.setActive(false);
    }

    protected void createHardwareMap() {
        hardwareMap = new HardwareMap();
        String[] motorNames = new String[] { "back_left_motor", "front_left_motor", "front_right_motor", "back_right_motor" };
        for (String name : motorNames) hardwareMap.put(name, new DcMotorExImpl(MOTOR_TYPE));
        String[] distNames = new String[] { "front_distance", "left_distance", "back_distance", "right_distance" };
        for (String name : distNames) hardwareMap.put(name, controller.new DistanceSensorImpl());
        hardwareMap.put("imu", new BNO055IMUImpl(this, 10));
        hardwareMap.put("color_sensor", controller.new ColorSensorImpl());
    }

    public synchronized void updateStateAndSensors(double millis) {
        double[] deltaPos = new double[4];
        double[] w = new double[4];
        for (int i = 0; i < 4; i++) {
            deltaPos[i] = motors[i].update(millis);
            w[i] = deltaPos[i] * wheelCircumference * gearRatioWheel / motors[i].MOTOR_TYPE.TICKS_PER_ROTATION;
            if (i < 2)
                w[i] = -w[i];
        }
        double[] robotDeltaPos = new double[] { 0, 0, 0, 0 };
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                robotDeltaPos[i] += tWR[i][j] * w[j];
            }
        }
        double dxR = robotDeltaPos[0];
        double dyR = robotDeltaPos[1];
        double headingChange = robotDeltaPos[2];
        double avgHeading = headingRadians + headingChange / 2.0;
        double sin = Math.sin(avgHeading);
        double cos = Math.cos(avgHeading);
        x += dxR * cos - dyR * sin;
        y += dxR * sin + dyR * cos;
        headingRadians += headingChange;
        if (headingRadians > Math.PI)
            headingRadians -= 2.0 * Math.PI;
        else if (headingRadians < -Math.PI)
            headingRadians += 2.0 * Math.PI;
        constrainToBoundaries();
        imu.updateHeadingRadians(headingRadians);
        colorSensor.updateColor(x, y);
        final double piOver2 = Math.PI / 2.0;

    }

    public synchronized void updateDisplay() {
        super.updateDisplay();
    }

    public void powerDownAndReset() {
        for (int i = 0; i < 4; i++) motors[i].stopAndReset();
        imu.close();
    }
}