diff --git a/src/audio/AudioModule.java b/src/audio/AudioModule.java index 5a9c789a..5dc6f2b5 100644 --- a/src/audio/AudioModule.java +++ b/src/audio/AudioModule.java @@ -124,9 +124,6 @@ public class AudioModule extends Module implements IAudioPacketProvider, AudioPacket packet = new AudioPacket( audio, mAudioMetadata.copyOf() ); - /* Reset the updated flag on the metadata */ - mAudioMetadata.setUpdated( false ); - mAudioPacketListener.receive( packet ); } } diff --git a/src/audio/metadata/AudioMetadata.java b/src/audio/metadata/AudioMetadata.java index 724d76a4..b80cddff 100644 --- a/src/audio/metadata/AudioMetadata.java +++ b/src/audio/metadata/AudioMetadata.java @@ -94,7 +94,9 @@ public class AudioMetadata implements Listener copy.mRecordable = mRecordable; copy.mMetadata.addAll( mMetadata ); copy.mUpdated = mUpdated; - copy.mIdentifier = mIdentifier; + copy.mIdentifier = new String( mIdentifier ); + + mUpdated = false; return copy; } @@ -283,4 +285,25 @@ public class AudioMetadata implements Listener mRecordable = recordable; mUpdated = true; } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "Audio Metadata - source:" ); + sb.append( getSource() ); + sb.append( " priority:" ); + sb.append( getPriority() ); + sb.append( " updated:" + isUpdated() ); + sb.append( " " ); + for( Metadata m: getMetadata() ) + { + sb.append( m.getKey() ); + sb.append( ":" ); + sb.append( m.getValue() ); + sb.append( " " ); + } + + return sb.toString(); + } } diff --git a/src/module/decode/p25/P25DecoderPanel.java b/src/module/decode/p25/P25DecoderPanel.java index ae3d9396..35aea8f5 100644 --- a/src/module/decode/p25/P25DecoderPanel.java +++ b/src/module/decode/p25/P25DecoderPanel.java @@ -168,7 +168,7 @@ public class P25DecoderPanel extends DecoderPanel } break; case TO_TALKGROUP_ALIAS: - Alias to = state.getToAlias(); + final Alias to = state.getToAlias(); if( to != null ) { diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index f7dfae15..4400561a 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -177,6 +178,7 @@ public class P25DecoderState extends DecoderState private P25CallEvent mCurrentCallEvent; private List mCallDetectTalkgroups = new ArrayList<>(); + private Map mChannelCallMap = new HashMap<>(); public P25DecoderState( AliasList aliasList, ChannelType channelType, @@ -256,6 +258,90 @@ public class P25DecoderState extends DecoderState { } + /** + * Indicates if there a call event exists for the specified channel and + * from and to talkgroup identifiers. + * + * @param channel - channel number + * @param from user id + * @param to talkgroup or user id + * @param time current in millis + * @return true if there is a call event + */ + public boolean hasCallEvent( String channel, String from, String to ) + { + boolean hasEvent = false; + + if( mChannelCallMap.containsKey( channel ) ) + { + P25CallEvent event = mChannelCallMap.get( channel ); + + if( to != null && + event.getToID() != null && + to.contentEquals( event.getToID() ) ) + { + if( from != null ) + { + if( event.getFromID() == null ) + { + hasEvent = true; + } + else if( from.contentEquals( event.getFromID() ) ) + { + hasEvent = true; + } + } + else if( event.getFromID() == null ) + { + hasEvent = true; + } + } + } + + return hasEvent; + } + + /** + * Adds the channel and event to the current channel call map. If an entry + * already exists, terminates the event and broadcasts the update. + * + * @param channel for the call + * @param event to place in the map + */ + public void registerCallEvent( P25CallEvent event ) + { + if( mChannelCallMap.containsKey( event.getChannel() ) ) + { + P25CallEvent previousEvent = mChannelCallMap.remove( event.getChannel() ); + + previousEvent.end(); + + broadcast( previousEvent ); + } + + mChannelCallMap.put( event.getChannel(), event ); + } + + public void updateCallEvent( String channel, String from, String to ) + { + P25CallEvent event = mChannelCallMap.get( channel ); + + if( event != null && + to != null && + event.getToID() != null && + event.getToID().contentEquals( to ) ) + { + if( event.getFromID() == null && + from != null && + !from.contentEquals( "0000" ) && + !from.contentEquals( "000000" ) ) + { + event.setFromID( from ); + broadcast( event ); + } + } + } + /** * Primary message processing method. */ @@ -1937,12 +2023,10 @@ public class P25DecoderState extends DecoderState TelephoneInterconnectChannelGrantExplicit ticge = (TelephoneInterconnectChannelGrantExplicit)pdu; - CallEvent callEvent = new P25CallEvent.Builder( CallEventType.CALL ) + CallEvent callEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) .aliasList( getAliasList() ) .channel( ticge.getTransmitChannel() ) - .details( "Trigger: PDU-TEL_INT_V_CH_GRNT " + - "TELEPHONE CALL " + - ( ticge.isEncrypted() ? "ENCRYPTED" : "" ) + + .details( ( ticge.isEncrypted() ? "ENCRYPTED" : "" ) + ( ticge.isEmergency() ? " EMERGENCY" : "") + " CALL TIMER:" + ticge.getCallTimer() ) .frequency( ticge.getDownlinkFrequency() ) @@ -2017,11 +2101,10 @@ public class P25DecoderState extends DecoderState UnitToUnitVoiceChannelGrantExtended uuvcge = (UnitToUnitVoiceChannelGrantExtended)pdu; - CallEvent uuCallEvent = new P25CallEvent.Builder( CallEventType.CALL ) + CallEvent uuCallEvent = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) .aliasList( getAliasList() ) .channel( uuvcge.getTransmitChannel() ) - .details( "Trigger:PDU-U2U_V-CH_GRNT " + - ( uuvcge.isEncrypted() ? "ENCRYPTED" : "" ) + + .details( ( uuvcge.isEncrypted() ? "ENCRYPTED" : "" ) + ( uuvcge.isEmergency() ? " EMERGENCY" : "") ) .frequency( uuvcge.getDownlinkFrequency() ) .from( uuvcge.getSourceWACN() + "-" + @@ -2043,11 +2126,10 @@ public class P25DecoderState extends DecoderState UnitToUnitVoiceChannelGrantUpdateExtended uuvcgue = (UnitToUnitVoiceChannelGrantUpdateExtended)pdu; - broadcast( new P25CallEvent.Builder( CallEventType.CALL ) + broadcast( new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) .aliasList( getAliasList() ) .channel( uuvcgue.getTransmitChannel() ) - .details( "Trigger: PDU-U2U_V_CH_G_U " + - ( uuvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + + .details( ( uuvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + ( uuvcgue.isEmergency() ? " EMERGENCY" : "") ) .frequency( uuvcgue.getDownlinkFrequency() ) .from( uuvcgue.getSourceWACN() + "-" + @@ -2479,178 +2561,362 @@ public class P25DecoderState extends DecoderState */ private void processTSBKChannelGrant( TSBKMessage message ) { - P25CallEvent event = null; + String channel = null; + String from = null; + String to = null; switch( message.getOpcode() ) { case GROUP_DATA_CHANNEL_GRANT: GroupDataChannelGrant gdcg = (GroupDataChannelGrant)message; - event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( gdcg.getChannel() ) - .details( ( gdcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gdcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gdcg.getDownlinkFrequency() ) - .from( gdcg.getSourceAddress() ) - .to( gdcg.getGroupAddress() ) - .build(); + channel = gdcg.getChannel(); + from = gdcg.getSourceAddress(); + to = gdcg.getGroupAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( gdcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gdcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gdcg.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + /* We don't allocate traffic channels for data */ break; case GROUP_VOICE_CHANNEL_GRANT: GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)message; - event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( gvcg.getChannel() ) - .details( "Trigger: TSBK-G_V_CH_GRNT " + - ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcg.getDownlinkFrequency() ) - .from( gvcg.getSourceAddress() ) - .to( gvcg.getGroupAddress() ) - .build(); + channel = gvcg.getChannel(); + from = gvcg.getSourceAddress(); + to = gvcg.getGroupAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcg.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); break; case GROUP_VOICE_CHANNEL_GRANT_UPDATE: GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)message; + + channel = gvcgu.getChannel1(); + from = null; + to = gvcgu.getGroupAddress1(); - event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( gvcgu.getChannel1() ) - .details( "Trigger: TSBK-G_V_CH_GRNT_UP " + - ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgu.getDownlinkFrequency1() ) - .to( gvcgu.getGroupAddress1() ) - .build(); + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .channel( gvcgu.getChannel1() ) + .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcgu.getDownlinkFrequency1() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); if( gvcgu.hasChannelNumber2() ) { - /* We handle this channel grant independent of the first event */ - P25CallEvent event2 = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( gvcgu.getChannel2() ) - .details( "Trigger: TSBK-G_V_CH_GRNT_UP#2 " + - ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgu.getDownlinkFrequency2() ) - .to( gvcgu.getGroupAddress2() ) - .build(); - - broadcast( new TrafficChannelAllocationEvent( this, event2 ) ); + channel = gvcgu.getChannel2(); + to = gvcgu.getGroupAddress2(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event2 = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) + .aliasList( getAliasList() ) + .channel( gvcgu.getChannel2() ) + .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcgu.getDownlinkFrequency2() ) + .to( gvcgu.getGroupAddress2() ) + .build(); + + registerCallEvent( event2 ); + broadcast( event2 ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); } break; case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: GroupVoiceChannelGrantUpdateExplicit gvcgue = (GroupVoiceChannelGrantUpdateExplicit)message; - event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( gvcgue.getTransmitChannelIdentifier() + "-" + - gvcgue.getTransmitChannelNumber() ) - .details( "Trigger: TSBK-G_V_CH_GRNT_U_E " + - ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgue.getDownlinkFrequency() ) - .to( gvcgue.getGroupAddress() ) - .build(); + channel = gvcgue.getTransmitChannelIdentifier() + "-" + + gvcgue.getTransmitChannelNumber(); + from = null; + to = gvcgue.getGroupAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) + .frequency( gvcgue.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); break; case INDIVIDUAL_DATA_CHANNEL_GRANT: IndividualDataChannelGrant idcg = (IndividualDataChannelGrant)message; - event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( idcg.getChannel() ) - .details( "Trigger: TSBK-IND_D_CH_GRNT " + - ( idcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( idcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( idcg.getDownlinkFrequency() ) - .from( idcg.getSourceAddress() ) - .to( idcg.getTargetAddress() ) - .build(); + channel = idcg.getChannel(); + from = idcg.getSourceAddress(); + to = idcg.getTargetAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( idcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( idcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( idcg.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + /* We don't allocate a traffic channel for data */ break; case SNDCP_DATA_CHANNEL_GRANT: SNDCPDataChannelGrant sdcg = (SNDCPDataChannelGrant)message; - event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( sdcg.getTransmitChannel() ) - .details( "Trigger: TSBK-SN_D_CH_GRNT " + - "SNDCP DATA NSAPI:" + sdcg.getNSAPI() ) - .frequency( sdcg.getDownlinkFrequency() ) - .to( sdcg.getTargetAddress() ) - .build(); + channel = sdcg.getTransmitChannel(); + from = null; + to = sdcg.getTargetAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( sdcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( sdcg.isEmergency() ? " EMERGENCY" : "") ) + .frequency( sdcg.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + /* We don't allocate a traffic channel for data */ break; case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: TelephoneInterconnectVoiceChannelGrant tivcg = (TelephoneInterconnectVoiceChannelGrant)message; - event = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) - .aliasList( getAliasList() ) - .channel( tivcg.getChannel() ) - .details( "Trigger: TSBK-T_INT_V_CH_GRNT " + - ( tivcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( tivcg.isEmergency() ? " EMERGENCY" : "") + - " CALL TIMER:" + tivcg.getCallTimer() ) - .frequency( tivcg.getDownlinkFrequency() ) - .from( tivcg.getAddress() ) - .build(); + channel = tivcg.getChannel(); + from = null; + /* Address is ambiguous and could mean either source or target, + * so we'll place the value in the to field */ + to = tivcg.getAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.TELEPHONE_INTERCONNECT ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( tivcg.isEncrypted() ? "ENCRYPTED " : "" ) + + ( tivcg.isEmergency() ? "EMERGENCY " : "") + + "CALL TIMER:" + tivcg.getCallTimer() ) + .frequency( tivcg.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); break; case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = (TelephoneInterconnectVoiceChannelGrantUpdate)message; - event = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) - .aliasList( getAliasList() ) - .channel( tivcgu.getChannelIdentifier() + "-" + - tivcgu.getChannelNumber() ) - .details( "Trigger: TSBK-T_INT_V_CH_G_UP " + - ( tivcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( tivcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( tivcgu.getDownlinkFrequency() ) - .from( tivcgu.getAddress() ) - .build(); + channel = tivcgu.getChannelIdentifier() + "-" + + tivcgu.getChannelNumber(); + from = null; + + /* Address is ambiguous and could mean either source or target, + * so we'll place the value in the to field */ + to = tivcgu.getAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.TELEPHONE_INTERCONNECT ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( tivcgu.isEncrypted() ? "ENCRYPTED " : "" ) + + ( tivcgu.isEmergency() ? "EMERGENCY " : "") + + "CALL TIMER:" + tivcgu.getCallTimer() ) + .frequency( tivcgu.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); break; case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: UnitToUnitVoiceChannelGrant uuvcg = (UnitToUnitVoiceChannelGrant)message; + + channel = uuvcg.getChannelIdentifier() + "-" + + uuvcg.getChannelNumber(); + from = uuvcg.getSourceAddress(); + to = uuvcg.getTargetAddress(); - event = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( uuvcg.getChannelIdentifier() + "-" + - uuvcg.getChannelNumber() ) - .details( "Trigger: TSBK-U2U_V_CH_GRNT " + - ( uuvcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( uuvcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( uuvcg.getDownlinkFrequency() ) - .from( uuvcg.getSourceAddress() ) - .to( uuvcg.getTargetAddress() ) - .build(); + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.UNIT_TO_UNIT_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( uuvcg.isEncrypted() ? "ENCRYPTED " : "" ) + + ( uuvcg.isEmergency() ? "EMERGENCY " : "") ) + .frequency( uuvcg.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); break; case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: UnitToUnitVoiceChannelGrantUpdate uuvcgu = (UnitToUnitVoiceChannelGrantUpdate)message; - event = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( uuvcgu.getChannelIdentifier() + "-" + - uuvcgu.getChannelNumber() ) - .details( "Trigger: TSBK-U2U_V_CH_G_UP " + - ( uuvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( uuvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( uuvcgu.getDownlinkFrequency() ) - .from( uuvcgu.getSourceAddress() ) - .to( uuvcgu.getTargetAddress() ) - .build(); + channel = uuvcgu.getChannelIdentifier() + "-" + + uuvcgu.getChannelNumber(); + from = uuvcgu.getSourceAddress(); + to = uuvcgu.getTargetAddress(); + + if( hasCallEvent( channel, from, to ) ) + { + updateCallEvent( channel, from, to ); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.UNIT_TO_UNIT_CALL ) + .aliasList( getAliasList() ) + .channel( channel ) + .details( ( uuvcgu.isEncrypted() ? "ENCRYPTED " : "" ) + + ( uuvcgu.isEmergency() ? "EMERGENCY " : "") ) + .frequency( uuvcgu.getDownlinkFrequency() ) + .from( from ) + .to( to ) + .build(); + + registerCallEvent( event ); + broadcast( event ); + } + + broadcast( new TrafficChannelAllocationEvent( this, + mChannelCallMap.get( channel ) ) ); break; default: break; } - - if( event != null && event.getCallEventType() != CallEventType.DATA_CALL ) - { - broadcast( new TrafficChannelAllocationEvent( this, event ) ); - } } /** @@ -2658,38 +2924,35 @@ public class P25DecoderState extends DecoderState */ private void updateTo( String to ) { - mLog.debug( "Updating to [" + to + "]" ); - if( to != null && - !to.contentEquals( "0000" ) && - !to.contentEquals( "000000" ) && - ( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) ) ) + if( to != null && !to.contentEquals( "0000" ) && !to.contentEquals( "000000" ) ) { - mToTalkgroup = to; - broadcast( ChangedAttribute.TO_TALKGROUP ); + if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) ) + { + mToTalkgroup = to; + broadcast( ChangedAttribute.TO_TALKGROUP ); - if( mCurrentCallEvent != null && - ( mCurrentCallEvent.getToID() == null || - !mCurrentCallEvent.getToID().contentEquals( to ) ) ) - { - mCurrentCallEvent.setToID( to ); - broadcast( mCurrentCallEvent ); - } + if( mCurrentCallEvent != null && + ( mCurrentCallEvent.getToID() == null || + !mCurrentCallEvent.getToID().contentEquals( to ) ) ) + { + mCurrentCallEvent.setToID( to ); + broadcast( mCurrentCallEvent ); + } - if( hasAliasList() ) - { - mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup ); - mLog.debug( "To Alias: " + mToAlias ); + if( hasAliasList() ) + { + mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup ); + } + else + { + mToAlias = null; + } + + broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); } - else - { - mLog.debug( "To Alias: no alias list" ); - mToAlias = null; - } - - broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); /* Send audio metadata update */ - broadcast( new Metadata( MetadataType.TO, to, mToAlias, true ) ); + broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) ); } } @@ -2698,35 +2961,35 @@ public class P25DecoderState extends DecoderState */ private void updateFrom( String from ) { - if( from != null && - !from.contentEquals( "0000" ) && - !from.contentEquals( "000000" ) && - ( mFromTalkgroup == null || !mFromTalkgroup.contentEquals( from ) ) ) + if( from != null && !from.contentEquals( "0000" ) && !from.contentEquals( "000000" ) ) { - mFromTalkgroup = from; - broadcast( ChangedAttribute.FROM_TALKGROUP ); - - if( mCurrentCallEvent != null && - mCurrentCallEvent.getFromID() == null || - !mCurrentCallEvent.getFromID().contentEquals( from ) ) + if( mFromTalkgroup == null || !mFromTalkgroup.contentEquals( from )) { - mCurrentCallEvent.setFromID( from ); - broadcast( mCurrentCallEvent ); + mFromTalkgroup = from; + broadcast( ChangedAttribute.FROM_TALKGROUP ); + + if( mCurrentCallEvent != null && + mCurrentCallEvent.getFromID() == null || + !mCurrentCallEvent.getFromID().contentEquals( from ) ) + { + mCurrentCallEvent.setFromID( from ); + broadcast( mCurrentCallEvent ); + } + + if( hasAliasList() ) + { + mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup ); + } + else + { + mFromAlias = null; + } + + broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS ); } - - if( hasAliasList() ) - { - mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup ); - } - else - { - mFromAlias = null; - } - - broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS ); /* Send audio metadata update */ - broadcast( new Metadata( MetadataType.FROM, from, mFromAlias, true ) ); + broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) ); } } diff --git a/src/module/decode/p25/audio/P25AudioModule.java b/src/module/decode/p25/audio/P25AudioModule.java index 7ff3b446..d3cfdecd 100644 --- a/src/module/decode/p25/audio/P25AudioModule.java +++ b/src/module/decode/p25/audio/P25AudioModule.java @@ -38,7 +38,6 @@ public class P25AudioModule extends Module implements Listener, private int mSourceID; private static boolean mLibraryLoadStatusLogged = false; private boolean mCanConvertAudio = false; - private boolean mEncryptedAudio = false; private AudioConverter mAudioConverter; private Listener mAudioPacketListener; private AudioMetadata mAudioMetadata; @@ -105,26 +104,17 @@ public class P25AudioModule extends Module implements Listener, /* If we detect a valid LDU message with the encryption flag * set to true, we toggle the encrypted audio state until we * receive the first TDU or TDULC message, then reset it. */ - if( ldu.isValid() && ldu.isEncrypted() ) - { - mEncryptedAudio = true; - } - - if( !mEncryptedAudio && mAudioConverter != null ) + if( !( ldu.isValid() && ldu.isEncrypted() ) ) { for( byte[] frame: ((LDUMessage)message).getIMBEFrames() ) { float[] audio = mAudioConverter.decode( frame ); - + mAudioPacketListener.receive( - new AudioPacket( audio, mAudioMetadata ) ); + new AudioPacket( audio, mAudioMetadata.copyOf() ) ); } } } - else if( message instanceof TDUMessage || message instanceof TDULinkControlMessage ) - { - mEncryptedAudio = false; - } } } diff --git a/src/module/decode/p25/message/ldu/LDU2Message.java b/src/module/decode/p25/message/ldu/LDU2Message.java index 776a33c7..e8f103e1 100644 --- a/src/module/decode/p25/message/ldu/LDU2Message.java +++ b/src/module/decode/p25/message/ldu/LDU2Message.java @@ -76,7 +76,10 @@ public class LDU2Message extends LDUMessage @Override public boolean isEncrypted() { - return getEncryption() != Encryption.UNENCRYPTED; + Encryption encryption = getEncryption(); + + return encryption != Encryption.UNENCRYPTED && + encryption != Encryption.UNKNOWN; } private void checkCRC() diff --git a/src/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java b/src/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java index 09cbaa4c..ad9d69d1 100644 --- a/src/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java +++ b/src/module/decode/p25/message/tsbk/motorola/MotorolaTSBKMessage.java @@ -22,11 +22,11 @@ public class MotorolaTSBKMessage extends TSBKMessage sb.append( " " ); sb.append( getDUID().getLabel() ); - sb.append( " MOTOROLA" ); + sb.append( " MOTOROLA " ); if( isEncrypted() ) { - sb.append( " ENCRYPTED" ); + sb.append( " ENCRYPTED " ); } sb.append( getMotorolaOpcode().getLabel() ); diff --git a/src/module/decode/state/ChannelState.java b/src/module/decode/state/ChannelState.java index 05e96f44..9141f64e 100644 --- a/src/module/decode/state/ChannelState.java +++ b/src/module/decode/state/ChannelState.java @@ -241,7 +241,14 @@ public class ChannelState extends Module implements ICallEventProvider, */ private void updateResetTimeout() { - mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY; + if( mChannelType == ChannelType.TRAFFIC ) + { + mResetTimeout = System.currentTimeMillis(); + } + else + { + mResetTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY; + } } /** diff --git a/src/settings/SettingsManager.java b/src/settings/SettingsManager.java index e37317e1..281d2bed 100644 --- a/src/settings/SettingsManager.java +++ b/src/settings/SettingsManager.java @@ -376,61 +376,64 @@ public class SettingsManager */ public ImageIcon getImageIcon( String name, int height ) { - String mergedName = name + height; - - if( mResizedIcons.containsKey( mergedName ) ) + if( name != null ) { - return mResizedIcons.get( mergedName ); - } - - MapIcon mapIcon = mIcons.get( name ); - - if( mapIcon != null ) - { - if( mapIcon.getImageIcon().getIconHeight() > height ) + String mergedName = name + height; + + if( mResizedIcons.containsKey( mergedName ) ) { - ImageIcon scaled = getScaledIcon( mapIcon.getImageIcon(), height ); - - mResizedIcons.put( mergedName, scaled ); - - return scaled; + return mResizedIcons.get( mergedName ); } - else - { - mResizedIcons.put( mergedName, mapIcon.getImageIcon() ); - } - } + + MapIcon mapIcon = mIcons.get( name ); - /* Use the default Icon */ - String mergedDefault = mCurrentDefaultIconName + height; - - if( mResizedIcons.containsKey( mergedDefault ) ) - { - return mResizedIcons.get( mergedDefault ); - } - else - { - MapIcon defaultIcon = mIcons.get( mCurrentDefaultIconName ); - - if( defaultIcon != null ) + if( mapIcon != null ) { - if( defaultIcon.getImageIcon().getIconHeight() > height ) + if( mapIcon.getImageIcon().getIconHeight() > height ) { - ImageIcon scaledDefault = getScaledIcon( defaultIcon.getImageIcon(), height ); - mResizedIcons.put( mergedDefault, scaledDefault ); - return scaledDefault; + ImageIcon scaled = getScaledIcon( mapIcon.getImageIcon(), height ); + + mResizedIcons.put( mergedName, scaled ); + + return scaled; } else { - mResizedIcons.put( mergedDefault, defaultIcon.getImageIcon() ); - return defaultIcon.getImageIcon(); + mResizedIcons.put( mergedName, mapIcon.getImageIcon() ); } } - } - /* Something happened ... the above should always return an icon */ - mLog.error( "SettingsManager - couldn't return an icon named [" + - name + "] of heigh [" + height + "]" ); + /* Use the default Icon */ + String mergedDefault = mCurrentDefaultIconName + height; + + if( mResizedIcons.containsKey( mergedDefault ) ) + { + return mResizedIcons.get( mergedDefault ); + } + else + { + MapIcon defaultIcon = mIcons.get( mCurrentDefaultIconName ); + + if( defaultIcon != null ) + { + if( defaultIcon.getImageIcon().getIconHeight() > height ) + { + ImageIcon scaledDefault = getScaledIcon( defaultIcon.getImageIcon(), height ); + mResizedIcons.put( mergedDefault, scaledDefault ); + return scaledDefault; + } + else + { + mResizedIcons.put( mergedDefault, defaultIcon.getImageIcon() ); + return defaultIcon.getImageIcon(); + } + } + } + + /* Something happened ... the above should always return an icon */ + mLog.error( "SettingsManager - couldn't return an icon named [" + + name + "] of heigh [" + height + "]" ); + } return null; } diff --git a/src/source/tuner/TunerController.java b/src/source/tuner/TunerController.java index eb978ef5..7b1aaea3 100644 --- a/src/source/tuner/TunerController.java +++ b/src/source/tuner/TunerController.java @@ -111,6 +111,18 @@ public abstract class TunerController implements Tunable { return mFrequencyController.getMaximumFrequency(); } + + public long getMinTunedFrequency() + { + return mFrequencyController.getFrequency() - + ( mFrequencyController.getBandwidth() / 2 ); + } + + public long getMaxTunedFrequency() + { + return mFrequencyController.getFrequency() + + ( mFrequencyController.getBandwidth() / 2 ); + } /** * Indicates if the tuner can accomodate this new channel frequency and diff --git a/src/source/tuner/airspy/AirspyTunerController.java b/src/source/tuner/airspy/AirspyTunerController.java index eb2d6eb6..0e72b92a 100644 --- a/src/source/tuner/airspy/AirspyTunerController.java +++ b/src/source/tuner/airspy/AirspyTunerController.java @@ -4,9 +4,11 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -27,6 +29,9 @@ import sample.Broadcaster; import sample.Listener; import sample.complex.ComplexBuffer; import source.SourceException; +import source.tuner.Tuner; +import source.tuner.TunerChannel; +import source.tuner.TunerChannelSource; import source.tuner.TunerConfiguration; import source.tuner.TunerController; import controller.ThreadPoolManager; @@ -826,6 +831,91 @@ public class AirspyTunerController extends TunerController } } + /** + * Indicates if the tuner can accomodate this new channel frequency and + * bandwidth, along with all of the existing tuned channels currently in + * place. + */ + public boolean canTuneChannel( TunerChannel channel ) + { + //If this is the first lock, then we're good + if( mTunedChannels.isEmpty() ) + { + return true; + } + else + { + //Sort the existing locks and get the min/max locked frequencies + Collections.sort( mTunedChannels ); + + long minLockedFrequency = mTunedChannels.get( 0 ).getMinFrequency(); + long maxLockedFrequency = mTunedChannels + .get( mTunedChannels.size() - 1 ).getMaxFrequency(); + + if( channel.getMinFrequency() >= minLockedFrequency && + channel.getMaxFrequency() <= maxLockedFrequency ) + { + return true; + } + + if( channel.getMinFrequency() < minLockedFrequency ) + { + return ( maxLockedFrequency - channel.getMinFrequency() ) < getBandwidth() - 1E5; + } + + if( channel.getMaxFrequency() > maxLockedFrequency ) + { + return ( channel.getMaxFrequency() - minLockedFrequency ) <= getBandwidth() - 1E5; + } + } + + return false; + } + + public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, + Tuner tuner, TunerChannel tunerChannel ) + throws RejectedExecutionException, SourceException + { + TunerChannelSource source = null; + + if( canTuneChannel( tunerChannel ) ) + { + mTunedChannels.add( tunerChannel ); + + Collections.sort( mTunedChannels ); + + long min = mTunedChannels.get( 0 ).getMinFrequency(); + long max = mTunedChannels.get( mTunedChannels.size() - 1 ).getMaxFrequency(); + + if( !( getMinTunedFrequency() <= min && + max <= getMaxTunedFrequency() ) ) + { + long freq = min + ( mFrequencyController.getBandwidth() / 2 ) + 50000; + + mFrequencyController.setFrequency( freq ); + } + + source = new TunerChannelSource( threadPoolManager, + tuner, tunerChannel ); + } + + return source; + } + + public void releaseChannel( TunerChannelSource tunerChannelSource ) + { + if( tunerChannelSource != null ) + { + mTunedChannels.remove( tunerChannelSource.getTunerChannel() ); + } + else + { + mLog.error( "Tuner Controller - couldn't find the tuned channel " + + "to release it" ); + } + } + + /** * Airspy Board Identifier */