diff --git a/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java index 27e5af6b..cc0061ab 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java @@ -21,12 +21,15 @@ package io.github.dsheirer.gui.viewer; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierCollection; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javafx.util.Callback; /** * JavaFX identifier collection viewer @@ -82,7 +85,19 @@ public class IdentifierCollectionViewer extends VBox valueColumn.setText("Value"); valueColumn.setCellValueFactory(new PropertyValueFactory<>("value")); - mIdentifierTableView.getColumns().addAll(classColumn, formColumn, roleColumn, valueColumn); + TableColumn textColumn = new TableColumn(); + textColumn.setPrefWidth(160); + textColumn.setText("Text"); + textColumn.setCellValueFactory((Callback, String>, ObservableValue>) param -> { + if(param.getValue() != null) + { + return new SimpleStringProperty(param.getValue().toString()); + } + + return null; + }); + + mIdentifierTableView.getColumns().addAll(classColumn, formColumn, roleColumn, valueColumn, textColumn); } return mIdentifierTableView; diff --git a/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java b/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java index 9ebab4ae..b1673368 100644 --- a/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java @@ -72,12 +72,22 @@ public abstract class FullyQualifiedRadioIdentifier extends RadioIdentifier } /** - * Indicates if the radio identify is aliased with a persona value that is different from the radio ID. + * Indicates if the radio identity is aliased with a persona value that is different from the radio ID. * @return true if aliased. */ public boolean isAliased() { - return getValue() != mRadio; + return getValue() != 0 && getValue() != mRadio; + } + + /** + * Override the default behavior of RadioIdentifier.isValid() to allow for fully qualified SUID's with a local + * ID of zero which indicates an ISSI patch. + */ + @Override + public boolean isValid() + { + return true; } @Override diff --git a/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java b/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java index cdcfc8de..db1ff6bb 100644 --- a/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java @@ -71,12 +71,22 @@ public abstract class FullyQualifiedTalkgroupIdentifier extends TalkgroupIdentif } /** - * Indicates if the talkgroup identify is aliased with a persona value that is different from the talkgroup ID. + * Indicates if the talkgroup identity is aliased with a persona value that is different from the talkgroup ID. * @return true if aliased. */ public boolean isAliased() { - return getValue() != mTalkgroup; + return getValue() != 0 && getValue() != mTalkgroup; + } + + /** + * Override the default behavior of TalkgroupIdentifier.isValid() to allow for fully qualified TGID's with a local + * ID of zero which indicates an ISSI patch. + */ + @Override + public boolean isValid() + { + return true; } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java index 8d20d624..8b319643 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java @@ -32,7 +32,6 @@ import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent; -import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.scramble.ScrambleParameterIdentifier; import io.github.dsheirer.log.LoggingSuppressor; import io.github.dsheirer.message.IMessage; @@ -307,7 +306,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * @param timeslot for the channel. * @param isIdleNull to indicate if this close event is trigged by an IDLE/NULL message */ - public void closeP2CallEvent(long frequency, int timeslot, long timestamp, boolean isIdleNull) + public void closeP2CallEvent(long frequency, int timeslot, boolean isIdleNull) { /** * Hack: L3Harris systems can issue a channel grant on control/TS1 which creates an event for TS2 and then the @@ -325,21 +324,13 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I try { - DecodeEvent event; - if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) { - event = mTS1ChannelGrantEventMap.remove(frequency); + mTS1ChannelGrantEventMap.remove(frequency); } else { - event = mTS2ChannelGrantEventMap.remove(frequency); - } - - if(event != null) - { - event.end(timestamp); - broadcast(event); + mTS2ChannelGrantEventMap.remove(frequency); } } finally @@ -1410,55 +1401,50 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * Compares the TO role identifier(s) from each collection for equality. This is normally used to compare a call * update where we only compare the TO role. * - * @param collection1 containing a TO identifier - * @param collection2 containing a TO identifier + * @param existingIC containing a TO identifier + * @param nextIC containing a TO identifier * @return true if both collections contain a TO identifier and the TO identifiers are the same value */ - private boolean isSameCallUpdate(IdentifierCollection collection1, IdentifierCollection collection2) + private boolean isSameCallUpdate(IdentifierCollection existingIC, IdentifierCollection nextIC) { - Identifier toIdentifier1 = collection1.getToIdentifier(); - Identifier toIdentifier2 = collection2.getToIdentifier(); - return toIdentifier1 != null && toIdentifier1.equals(toIdentifier2); + Identifier existingTO = existingIC.getToIdentifier(); + Identifier nextTO = nextIC.getToIdentifier(); + return existingTO != null && existingTO.equals(nextTO); } /** * Compares the TO role identifier(s) from each collection for equality. This is normally used to compare a call * update where we only compare the TO role. * - * @param collection1 containing a TO identifier - * @param collection2 containing a TO identifier + * @param existingIC for the existing/previous event containing a TO identifier + * @param nextIC for the next event containing a TO identifier * @return true if both collections contain a TO identifier and the TO identifiers are the same value */ - private boolean isSameCallFull(IdentifierCollection collection1, IdentifierCollection collection2) + private boolean isSameCallFull(IdentifierCollection existingIC, IdentifierCollection nextIC) { - Identifier to1 = collection1.getToIdentifier(); - Identifier to2 = collection2.getToIdentifier(); + Identifier existingTO = existingIC.getToIdentifier(); + Identifier nextTO = nextIC.getToIdentifier(); - if(to1 != null && to1.equals(to2)) + if(existingTO != null && existingTO.equals(nextTO)) { - Identifier from1 = collection1.getFromIdentifier(); + Identifier existingFROM = existingIC.getFromIdentifier(); //If the FROM identifier hasn't yet been established, then this is the same call. We also ignore the //talker alias as a call identifier since on L3Harris systems they can transmit the talker alias before //they transmit the radio ID. - if(from1 == null || from1.getForm() == Form.TALKER_ALIAS) + if(existingFROM == null || existingFROM.getForm() == Form.TALKER_ALIAS) { return true; } - Identifier from2 = collection2.getFromIdentifier(); + Identifier nextFROM = nextIC.getFromIdentifier(); - if(from2 != null && from2.getForm() == Form.TALKER_ALIAS) + if(nextFROM != null && nextFROM.getForm() == Form.TALKER_ALIAS) { return true; } - if(from2 instanceof RadioIdentifier radio && radio.getValue() == 0) - { - return true; - } - - return from1.equals(from2); + return existingFROM.equals(nextFROM); } return false; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index 624c24d7..d876806e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -188,7 +188,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie protected void resetState() { super.resetState(); - closeCurrentCallEvent(System.currentTimeMillis(), true, false); + closeCurrentCallEvent(true, false); mEndPttOnFacchCounter = 0; } @@ -1274,7 +1274,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { if(mac instanceof EndPushToTalk) { - closeCurrentCallEvent(message.getTimestamp(), true, false); + closeCurrentCallEvent(true, false); if(message.getDataUnitID().isFACCH()) { @@ -1561,7 +1561,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { if(mac instanceof MacRelease mr) { - closeCurrentCallEvent(message.getTimestamp(), true, false); + closeCurrentCallEvent(true, false); broadcast(message, mac, DecodeEventType.COMMAND, (mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : " BY CONTROLLER")); @@ -1882,13 +1882,12 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie /** * Ends/closes the current call event. * - * @param timestamp of the message that indicates the event has ended. * @param resetIdentifiers to reset the FROM/TO identifiers (true) or reset just the FROM identifiers (false) * @param isIdleNull to indicate if the calling trigger is an IDLE/NULL message */ - private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers, boolean isIdleNull) + private void closeCurrentCallEvent(boolean resetIdentifiers, boolean isIdleNull) { - mTrafficChannelManager.closeP2CallEvent(getCurrentFrequency(), getTimeslot(), timestamp, isIdleNull); + mTrafficChannelManager.closeP2CallEvent(getCurrentFrequency(), getTimeslot(), isIdleNull); if(resetIdentifiers) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java index ee3f6d2e..32002d54 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java @@ -136,11 +136,11 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantUpdate; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupDeleteCommand; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupExtendedFunctionCommand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupOpcode145; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupTalkerAlias; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupTalkerAliasContinuation; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUpdate; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaOpcode149; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse; import io.github.dsheirer.module.decode.p25.reference.Vendor; import java.util.ArrayList; @@ -585,9 +585,9 @@ public class MacMessageFactory case MOTOROLA_89_GROUP_REGROUP_DELETE: return new MotorolaGroupRegroupDeleteCommand(message, offset); case MOTOROLA_91_GROUP_REGROUP_UNKNOWN: - return new MotorolaGroupRegroupOpcode145(message, offset); + return new MotorolaGroupRegroupTalkerAlias(message, offset); case MOTOROLA_95_UNKNOWN_149: - return new MotorolaOpcode149(message, offset); + return new MotorolaGroupRegroupTalkerAliasContinuation(message, offset); case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED: return new MotorolaGroupRegroupVoiceChannelUserExtended(message, offset); case MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupOpcode145.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupTalkerAlias.java similarity index 92% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupOpcode145.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupTalkerAlias.java index 3f8bb30f..2e32a37c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupOpcode145.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupTalkerAlias.java @@ -30,9 +30,9 @@ import java.util.ArrayList; import java.util.List; /** - * Motorola Group Regroup Unknown Opcode 145 - 17-bytes long + * Motorola Group Regroup Talker Alias - 17-bytes long */ -public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor +public class MotorolaGroupRegroupTalkerAlias extends MacStructureVendor { private static final IntField TALKGROUP = IntField.length16(24); private static final IntField UNKNOWN = IntField.length32(40); @@ -49,7 +49,7 @@ public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor * @param message containing the message bits * @param offset into the message for this structure */ - public MotorolaGroupRegroupOpcode145(CorrectedBinaryMessage message, int offset) + public MotorolaGroupRegroupTalkerAlias(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -60,7 +60,7 @@ public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("MOTOROLA GROUP REGROUP UNKNOWN OPCODE 145 TALKGROUP:").append(getTalkgroup()); + sb.append("MOTOROLA GROUP REGROUP TALKER ALIAS TALKGROUP:").append(getTalkgroup()); sb.append(" SOURCE SUID RADIO:").append(getRadio()); sb.append(" UNK:").append(Integer.toHexString(getInt(UNKNOWN)).toUpperCase()); sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString()); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaOpcode149.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupTalkerAliasContinuation.java similarity index 69% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaOpcode149.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupTalkerAliasContinuation.java index 317ad2c5..6ea069fd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaOpcode149.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupTalkerAliasContinuation.java @@ -26,19 +26,30 @@ import java.util.ArrayList; import java.util.List; /** - * Motorola Unknown Opcode 149 (0x95) + * Motorola Unknown Opcode 149 (0x95) Talker Alias continuation message. * - * This was observed at the end of a Group Regroup call sequence during HANGTIME, so it seems more of an announcement - * type message and less as something relevant to the call. + * This was observed at the end of a Group Regroup call sequence during HANGTIME. Each radio doesn't know the alias + * until it receives it once and then queues the alias for subsequent use. + * + * This opcode is observed following an Opcode 145 message and seems to be a continuation message. * * Examples: * 959011 018E58B82BAB4D3B70E9A8457F9D C67 * 959011 0287000000000000000000000000 E26 * - * The opcode, vendor and length octets are consistent. The first octet seems to be an identifier, 01, 02, etc. + * Example 2: + * 9190110BCD02010026BEE004A403151724FD9 Opcode 145 + * 959011012436C4C022EC902A9943EDAA69E76 Opcode 149 + * 95901102298FAA77683FAE0000000000000AB Opcode 149 + * (immediately followed in the same call sequence by a slight variation) + * 9190110BCD02010036BEE004A403151724513 Opcode 145 + * 959011013436C4C022EC902A9943EDAA699F6 Opcode 149 + * 95901102398FAA77683FAE00000000000072B Opcode 149 + * + * The opcode, vendor and length octets are consistent. The first octet seems to be a sequence identifier, 01, 02, etc. * Nothing else seems to match any of the other identifiers that were active at call time. */ -public class MotorolaOpcode149 extends MacStructureVendor +public class MotorolaGroupRegroupTalkerAliasContinuation extends MacStructureVendor { private List mIdentifiers; @@ -48,7 +59,7 @@ public class MotorolaOpcode149 extends MacStructureVendor * @param message containing the message bits * @param offset into the message for this structure */ - public MotorolaOpcode149(CorrectedBinaryMessage message, int offset) + public MotorolaGroupRegroupTalkerAliasContinuation(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -59,7 +70,7 @@ public class MotorolaOpcode149 extends MacStructureVendor public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("MOTOROLA UNKNOWN OPCODE 149"); + sb.append("MOTOROLA TALKER ALIAS CONTINUATION"); sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java index dae250e4..155057af 100644 --- a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java @@ -227,16 +227,30 @@ public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter { case DECIMAL: case FORMATTED: - sb.append(toDecimal(id, RADIO_DECIMAL_WIDTH)); - sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + if(id > 0) + { + sb.append(toDecimal(id, RADIO_DECIMAL_WIDTH)).append(" ("); + } + sb.append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); - sb.append(".").append(toDecimal(radio, RADIO_DECIMAL_WIDTH)).append(")"); + sb.append(".").append(toDecimal(radio, RADIO_DECIMAL_WIDTH)); + if(id > 0) + { + sb.append(")"); + } return sb.toString(); case HEXADECIMAL: - sb.append(toHex(id, RADIO_HEXADECIMAL_WIDTH)); - sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + if(id > 0) + { + sb.append(toHex(id, RADIO_HEXADECIMAL_WIDTH)).append(" ("); + } + sb.append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); - sb.append(".").append(toHex(radio, RADIO_HEXADECIMAL_WIDTH)).append(")"); + sb.append(".").append(toHex(radio, RADIO_HEXADECIMAL_WIDTH)); + if(id > 0) + { + sb.append(")"); + } return sb.toString(); default: throw new IllegalArgumentException("Unrecognized integer format: " + format); @@ -248,16 +262,30 @@ public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter { case DECIMAL: case FORMATTED: - sb.append(id); + if(id > 0) + { + sb.append(id).append(" ("); + } sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); - sb.append(".").append(radio).append(")"); + sb.append(".").append(radio); + if(id > 0) + { + sb.append(")"); + } return sb.toString(); case HEXADECIMAL: - sb.append(toHex(id)); - sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + if(id > 0) + { + sb.append(toHex(id)).append(" ("); + } + sb.append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); - sb.append(".").append(toHex(radio)).append(")"); + sb.append(".").append(toHex(radio)); + if(id > 0) + { + sb.append(")"); + } return sb.toString(); default: throw new IllegalArgumentException("Unrecognized integer format: " + format); diff --git a/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java b/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java index 0b69b372..35da717a 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/frequency/FrequencyController.java @@ -300,15 +300,18 @@ public class FrequencyController */ public void removeSourceEventProcessor(ISourceEventProcessor processor) { - mTunable.getLock().lock(); + if(mTunable != null) + { + mTunable.getLock().lock(); - try - { - mProcessors.remove(processor); - } - finally - { - mTunable.getLock().unlock(); + try + { + mProcessors.remove(processor); + } + finally + { + mTunable.getLock().unlock(); + } } }