Throw IllegalStateException if native objects are used after dispose.

This makes it easier to debug issues related to double dispose /
use after dispose.

Bug: webrtc:7566, webrtc:8297
Change-Id: I07429b2b794deabb62b5f3ea1cf92eea6f66a149
Reviewed-on: https://webrtc-review.googlesource.com/102540
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Paulina Hensman <phensman@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24894}
diff --git a/sdk/android/api/org/webrtc/AudioSource.java b/sdk/android/api/org/webrtc/AudioSource.java
index 99fcad1..f8104e5 100644
--- a/sdk/android/api/org/webrtc/AudioSource.java
+++ b/sdk/android/api/org/webrtc/AudioSource.java
@@ -18,4 +18,9 @@
   public AudioSource(long nativeSource) {
     super(nativeSource);
   }
+
+  /** Returns a pointer to webrtc::AudioSourceInterface. */
+  long getNativeAudioSource() {
+    return getNativeMediaSource();
+  }
 }
diff --git a/sdk/android/api/org/webrtc/AudioTrack.java b/sdk/android/api/org/webrtc/AudioTrack.java
index 828e89c..ca745db 100644
--- a/sdk/android/api/org/webrtc/AudioTrack.java
+++ b/sdk/android/api/org/webrtc/AudioTrack.java
@@ -20,7 +20,12 @@
    *  0 to 10.
    */
   public void setVolume(double volume) {
-    nativeSetVolume(super.nativeTrack, volume);
+    nativeSetVolume(getNativeAudioTrack(), volume);
+  }
+
+  /** Returns a pointer to webrtc::AudioTrackInterface. */
+  long getNativeAudioTrack() {
+    return getNativeMediaStreamTrack();
   }
 
   private static native void nativeSetVolume(long track, double volume);
diff --git a/sdk/android/api/org/webrtc/DataChannel.java b/sdk/android/api/org/webrtc/DataChannel.java
index 53ea0d8..f51dab9 100644
--- a/sdk/android/api/org/webrtc/DataChannel.java
+++ b/sdk/android/api/org/webrtc/DataChannel.java
@@ -102,7 +102,7 @@
     }
   }
 
-  private final long nativeDataChannel;
+  private long nativeDataChannel;
   private long nativeObserver;
 
   @CalledByNative
