| Index: webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
 | 
| diff --git a/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java b/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
 | 
| index 6352bf799f7be0a74364197a6c1756091dc736e6..78d042df50c850bf468739907953deee2bb6eab0 100644
 | 
| --- a/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
 | 
| +++ b/webrtc/api/java/android/org/webrtc/VideoCapturerAndroid.java
 | 
| @@ -38,13 +38,14 @@
 | 
|  // arbitrary Java threads. All public entry points are thread safe, and delegate the work to the
 | 
|  // camera thread. The internal *OnCameraThread() methods must check |camera| for null to check if
 | 
|  // the camera has been stopped.
 | 
| -// TODO(magjed): This class name is now confusing - rename to Camera1VideoCapturer.
 | 
|  @SuppressWarnings("deprecation")
 | 
|  public class VideoCapturerAndroid implements
 | 
| -    CameraVideoCapturer,
 | 
| +    VideoCapturer,
 | 
|      android.hardware.Camera.PreviewCallback,
 | 
|      SurfaceTextureHelper.OnTextureFrameAvailableListener {
 | 
|    private final static String TAG = "VideoCapturerAndroid";
 | 
| +  private final static int CAMERA_OBSERVER_PERIOD_MS = 2000;
 | 
| +  private final static int CAMERA_FREEZE_REPORT_TIMOUT_MS = 4000;
 | 
|    private static final int CAMERA_STOP_TIMEOUT_MS = 7000;
 | 
|  
 | 
|    private boolean isDisposed = false;
 | 
| @@ -59,7 +60,7 @@
 | 
|    private final Object cameraIdLock = new Object();
 | 
|    private int id;
 | 
|    private android.hardware.Camera.CameraInfo info;
 | 
| -  private CameraStatistics cameraStatistics;
 | 
| +  private final CameraStatistics cameraStatistics;
 | 
|    // Remember the requested format in case we want to switch cameras.
 | 
|    private int requestedWidth;
 | 
|    private int requestedHeight;
 | 
| @@ -103,6 +104,84 @@
 | 
|      }
 | 
|    };
 | 
|  
 | 
| +  // Camera observer - monitors camera framerate. Observer is executed on camera thread.
 | 
| +  private final Runnable cameraObserver = new Runnable() {
 | 
| +    private int freezePeriodCount;
 | 
| +    @Override
 | 
| +    public void run() {
 | 
| +      int cameraFramesCount = cameraStatistics.getAndResetFrameCount();
 | 
| +      int cameraFps = (cameraFramesCount * 1000 + CAMERA_OBSERVER_PERIOD_MS / 2)
 | 
| +          / CAMERA_OBSERVER_PERIOD_MS;
 | 
| +
 | 
| +      Logging.d(TAG, "Camera fps: " + cameraFps +".");
 | 
| +      if (cameraFramesCount == 0) {
 | 
| +        ++freezePeriodCount;
 | 
| +        if (CAMERA_OBSERVER_PERIOD_MS * freezePeriodCount >= CAMERA_FREEZE_REPORT_TIMOUT_MS
 | 
| +            && eventsHandler != null) {
 | 
| +          Logging.e(TAG, "Camera freezed.");
 | 
| +          if (surfaceHelper.isTextureInUse()) {
 | 
| +            // This can only happen if we are capturing to textures.
 | 
| +            eventsHandler.onCameraFreezed("Camera failure. Client must return video buffers.");
 | 
| +          } else {
 | 
| +            eventsHandler.onCameraFreezed("Camera failure.");
 | 
| +          }
 | 
| +          return;
 | 
| +        }
 | 
| +      } else {
 | 
| +        freezePeriodCount = 0;
 | 
| +      }
 | 
| +      maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, this);
 | 
| +    }
 | 
| +  };
 | 
| +
 | 
