Update CarEvsCameraActivity and CarEvsCameraPreviewActivity
- CarEvsCameraActivity explicitly stops monitoring the car service's
status in its onDestroy(), to make sure it does not respond to any
status changes before it is handled by the garbage collector.
- CarEvsCameraPreviewActivity stops rendering the contents but keeps
circulating frame buffers when it becomes invisible.
- When the car service is restored from the incidents,
CarEvsCameraPreviewApp will request staring a video stream if it was
initially launched by the user.
Fix: 235697779
Test: Run EVS regression tests
Change-Id: Id81f1dba1d98de9f0b32fb1838eae877d91aa63b
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraActivity.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraActivity.java
index e7a9ccd..4c3eaa1 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraActivity.java
@@ -16,8 +16,11 @@
package com.google.android.car.evs;
+import static android.car.evs.CarEvsManager.ERROR_NONE;
+
import android.app.Activity;
import android.car.Car;
+import android.car.Car.CarServiceLifecycleListener;
import android.car.evs.CarEvsManager;
import android.os.Bundle;
import android.util.Log;
@@ -26,21 +29,42 @@
private static final String TAG = CarEvsCameraActivity.class.getSimpleName();
private static final int CAR_WAIT_TIMEOUT_MS = 3_000;
+ /** CarService status listener */
+ private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
+ if (!ready) {
+ return;
+ }
+
+ try {
+ CarEvsManager evsManager = (CarEvsManager) car.getCarManager(
+ Car.CAR_EVS_SERVICE);
+ if (evsManager.startActivity(CarEvsManager.SERVICE_TYPE_REARVIEW) != ERROR_NONE) {
+ Log.e(TAG, "Failed to start a camera preview activity");
+ }
+ } finally {
+ mCar = car;
+ finish();
+ }
+ };
+
+ private Car mCar;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Car.createCar(getApplicationContext(), /* handler = */ null, CAR_WAIT_TIMEOUT_MS,
- (car, ready) -> {
- if (!ready) {
- finish();
- return;
- }
- CarEvsManager evsManager = (CarEvsManager) car.getCarManager(
- Car.CAR_EVS_SERVICE);
- int result = evsManager.startActivity(CarEvsManager.SERVICE_TYPE_REARVIEW);
- Log.i(TAG, "startActivity(): " + result);
- finish();
- });
+ mCarServiceLifecycleListener);
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ if (mCar != null) {
+ // Explicitly stops monitoring the car service's status
+ mCar.disconnect();
+ }
+
+ super.onDestroy();
}
}
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
index 90fd608..bb1c6a1 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
@@ -16,6 +16,7 @@
package com.google.android.car.evs;
+import static android.car.evs.CarEvsManager.ERROR_NONE;
import static android.hardware.display.DisplayManager.DisplayListener;
import android.app.Activity;
@@ -41,6 +42,8 @@
import android.view.WindowManager;
import android.widget.LinearLayout;
+import androidx.annotation.GuardedBy;
+
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -48,7 +51,35 @@
public class CarEvsCameraPreviewActivity extends Activity {
private static final String TAG = CarEvsCameraPreviewActivity.class.getSimpleName();
+ /**
+ * Defines internal states.
+ */
+ private final static int STREAM_STATE_STOPPED = 0;
+ private final static int STREAM_STATE_VISIBLE = 1;
+ private final static int STREAM_STATE_INVISIBLE = 2;
+ private final static int STREAM_STATE_LOST = 3;
+
+ private static String streamStateToString(int state) {
+ switch (state) {
+ case STREAM_STATE_STOPPED:
+ return "STOPPED";
+
+ case STREAM_STATE_VISIBLE:
+ return "VISIBLE";
+
+ case STREAM_STATE_INVISIBLE:
+ return "INVISIBLE";
+
+ case STREAM_STATE_LOST:
+ return "LOST";
+
+ default:
+ return "UNKNOWN: " + state;
+ }
+ }
+
/** Buffer queue to store references of received frames */
+ @GuardedBy("mLock")
private final ArrayList<CarEvsBufferDescriptor> mBufferQueue = new ArrayList<>();
private final Object mLock = new Object();
@@ -68,11 +99,16 @@
private int mDisplayState = Display.STATE_OFF;
/** Tells whether or not a video stream is running */
- private boolean mStreamRunning = false;
+ @GuardedBy("mLock")
+ private int mStreamState = STREAM_STATE_STOPPED;
+ @GuardedBy("mLock")
private Car mCar;
+
+ @GuardedBy("mLock")
private CarEvsManager mEvsManager;
+ @GuardedBy("mLock")
private IBinder mSessionToken;
private boolean mUseSystemWindow;
@@ -93,9 +129,15 @@
@Override
public void onNewFrame(CarEvsBufferDescriptor buffer) {
- // Enqueues a new frame and posts a rendering job
- synchronized (mBufferQueue) {
- mBufferQueue.add(buffer);
+ synchronized (mLock) {
+ if (mStreamState == STREAM_STATE_INVISIBLE) {
+ // When the activity becomes invisible (e.g. goes background), we immediately
+ // returns received frame buffers instead of stopping a video stream.
+ returnBufferLocked(buffer);
+ } else {
+ // Enqueues a new frame and posts a rendering job
+ mBufferQueue.add(buffer);
+ }
}
}
};
@@ -119,34 +161,37 @@
int state = decideViewVisibility();
synchronized (mLock) {
mDisplayState = state;
- handleVideoStreamLocked();
+ handleVideoStreamLocked(state == Display.STATE_ON ?
+ STREAM_STATE_VISIBLE : STREAM_STATE_INVISIBLE);
}
}
};
/** CarService status listener */
private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
- if (!ready) {
- Log.d(TAG, "Disconnected from the Car Service");
- // Upon the CarService's accidental termination, CarEvsService gets released and
- // CarEvsManager deregisters all listeners and callbacks. So, we simply release
- // CarEvsManager instance and update the status in handleVideoStreamLocked().
+ try {
synchronized (mLock) {
- mCar = null;
- mEvsManager = null;
- handleVideoStreamLocked();
- }
- } else {
- Log.d(TAG, "Connected to the Car Service");
- try {
- synchronized (mLock) {
- mCar = car;
- mEvsManager = (CarEvsManager) car.getCarManager(Car.CAR_EVS_SERVICE);
- handleVideoStreamLocked();
+ mCar = ready ? car : null;
+ mEvsManager = ready ? (CarEvsManager) car.getCarManager(Car.CAR_EVS_SERVICE) : null;
+ if (!ready) {
+ if (!mUseSystemWindow) {
+ // If we were launched by the user manually, we enter the LOST state and
+ // wait for the car service's restoration.
+ handleVideoStreamLocked(STREAM_STATE_LOST);
+ } else {
+ // If we were launched by the system,we will clean up the states and
+ // then finish; the car service will request a new instance when it comes
+ // back from the incident while the system still requires the rearview.
+ handleVideoStreamLocked(STREAM_STATE_STOPPED);
+ finish();
+ }
+ } else {
+ // We request to start a video stream if we get connected to the car service.
+ handleVideoStreamLocked(STREAM_STATE_VISIBLE);
}
- } catch (CarNotConnectedException err) {
- Log.e(TAG, "Failed to connect to the Car Service");
}
+ } catch (CarNotConnectedException err) {
+ Log.e(TAG, "Failed to connect to the Car Service");
}
};
@@ -246,78 +291,105 @@
}
@Override
- protected void onStart() {
- Log.d(TAG, "onStart");
- super.onStart();
- handleVideoStreamLocked();
+ protected void onRestart() {
+ Log.d(TAG, "onRestart");
+ super.onRestart();
+ synchronized (mLock) {
+ // When we come back to the top task, we start rendering the view.
+ handleVideoStreamLocked(STREAM_STATE_VISIBLE);
+ }
}
-
@Override
protected void onStop() {
Log.d(TAG, "onStop");
- super.onStop();
+ try {
+ synchronized (mLock) {
+ handleVideoStreamLocked(STREAM_STATE_INVISIBLE);
+ }
+ } finally {
+ super.onStop();
+ }
}
@Override
protected void onDestroy() {
- super.onDestroy();
Log.d(TAG, "onDestroy");
- // Request to stop current service and unregister a status listener
- synchronized (mBufferQueue) {
- mBufferQueue.clear();
- }
- synchronized (mLock) {
- if (mEvsManager != null) {
- mEvsManager.stopVideoStream();
- mEvsManager.stopActivity();
- mEvsManager.clearStatusListener();
+ try {
+ // Request to stop current service and unregister a status listener
+ synchronized (mLock) {
+ if (mEvsManager != null) {
+ mEvsManager.stopActivity();
+ mEvsManager.clearStatusListener();
+ }
+ if (mCar != null) {
+ mCar.disconnect();
+ }
}
- if (mCar != null) {
- mCar.disconnect();
- }
- }
- mDisplayManager.unregisterDisplayListener(mDisplayListener);
- if (mUseSystemWindow) {
- WindowManager wm = getSystemService(WindowManager.class);
- wm.removeView(mRootView);
- }
- unregisterReceiver(mBroadcastReceiver);
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ if (mUseSystemWindow) {
+ WindowManager wm = getSystemService(WindowManager.class);
+ wm.removeView(mRootView);
+ }
+
+ unregisterReceiver(mBroadcastReceiver);
+ } finally {
+ super.onDestroy();
+ }
}
- private void handleVideoStreamLocked() {
- if (mEvsManager == null) {
- Log.w(TAG, "CarEvsManager is not available.");
+ @GuardedBy("mLock")
+ private void handleVideoStreamLocked(int newState) {
+ Log.d(TAG, "Requested: " + streamStateToString(mStreamState) + " -> " +
+ streamStateToString(newState));
+ if (newState == mStreamState) {
+ // Safely ignore a request of transitioning to the current state.
return;
}
- if (mDisplayState == Display.STATE_ON) {
- // We show a camera preview only when the activity has been resumed and the display is
- // on.
- if (!mStreamRunning) {
- Log.d(TAG, "Request to start a video stream");
- mEvsManager.startVideoStream(CarEvsManager.SERVICE_TYPE_REARVIEW,
- mSessionToken, mCallbackExecutor, mStreamHandler);
- mStreamRunning = true;
- }
+ boolean needToUpdateState = false;
+ switch (newState) {
+ case STREAM_STATE_STOPPED:
+ if (mEvsManager != null) {
+ mEvsManager.stopVideoStream();
+ mBufferQueue.clear();
+ needToUpdateState = true;
+ } else {
+ Log.w(TAG, "EvsManager is not available");
+ }
+ break;
- return;
+ case STREAM_STATE_VISIBLE:
+ // Starts a video stream
+ if (mEvsManager != null) {
+ int result = mEvsManager.startVideoStream(CarEvsManager.SERVICE_TYPE_REARVIEW,
+ mSessionToken, mCallbackExecutor, mStreamHandler);
+ if (result != ERROR_NONE) {
+ Log.e(TAG, "Failed to start a video stream, error = " + result);
+ } else {
+ needToUpdateState = true;
+ }
+ } else {
+ Log.w(TAG, "EvsManager is not available");
+ }
+ break;
+
+ case STREAM_STATE_INVISIBLE:
+ needToUpdateState = true;
+ break;
+
+ case STREAM_STATE_LOST:
+ needToUpdateState = true;
+ break;
+
+ default:
+ throw new IllegalArgumentException();
}
- // Otherwise, we do not need a video stream.
- if (mStreamRunning) {
- Log.d(TAG, "Request to stop a video stream");
- mEvsManager.stopVideoStream();
- mStreamRunning = false;
-
- // We already stopped an active video stream so are safe to drop all buffer references.
- synchronized (mBufferQueue) {
- mBufferQueue.clear();
- }
-
- // Clear a buffer reference CarEvsCameraGLSurfaceView holds.
- mEvsView.clearBuffer();
+ if (needToUpdateState) {
+ mStreamState = newState;
+ Log.d(TAG, "Completed: " + streamStateToString(mStreamState));
}
}
@@ -337,8 +409,8 @@
}
/** Get a new frame */
- public CarEvsBufferDescriptor getNewFrame() {
- synchronized (mBufferQueue) {
+ CarEvsBufferDescriptor getNewFrame() {
+ synchronized (mLock) {
if (mBufferQueue.isEmpty()) {
return null;
}
@@ -353,7 +425,18 @@
}
/** Request to return a buffer we're done with */
- public void returnBuffer(CarEvsBufferDescriptor buffer) {
- mEvsManager.returnFrameBuffer(buffer);
+ void returnBuffer(CarEvsBufferDescriptor buffer) {
+ synchronized (mLock) {
+ returnBufferLocked(buffer);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void returnBufferLocked(CarEvsBufferDescriptor buffer) {
+ try {
+ mEvsManager.returnFrameBuffer(buffer);
+ } catch (Exception e) {
+ Log.w(TAG, "CarEvsService is not available.");
+ }
}
}