@@ -112,6 +112,7 @@
 
   /** Register |observer|, replacing any previously-registered observer. */
   public void registerObserver(Observer observer) {
+    checkDataChannelExists();
     if (nativeObserver != 0) {
       nativeUnregisterObserver(nativeObserver);
     }
@@ -120,18 +121,22 @@
 
   /** Unregister the (only) observer. */
   public void unregisterObserver() {
+    checkDataChannelExists();
     nativeUnregisterObserver(nativeObserver);
   }
 
   public String label() {
+    checkDataChannelExists();
     return nativeLabel();
   }
 
   public int id() {
+    checkDataChannelExists();
     return nativeId();
   }
 
   public State state() {
+    checkDataChannelExists();
     return nativeState();
   }
 
@@ -141,16 +146,19 @@
    * to the network.
    */
   public long bufferedAmount() {
+    checkDataChannelExists();
     return nativeBufferedAmount();
   }
 
   /** Close the channel. */
   public void close() {
+    checkDataChannelExists();
     nativeClose();
   }
 
   /** Send |data| to the remote peer; return success. */
   public boolean send(Buffer buffer) {
+    checkDataChannelExists();
     // TODO(fischman): this could be cleverer about avoiding copies if the
     // ByteBuffer is direct and/or is backed by an array.
     byte[] data = new byte[buffer.data.remaining()];
@@ -160,7 +168,9 @@
 
   /** Dispose of native resources attached to this channel. */
   public void dispose() {
+    checkDataChannelExists();
     JniCommon.nativeReleaseRef(nativeDataChannel);
+    nativeDataChannel = 0;
   }
 
   @CalledByNative
@@ -168,6 +178,12 @@
     return nativeDataChannel;
   }
 
+  private void checkDataChannelExists() {
+    if (nativeDataChannel == 0) {
+      throw new IllegalStateException("DataChannel has been disposed.");
+    }
+  }
+
   private native long nativeRegisterObserver(Observer observer);
   private native void nativeUnregisterObserver(long observer);
   private native String nativeLabel();
diff --git a/sdk/android/api/org/webrtc/DtmfSender.java b/sdk/android/api/org/webrtc/DtmfSender.java
index 9f203f3..6549823 100644
--- a/sdk/android/api/org/webrtc/DtmfSender.java
+++ b/sdk/android/api/org/webrtc/DtmfSender.java
@@ -12,7 +12,7 @@
 
 /** Java wrapper for a C++ DtmfSenderInterface. */
 public class DtmfSender {
-  final long nativeDtmfSender;
+  private long nativeDtmfSender;
 
   public DtmfSender(long nativeDtmfSender) {
     this.nativeDtmfSender = nativeDtmfSender;
@@ -22,6 +22,7 @@
    * @return true if this DtmfSender is capable of sending DTMF. Otherwise false.
    */
   public boolean canInsertDtmf() {
+    checkDtmfSenderExists();
     return nativeCanInsertDtmf(nativeDtmfSender);
   }
 
@@ -43,6 +44,7 @@
    * @return             true on success and false on failure.
    */
   public boolean insertDtmf(String tones, int duration, int interToneGap) {
+    checkDtmfSenderExists();
     return nativeInsertDtmf(nativeDtmfSender, tones, duration, interToneGap);
   }
 
@@ -50,6 +52,7 @@
    * @return The tones remaining to be played out
    */
   public String tones() {
+    checkDtmfSenderExists();
     return nativeTones(nativeDtmfSender);
   }
 
@@ -58,6 +61,7 @@
    *         insertDtmf() method, or the default value of 100 ms if insertDtmf() was never called.
    */
   public int duration() {
+    checkDtmfSenderExists();
     return nativeDuration(nativeDtmfSender);
   }
 
@@ -67,11 +71,20 @@
    *         called.
    */
   public int interToneGap() {
+    checkDtmfSenderExists();
     return nativeInterToneGap(nativeDtmfSender);
   }
 
   public void dispose() {
+    checkDtmfSenderExists();
     JniCommon.nativeReleaseRef(nativeDtmfSender);
+    nativeDtmfSender = 0;
+  }
+
+  private void checkDtmfSenderExists() {
+    if (nativeDtmfSender == 0) {
+      throw new IllegalStateException("DtmfSender has been disposed.");
+    }
   }
 
   private static native boolean nativeCanInsertDtmf(long dtmfSender);
diff --git a/sdk/android/api/org/webrtc/MediaSource.java b/sdk/android/api/org/webrtc/MediaSource.java
index 3f20dd7..0b19e1a 100644
--- a/sdk/android/api/org/webrtc/MediaSource.java
+++ b/sdk/android/api/org/webrtc/MediaSource.java
@@ -25,18 +25,33 @@
     }
   }
 
-  final long nativeSource; // Package-protected for PeerConnectionFactory.
+  private long nativeSource;
 
   public MediaSource(long nativeSource) {
     this.nativeSource = nativeSource;
   }
 
   public State state() {
+    checkMediaSourceExists();
     return nativeGetState(nativeSource);
   }
 
   public void dispose() {
+    checkMediaSourceExists();
     JniCommon.nativeReleaseRef(nativeSource);
+    nativeSource = 0;
+  }
+
+  /** Returns a pointer to webrtc::MediaSourceInterface. */
+  protected long getNativeMediaSource() {
+    checkMediaSourceExists();
+    return nativeSource;
+  }
+
+  private void checkMediaSourceExists() {
+    if (nativeSource == 0) {
+      throw new IllegalStateException("MediaSource has been disposed.");
+    }
   }
 
   private static native State nativeGetState(long pointer);
diff --git a/sdk/android/api/org/webrtc/MediaStream.java b/sdk/android/api/org/webrtc/MediaStream.java
index 7f4a434..e530fe5 100644
--- a/sdk/android/api/org/webrtc/MediaStream.java
+++ b/sdk/android/api/org/webrtc/MediaStream.java
@@ -11,8 +11,8 @@
 package org.webrtc;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Iterator;
+import java.util.List;
 
 /** Java wrapper for a C++ MediaStreamInterface. */
 public class MediaStream {
@@ -21,8 +21,7 @@
   public final List<AudioTrack> audioTracks = new ArrayList<>();
   public final List<VideoTrack> videoTracks = new ArrayList<>();
   public final List<VideoTrack> preservedVideoTracks = new ArrayList<>();
-  // Package-protected for PeerConnection.
-  final long nativeStream;
+  private long nativeStream;
 
   @CalledByNative
   public MediaStream(long nativeStream) {
@@ -30,7 +29,8 @@
   }
 
   public boolean addTrack(AudioTrack track) {
-    if (nativeAddAudioTrackToNativeStream(nativeStream, track.nativeTrack)) {
+    checkMediaStreamExists();
+    if (nativeAddAudioTrackToNativeStream(nativeStream, track.getNativeAudioTrack())) {
       audioTracks.add(track);
       return true;
     }
@@ -38,7 +38,8 @@
   }
 
   public boolean addTrack(VideoTrack track) {
-    if (nativeAddVideoTrackToNativeStream(nativeStream, track.nativeTrack)) {
+    checkMediaStreamExists();
+    if (nativeAddVideoTrackToNativeStream(nativeStream, track.getNativeVideoTrack())) {
       videoTracks.add(track);
       return true;
     }
@@ -49,7 +50,8 @@
   // is called. If video track need to be preserved after MediaStream is destroyed it
   // should be added to MediaStream using addPreservedTrack() call.
   public boolean addPreservedTrack(VideoTrack track) {
-    if (nativeAddVideoTrackToNativeStream(nativeStream, track.nativeTrack)) {
+    checkMediaStreamExists();
+    if (nativeAddVideoTrackToNativeStream(nativeStream, track.getNativeVideoTrack())) {
       preservedVideoTracks.add(track);
       return true;
     }
@@ -57,18 +59,21 @@
   }
 
   public boolean removeTrack(AudioTrack track) {
+    checkMediaStreamExists();
     audioTracks.remove(track);
-    return nativeRemoveAudioTrack(nativeStream, track.nativeTrack);
+    return nativeRemoveAudioTrack(nativeStream, track.getNativeAudioTrack());
   }
 
   public boolean removeTrack(VideoTrack track) {
+    checkMediaStreamExists();
     videoTracks.remove(track);
     preservedVideoTracks.remove(track);
-    return nativeRemoveVideoTrack(nativeStream, track.nativeTrack);
+    return nativeRemoveVideoTrack(nativeStream, track.getNativeVideoTrack());
   }
 
   @CalledByNative
   public void dispose() {
+    checkMediaStreamExists();
     // Remove and release previously added audio and video tracks.
     while (!audioTracks.isEmpty()) {
       AudioTrack track = audioTracks.get(0 /* index */);
@@ -85,9 +90,11 @@
       removeTrack(preservedVideoTracks.get(0 /* index */));
     }
     JniCommon.nativeReleaseRef(nativeStream);
+    nativeStream = 0;
   }
 
   public String getId() {
+    checkMediaStreamExists();
     return nativeGetId(nativeStream);
   }
 
@@ -116,12 +123,24 @@
     removeMediaStreamTrack(videoTracks, nativeTrack);
   }
 
+  /** Returns a pointer to webrtc::MediaStreamInterface. */
+  long getNativeMediaStream() {
+    checkMediaStreamExists();
+    return nativeStream;
+  }
+
+  private void checkMediaStreamExists() {
+    if (nativeStream == 0) {
+      throw new IllegalStateException("MediaStream has been disposed.");
+    }
+  }
+
   private static void removeMediaStreamTrack(
       List<? extends MediaStreamTrack> tracks, long nativeTrack) {
     final Iterator<? extends MediaStreamTrack> it = tracks.iterator();
     while (it.hasNext()) {
       MediaStreamTrack track = it.next();
-      if (track.nativeTrack == nativeTrack) {
+      if (track.getNativeMediaStreamTrack() == nativeTrack) {
         track.dispose();
         it.remove();
         return;
diff --git a/sdk/android/api/org/webrtc/MediaStreamTrack.java b/sdk/android/api/org/webrtc/MediaStreamTrack.java
index 198da1e..944c7ef 100644
--- a/sdk/android/api/org/webrtc/MediaStreamTrack.java
+++ b/sdk/android/api/org/webrtc/MediaStreamTrack.java
@@ -70,34 +70,52 @@
     }
   }
 
-  final long nativeTrack;
+  private long nativeTrack;
 
   public MediaStreamTrack(long nativeTrack) {
     this.nativeTrack = nativeTrack;
   }
 
   public String id() {
+    checkMediaStreamTrackExists();
     return nativeGetId(nativeTrack);
   }
 
   public String kind() {
+    checkMediaStreamTrackExists();
     return nativeGetKind(nativeTrack);
   }
 
   public boolean enabled() {
+    checkMediaStreamTrackExists();
     return nativeGetEnabled(nativeTrack);
   }
 
   public boolean setEnabled(boolean enable) {
+    checkMediaStreamTrackExists();
     return nativeSetEnabled(nativeTrack, enable);
   }
 
   public State state() {
+    checkMediaStreamTrackExists();
     return nativeGetState(nativeTrack);
   }
 
   public void dispose() {
+    checkMediaStreamTrackExists();
     JniCommon.nativeReleaseRef(nativeTrack);
+    nativeTrack = 0;
+  }
+
+  long getNativeMediaStreamTrack() {
+    checkMediaStreamTrackExists();
+    return nativeTrack;
+  }
+
+  private void checkMediaStreamTrackExists() {
+    if (nativeTrack == 0) {
+      throw new IllegalStateException("MediaStreamTrack has been disposed.");
+    }
   }
 
   private static native String nativeGetId(long track);
diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java
index 2f9adcf..84d49eb 100644
--- a/sdk/android/api/org/webrtc/PeerConnection.java
+++ b/sdk/android/api/org/webrtc/PeerConnection.java
@@ -781,7 +781,7 @@
    * use addTrack instead.
    */
   public boolean addStream(MediaStream stream) {
-    boolean ret = nativeAddLocalStream(stream.nativeStream);
+    boolean ret = nativeAddLocalStream(stream.getNativeMediaStream());
     if (!ret) {
       return false;
     }
@@ -795,7 +795,7 @@
    * removeTrack instead.
    */
   public void removeStream(MediaStream stream) {
-    nativeRemoveLocalStream(stream.nativeStream);
+    nativeRemoveLocalStream(stream.getNativeMediaStream());
     localStreams.remove(stream);
   }
 
@@ -905,7 +905,7 @@
     if (track == null || streamIds == null) {
       throw new NullPointerException("No MediaStreamTrack specified in addTrack.");
     }
-    RtpSender newSender = nativeAddTrack(track.nativeTrack, streamIds);
+    RtpSender newSender = nativeAddTrack(track.getNativeMediaStreamTrack(), streamIds);
     if (newSender == null) {
       throw new IllegalStateException("C++ addTrack failed.");
     }
@@ -922,7 +922,7 @@
     if (sender == null) {
       throw new NullPointerException("No RtpSender specified for removeTrack.");
     }
-    return nativeRemoveTrack(sender.nativeRtpSender);
+    return nativeRemoveTrack(sender.getNativeRtpSender());
   }
 
   /**
@@ -962,7 +962,8 @@
     if (init == null) {
       init = new RtpTransceiver.RtpTransceiverInit();
     }
-    RtpTransceiver newTransceiver = nativeAddTransceiverWithTrack(track.nativeTrack, init);
+    RtpTransceiver newTransceiver =
+        nativeAddTransceiverWithTrack(track.getNativeMediaStreamTrack(), init);
     if (newTransceiver == null) {
       throw new IllegalStateException("C++ addTransceiver failed.");
     }
@@ -993,7 +994,7 @@
   // Older, non-standard implementation of getStats.
   @Deprecated
   public boolean getStats(StatsObserver observer, @Nullable MediaStreamTrack track) {
-    return nativeOldGetStats(observer, (track == null) ? 0 : track.nativeTrack);
+    return nativeOldGetStats(observer, (track == null) ? 0 : track.getNativeMediaStreamTrack());
   }
 
   /**
@@ -1070,7 +1071,7 @@
   public void dispose() {
     close();
     for (MediaStream stream : localStreams) {
-      nativeRemoveLocalStream(stream.nativeStream);
+      nativeRemoveLocalStream(stream.getNativeMediaStream());
       stream.dispose();
     }
     localStreams.clear();
diff --git a/sdk/android/api/org/webrtc/PeerConnectionFactory.java b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
index 672bd0e..eb98e92 100644
--- a/sdk/android/api/org/webrtc/PeerConnectionFactory.java
+++ b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
@@ -28,7 +28,7 @@
   private static final String TAG = "PeerConnectionFactory";
   private static final String VIDEO_CAPTURER_THREAD_NAME = "VideoCapturerThread";
 
-  private final long nativeFactory;
+  private long nativeFactory;
   private static volatile boolean internalTracerInitialized;
   @Nullable private static Thread networkThread;
   @Nullable private static Thread workerThread;
@@ -307,6 +307,7 @@
   PeerConnection createPeerConnectionInternal(PeerConnection.RTCConfiguration rtcConfig,
       MediaConstraints constraints, PeerConnection.Observer observer,
       SSLCertificateVerifier sslCertificateVerifier) {
+    checkPeerConnectionFactoryExists();
     long nativeObserver = PeerConnection.createNativePeerConnectionObserver(observer);
     if (nativeObserver == 0) {
       return null;
@@ -364,61 +365,80 @@
   }
 
   public MediaStream createLocalMediaStream(String label) {
+    checkPeerConnectionFactoryExists();
     return new MediaStream(nativeCreateLocalMediaStream(nativeFactory, label));
   }
 
   public VideoSource createVideoSource(boolean isScreencast) {
+    checkPeerConnectionFactoryExists();
     return new VideoSource(nativeCreateVideoSource(nativeFactory, isScreencast));
   }
 
   public VideoTrack createVideoTrack(String id, VideoSource source) {
-    return new VideoTrack(nativeCreateVideoTrack(nativeFactory, id, source.nativeSource));
+    checkPeerConnectionFactoryExists();
+    return new VideoTrack(
+        nativeCreateVideoTrack(nativeFactory, id, source.getNativeVideoTrackSource()));
   }
 
   public AudioSource createAudioSource(MediaConstraints constraints) {
+    checkPeerConnectionFactoryExists();
     return new AudioSource(nativeCreateAudioSource(nativeFactory, constraints));
   }
 
   public AudioTrack createAudioTrack(String id, AudioSource source) {
-    return new AudioTrack(nativeCreateAudioTrack(nativeFactory, id, source.nativeSource));
+    checkPeerConnectionFactoryExists();
+    return new AudioTrack(nativeCreateAudioTrack(nativeFactory, id, source.getNativeAudioSource()));
   }
 
   // Starts recording an AEC dump. Ownership of the file is transfered to the
   // native code. If an AEC dump is already in progress, it will be stopped and
   // a new one will start using the provided file.
   public boolean startAecDump(int file_descriptor, int filesize_limit_bytes) {
+    checkPeerConnectionFactoryExists();
     return nativeStartAecDump(nativeFactory, file_descriptor, filesize_limit_bytes);
   }
 
   // Stops recording an AEC dump. If no AEC dump is currently being recorded,
   // this call will have no effect.
   public void stopAecDump() {
+    checkPeerConnectionFactoryExists();
     nativeStopAecDump(nativeFactory);
   }
 
   public void dispose() {
+    checkPeerConnectionFactoryExists();
     nativeFreeFactory(nativeFactory);
     networkThread = null;
     workerThread = null;
     signalingThread = null;
     MediaCodecVideoEncoder.disposeEglContext();
     MediaCodecVideoDecoder.disposeEglContext();
+    nativeFactory = 0;
   }
 
   public void threadsCallbacks() {
+    checkPeerConnectionFactoryExists();
     nativeInvokeThreadsCallbacks(nativeFactory);
   }
 
   /** Returns a pointer to the native webrtc::PeerConnectionFactoryInterface. */
   public long getNativePeerConnectionFactory() {
+    checkPeerConnectionFactoryExists();
     return nativeGetNativePeerConnectionFactory(nativeFactory);
   }
 
   /** Returns a pointer to the native OwnedFactoryAndThreads object */
   public long getNativeOwnedFactoryAndThreads() {
+    checkPeerConnectionFactoryExists();
     return nativeFactory;
   }
 
+  private void checkPeerConnectionFactoryExists() {
+    if (nativeFactory == 0) {
+      throw new IllegalStateException("PeerConnectionFactory has been disposed.");
+    }
+  }
+
   private static void printStackTrace(@Nullable Thread thread, String threadName) {
     if (thread != null) {
       StackTraceElement[] stackTraces = thread.getStackTrace();
diff --git a/sdk/android/api/org/webrtc/RtpReceiver.java b/sdk/android/api/org/webrtc/RtpReceiver.java
index 4da7537..8be494f 100644
--- a/sdk/android/api/org/webrtc/RtpReceiver.java
+++ b/sdk/android/api/org/webrtc/RtpReceiver.java
@@ -22,7 +22,7 @@
     public void onFirstPacketReceived(MediaStreamTrack.MediaType media_type);
   }
 
-  final long nativeRtpReceiver;
+  private long nativeRtpReceiver;
   private long nativeObserver;
 
   @Nullable private MediaStreamTrack cachedTrack;
@@ -40,28 +40,34 @@
   }
 
   public boolean setParameters(@Nullable RtpParameters parameters) {
+    checkRtpReceiverExists();
     return parameters == null ? false : nativeSetParameters(nativeRtpReceiver, parameters);
   }
 
   public RtpParameters getParameters() {
+    checkRtpReceiverExists();
     return nativeGetParameters(nativeRtpReceiver);
   }
 
   public String id() {
+    checkRtpReceiverExists();
     return nativeGetId(nativeRtpReceiver);
   }
 
   @CalledByNative
   public void dispose() {
+    checkRtpReceiverExists();
     cachedTrack.dispose();
     if (nativeObserver != 0) {
       nativeUnsetObserver(nativeRtpReceiver, nativeObserver);
       nativeObserver = 0;
     }
     JniCommon.nativeReleaseRef(nativeRtpReceiver);
+    nativeRtpReceiver = 0;
   }
 
   public void SetObserver(Observer observer) {
+    checkRtpReceiverExists();
     // Unset the existing one before setting a new one.
     if (nativeObserver != 0) {
       nativeUnsetObserver(nativeRtpReceiver, nativeObserver);
@@ -70,9 +76,16 @@
   }
 
   public void setFrameDecryptor(FrameDecryptor frameDecryptor) {
+    checkRtpReceiverExists();
     nativeSetFrameDecryptor(nativeRtpReceiver, frameDecryptor.getNativeFrameDecryptor());
   }
 
+  private void checkRtpReceiverExists() {
+    if (nativeRtpReceiver == 0) {
+      throw new IllegalStateException("RtpReceiver has been disposed.");
+    }
+  }
+
   // This should increment the reference count of the track.
   // Will be released in dispose().
   private static native long nativeGetTrack(long rtpReceiver);
diff --git a/sdk/android/api/org/webrtc/RtpSender.java b/sdk/android/api/org/webrtc/RtpSender.java
index e2a9932..9d7451d 100644
--- a/sdk/android/api/org/webrtc/RtpSender.java
+++ b/sdk/android/api/org/webrtc/RtpSender.java
@@ -14,7 +14,7 @@
 
 /** Java wrapper for a C++ RtpSenderInterface. */
 public class RtpSender {
-  final long nativeRtpSender;
+  private long nativeRtpSender;
 
   @Nullable private MediaStreamTrack cachedTrack;
   private boolean ownsTrack = true;
@@ -45,7 +45,8 @@
    * @return              true on success and false on failure.
    */
   public boolean setTrack(@Nullable MediaStreamTrack track, boolean takeOwnership) {
-    if (!nativeSetTrack(nativeRtpSender, (track == null) ? 0 : track.nativeTrack)) {
+    checkRtpSenderExists();
+    if (!nativeSetTrack(nativeRtpSender, (track == null) ? 0 : track.getNativeMediaStreamTrack())) {
       return false;
     }
     if (cachedTrack != null && ownsTrack) {
@@ -62,14 +63,17 @@
   }
 
   public boolean setParameters(RtpParameters parameters) {
+    checkRtpSenderExists();
     return nativeSetParameters(nativeRtpSender, parameters);
   }
 
   public RtpParameters getParameters() {
+    checkRtpSenderExists();
     return nativeGetParameters(nativeRtpSender);
   }
 
   public String id() {
+    checkRtpSenderExists();
     return nativeGetId(nativeRtpSender);
   }
 
@@ -79,10 +83,12 @@
   }
 
   public void setFrameEncryptor(FrameEncryptor frameEncryptor) {
+    checkRtpSenderExists();
     nativeSetFrameEncryptor(nativeRtpSender, frameEncryptor.getNativeFrameEncryptor());
   }
 
   public void dispose() {
+    checkRtpSenderExists();
     if (dtmfSender != null) {
       dtmfSender.dispose();
     }
@@ -90,6 +96,19 @@
       cachedTrack.dispose();
     }
     JniCommon.nativeReleaseRef(nativeRtpSender);
+    nativeRtpSender = 0;
+  }
+
+  /** Returns a pointer to webrtc::RtpSenderInterface. */
+  long getNativeRtpSender() {
+    checkRtpSenderExists();
+    return nativeRtpSender;
+  }
+
+  private void checkRtpSenderExists() {
+    if (nativeRtpSender == 0) {
+      throw new IllegalStateException("RtpSender has been disposed.");
+    }
   }
 
   private static native boolean nativeSetTrack(long rtpSender, long nativeTrack);
diff --git a/sdk/android/api/org/webrtc/RtpTransceiver.java b/sdk/android/api/org/webrtc/RtpTransceiver.java
index 93326a4..a4a5aa0 100644
--- a/sdk/android/api/org/webrtc/RtpTransceiver.java
+++ b/sdk/android/api/org/webrtc/RtpTransceiver.java
@@ -13,7 +13,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import org.webrtc.RtpParameters.Encoding;
 
 /**
  * Java wrapper for a C++ RtpTransceiverInterface.
@@ -96,7 +95,7 @@
     }
   }
 
-  private final long nativeRtpTransceiver;
+  private long nativeRtpTransceiver;
   private RtpSender cachedSender;
   private RtpReceiver cachedReceiver;
 
@@ -112,6 +111,7 @@
    * type as well.
    */
   public MediaStreamTrack.MediaType getMediaType() {
+    checkRtpTransceiverExists();
     return nativeGetMediaType(nativeRtpTransceiver);
   }
 
@@ -122,6 +122,7 @@
    * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-mid
    */
   public String getMid() {
+    checkRtpTransceiverExists();
     return nativeGetMid(nativeRtpTransceiver);
   }
 
@@ -153,6 +154,7 @@
    * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stopped
    */
   public boolean isStopped() {
+    checkRtpTransceiverExists();
     return nativeStopped(nativeRtpTransceiver);
   }
 
@@ -162,6 +164,7 @@
    * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction
    */
   public RtpTransceiverDirection getDirection() {
+    checkRtpTransceiverExists();
     return nativeDirection(nativeRtpTransceiver);
   }
 
@@ -172,6 +175,7 @@
    * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-currentdirection
    */
   public RtpTransceiverDirection getCurrentDirection() {
+    checkRtpTransceiverExists();
     return nativeCurrentDirection(nativeRtpTransceiver);
   }
 
@@ -183,6 +187,7 @@
    * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction
    */
   public void setDirection(RtpTransceiverDirection rtpTransceiverDirection) {
+    checkRtpTransceiverExists();
     nativeSetDirection(nativeRtpTransceiver, rtpTransceiverDirection);
   }
 
@@ -192,14 +197,23 @@
    * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stop
    */
   public void stop() {
+    checkRtpTransceiverExists();
     nativeStop(nativeRtpTransceiver);
   }
 
   @CalledByNative
   public void dispose() {
+    checkRtpTransceiverExists();
     cachedSender.dispose();
     cachedReceiver.dispose();
     JniCommon.nativeReleaseRef(nativeRtpTransceiver);
+    nativeRtpTransceiver = 0;
+  }
+
+  private void checkRtpTransceiverExists() {
+    if (nativeRtpTransceiver == 0) {
+      throw new IllegalStateException("RtpTransceiver has been disposed.");
+    }
   }
 
   private static native MediaStreamTrack.MediaType nativeGetMediaType(long rtpTransceiver);
diff --git a/sdk/android/api/org/webrtc/TurnCustomizer.java b/sdk/android/api/org/webrtc/TurnCustomizer.java
index bfc72ea..41bedb7 100644
--- a/sdk/android/api/org/webrtc/TurnCustomizer.java
+++ b/sdk/android/api/org/webrtc/TurnCustomizer.java
@@ -12,20 +12,30 @@
 
 /** Java wrapper for a C++ TurnCustomizer. */
 public class TurnCustomizer {
-  final long nativeTurnCustomizer;
+  private long nativeTurnCustomizer;
 
   public TurnCustomizer(long nativeTurnCustomizer) {
     this.nativeTurnCustomizer = nativeTurnCustomizer;
   }
 
   public void dispose() {
+    checkTurnCustomizerExists();
     nativeFreeTurnCustomizer(nativeTurnCustomizer);
+    nativeTurnCustomizer = 0;
   }
 
   private static native void nativeFreeTurnCustomizer(long turnCustomizer);
 
+  /** Return a pointer to webrtc::TurnCustomizer. */
   @CalledByNative
   long getNativeTurnCustomizer() {
+    checkTurnCustomizerExists();
     return nativeTurnCustomizer;
   }
+
+  private void checkTurnCustomizerExists() {
+    if (nativeTurnCustomizer == 0) {
+      throw new IllegalStateException("TurnCustomizer has been disposed.");
+    }
+  }
 }
diff --git a/sdk/android/api/org/webrtc/VideoSource.java b/sdk/android/api/org/webrtc/VideoSource.java
index e0c6167..7f9f3e5 100644
--- a/sdk/android/api/org/webrtc/VideoSource.java
+++ b/sdk/android/api/org/webrtc/VideoSource.java
@@ -30,13 +30,18 @@
    * maintain the input orientation, so it doesn't matter if e.g. 1280x720 or 720x1280 is requested.
    */
   public void adaptOutputFormat(int width, int height, int fps) {
-    nativeAdaptOutputFormat(nativeSource, width, height, fps);
+    nativeAdaptOutputFormat(getNativeVideoTrackSource(), width, height, fps);
   }
 
   public CapturerObserver getCapturerObserver() {
     return capturerObserver;
   }
 
+  /** Returns a pointer to webrtc::VideoTrackSourceInterface. */
+  long getNativeVideoTrackSource() {
+    return getNativeMediaSource();
+  }
+
   // Returns source->internal() from webrtc::VideoTrackSourceProxy.
   private static native long nativeGetInternalSource(long source);
   private static native void nativeAdaptOutputFormat(long source, int width, int height, int fps);
diff --git a/sdk/android/api/org/webrtc/VideoTrack.java b/sdk/android/api/org/webrtc/VideoTrack.java
index 5495ee8..5e33e1e 100644
--- a/sdk/android/api/org/webrtc/VideoTrack.java
+++ b/sdk/android/api/org/webrtc/VideoTrack.java
@@ -10,8 +10,8 @@
 
 package org.webrtc;
 
-import java.util.IdentityHashMap;
 import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import java.util.List;
 
 /** Java version of VideoTrackInterface. */
@@ -39,7 +39,7 @@
     if (!sinks.containsKey(sink)) {
       final long nativeSink = nativeWrapSink(sink);
       sinks.put(sink, nativeSink);
-      nativeAddSink(nativeTrack, nativeSink);
+      nativeAddSink(getNativeMediaStreamTrack(), nativeSink);
     }
   }
 
@@ -51,7 +51,7 @@
   public void removeSink(VideoSink sink) {
     final Long nativeSink = sinks.remove(sink);
     if (nativeSink != null) {
-      nativeRemoveSink(nativeTrack, nativeSink);
+      nativeRemoveSink(getNativeMediaStreamTrack(), nativeSink);
       nativeFreeSink(nativeSink);
     }
   }
@@ -59,13 +59,18 @@
   @Override
   public void dispose() {
     for (long nativeSink : sinks.values()) {
-      nativeRemoveSink(nativeTrack, nativeSink);
+      nativeRemoveSink(getNativeMediaStreamTrack(), nativeSink);
       nativeFreeSink(nativeSink);
     }
     sinks.clear();
     super.dispose();
   }
 
+  /** Returns a pointer to webrtc::VideoTrackInterface. */
+  long getNativeVideoTrack() {
+    return getNativeMediaStreamTrack();
+  }
+
   private static native void nativeAddSink(long track, long nativeSink);
   private static native void nativeRemoveSink(long track, long nativeSink);
   private static native long nativeWrapSink(VideoSink sink);