| +  private static class CameraStatistics {
 | 
| +    private int frameCount = 0;
 | 
| +    private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker();
 | 
| +
 | 
| +    CameraStatistics() {
 | 
| +      threadChecker.detachThread();
 | 
| +    }
 | 
| +
 | 
| +    public void addFrame() {
 | 
| +      threadChecker.checkIsOnValidThread();
 | 
| +      ++frameCount;
 | 
| +    }
 | 
| +
 | 
| +    public int getAndResetFrameCount() {
 | 
| +      threadChecker.checkIsOnValidThread();
 | 
| +      int count = frameCount;
 | 
| +      frameCount = 0;
 | 
| +      return count;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  public static interface CameraEventsHandler {
 | 
| +    // Camera error handler - invoked when camera can not be opened
 | 
| +    // or any camera exception happens on camera thread.
 | 
| +    void onCameraError(String errorDescription);
 | 
| +
 | 
| +    // Invoked when camera stops receiving frames
 | 
| +    void onCameraFreezed(String errorDescription);
 | 
| +
 | 
| +    // Callback invoked when camera is opening.
 | 
| +    void onCameraOpening(int cameraId);
 | 
| +
 | 
| +    // Callback invoked when first camera frame is available after camera is opened.
 | 
| +    void onFirstFrameAvailable();
 | 
| +
 | 
| +    // Callback invoked when camera closed.
 | 
| +    void onCameraClosed();
 | 
| +  }
 | 
| +
 | 
| +  // Camera switch handler - one of these functions are invoked with the result of switchCamera().
 | 
| +  // The callback may be called on an arbitrary thread.
 | 
| +  public interface CameraSwitchHandler {
 | 
| +    // Invoked on success. |isFrontCamera| is true if the new camera is front facing.
 | 
| +    void onCameraSwitchDone(boolean isFrontCamera);
 | 
| +    // Invoked on failure, e.g. camera is stopped or only one camera available.
 | 
| +    void onCameraSwitchError(String errorDescription);
 | 
| +  }
 | 
| +
 | 
|    public static VideoCapturerAndroid create(String name,
 | 
|        CameraEventsHandler eventsHandler) {
 | 
|      return VideoCapturerAndroid.create(name, eventsHandler, false /* captureToTexture */);
 | 
| @@ -137,7 +216,6 @@
 | 
|  
 | 
|    // Switch camera to the next valid camera id. This can only be called while
 | 
|    // the camera is running.
 | 
| -  @Override
 | 
|    public void switchCamera(final CameraSwitchHandler switchEventsHandler) {
 | 
|      if (android.hardware.Camera.getNumberOfCameras() < 2) {
 | 
|        if (switchEventsHandler != null) {
 | 
| @@ -221,6 +299,7 @@
 | 
|      this.id = cameraId;
 | 
|      this.eventsHandler = eventsHandler;
 | 
|      isCapturingToTexture = captureToTexture;
 | 
| +    cameraStatistics = new CameraStatistics();
 | 
|      Logging.d(TAG, "VideoCapturerAndroid isCapturingToTexture : " + isCapturingToTexture);
 | 
|    }
 | 
|  
 | 
| @@ -381,7 +460,7 @@
 | 
|        }
 | 
|  
 | 
|        // Start camera observer.
 | 
| -      cameraStatistics = new CameraStatistics(surfaceHelper, eventsHandler);
 | 
| +      maybePostDelayedOnCameraThread(CAMERA_OBSERVER_PERIOD_MS, cameraObserver);
 | 
|        return;
 | 
|      } catch (RuntimeException e) {
 | 
|        error = e;
 | 
| @@ -534,10 +613,8 @@
 | 
|      if (surfaceHelper != null) {
 | 
|        surfaceHelper.stopListening();
 | 
|      }
 | 
| -    if (cameraStatistics != null) {
 | 
| -      cameraStatistics.release();
 | 
| -      cameraStatistics = null;
 | 
| -    }
 | 
| +    cameraThreadHandler.removeCallbacks(cameraObserver);
 | 
| +    cameraStatistics.getAndResetFrameCount();
 | 
|      Logging.d(TAG, "Stop preview.");
 | 
|      if (camera != null) {
 | 
|        camera.stopPreview();
 | 
| 
 |