blob: cd6c840a08d362d5c74b3b5da72e7b066bab023d [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.car.evs;
import static android.hardware.display.DisplayManager.DisplayListener;
import android.app.Activity;
import android.car.Car;
import android.car.Car.CarServiceLifecycleListener;
import android.car.CarNotConnectedException;
import android.car.evs.CarEvsBufferDescriptor;
import android.car.evs.CarEvsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CarEvsCameraPreviewActivity extends Activity {
private static final String TAG = CarEvsCameraPreviewActivity.class.getSimpleName();
/** Buffer queue to store references of received frames */
private final ArrayList<CarEvsBufferDescriptor> mBufferQueue = new ArrayList<>();
private final Object mLock = new Object();
/** Callback executors */
private final ExecutorService mCallbackExecutor = Executors.newFixedThreadPool(1);
/** GL backed surface view to render the camera preview */
private CarEvsCameraGLSurfaceView mEvsView;
private ViewGroup mRootView;
private LinearLayout mPreviewContainer;
/** Display manager to monitor the display's state */
private DisplayManager mDisplayManager;
/** Current display state */
private int mDisplayState = Display.STATE_OFF;
/** Tells whether or not a video stream is running */
private boolean mStreamRunning = false;
private Car mCar;
private CarEvsManager mEvsManager;
private IBinder mSessionToken;
private boolean mUseSystemWindow;
/** Callback to listen to EVS stream */
private final CarEvsManager.CarEvsStreamCallback mStreamHandler =
new CarEvsManager.CarEvsStreamCallback() {
@Override
public void onStreamEvent(int event) {
// This reference implementation only monitors a stream event without any action.
Log.i(TAG, "Received: " + event);
if (event == CarEvsManager.STREAM_EVENT_STREAM_STOPPED) {
finish();
}
}
@Override
public void onNewFrame(CarEvsBufferDescriptor buffer) {
// Enqueues a new frame and posts a rendering job
synchronized (mBufferQueue) {
mBufferQueue.add(buffer);
}
}
};
/**
* The Activity with showWhenLocked doesn't go to sleep even if the display sleeps.
* So we'd like to monitor the display state and react on it manually.
*/
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {}
@Override
public void onDisplayRemoved(int displayId) {}
@Override
public void onDisplayChanged(int displayId) {
if (displayId != Display.DEFAULT_DISPLAY) {
return;
}
int state = decideViewVisibility();
synchronized (mLock) {
mDisplayState = state;
handleVideoStreamLocked();
}
}
};
/** 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().
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();
}
} catch (CarNotConnectedException err) {
Log.e(TAG, "Failed to connect to the Car Service");
}
}
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
finish();
} else {
Log.e(TAG, "Unexpected intent " + intent);
}
}
};
// To close the PreviewActiivty when Home button is clicked.
private void registerBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
// Need to register the receiver for all users, because we want to receive the Intent after
// the user is changed.
registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
registerBroadcastReceiver();
parseExtra(getIntent());
setShowWhenLocked(true);
mDisplayManager = getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
int state = decideViewVisibility();
synchronized (mLock) {
mDisplayState = state;
}
Car.createCar(getApplicationContext(), /* handler = */ null,
Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
mEvsView = new CarEvsCameraGLSurfaceView(getApplication(), this);
mRootView = (ViewGroup) LayoutInflater.from(this).inflate(
R.layout.evs_preview_activity, /* root= */ null);
mPreviewContainer = mRootView.findViewById(R.id.evs_preview_container);
LinearLayout.LayoutParams viewParam = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT,
1.0f
);
mEvsView.setLayoutParams(viewParam);
mPreviewContainer.addView(mEvsView, 0);
View closeButton = mRootView.findViewById(R.id.close_button);
if (closeButton != null) {
closeButton.setOnClickListener(v -> finish());
}
int width = WindowManager.LayoutParams.MATCH_PARENT;
int height = WindowManager.LayoutParams.MATCH_PARENT;
if (mUseSystemWindow) {
width = getResources().getDimensionPixelOffset(R.dimen.camera_preview_width);
height = getResources().getDimensionPixelOffset(R.dimen.camera_preview_height);
}
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
width, height,
2020 /* WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY */,
WindowManager.LayoutParams.FLAG_DIM_BEHIND
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.CENTER;
params.dimAmount = getResources().getFloat(R.dimen.config_cameraBackgroundScrim);
if (mUseSystemWindow) {
WindowManager wm = getSystemService(WindowManager.class);
wm.addView(mRootView, params);
} else {
setContentView(mRootView, params);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
parseExtra(intent);
}
private void parseExtra(Intent intent) {
Bundle extras = intent.getExtras();
if (extras == null) {
mSessionToken = null;
return;
}
mSessionToken = extras.getBinder(CarEvsManager.EXTRA_SESSION_TOKEN);
mUseSystemWindow = mSessionToken != null;
}
@Override
protected void onStart() {
Log.d(TAG, "onStart");
super.onStart();
handleVideoStreamLocked();
}
@Override
protected void onStop() {
Log.d(TAG, "onStop");
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();
}
if (mCar != null) {
mCar.disconnect();
}
}
mDisplayManager.unregisterDisplayListener(mDisplayListener);
if (mUseSystemWindow) {
WindowManager wm = getSystemService(WindowManager.class);
wm.removeView(mRootView);
}
unregisterReceiver(mBroadcastReceiver);
}
private void handleVideoStreamLocked() {
if (mEvsManager == null) {
Log.w(TAG, "CarEvsManager is not available.");
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;
}
return;
}
// 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();
}
}
// Hides the view when the display is off to save the system resource, since this has
// 'showWhenLocked' attribute, this will not go to PAUSED state even if the display turns off.
private int decideViewVisibility() {
Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
int state = defaultDisplay.getState();
Log.d(TAG, "decideShowWhenLocked: displayState=" + state);
if (state == Display.STATE_ON) {
getWindow().getDecorView().setVisibility(View.VISIBLE);
} else {
getWindow().getDecorView().setVisibility(View.INVISIBLE);
}
return state;
}
/** Get a new frame */
public CarEvsBufferDescriptor getNewFrame() {
synchronized (mBufferQueue) {
if (mBufferQueue.isEmpty()) {
return null;
}
// The renderer refreshes faster than 30fps so it's okay to fetch the frame from the
// front of the buffer queue always.
CarEvsBufferDescriptor newFrame = mBufferQueue.get(0);
mBufferQueue.remove(0);
return newFrame;
}
}
/** Request to return a buffer we're done with */
public void returnBuffer(CarEvsBufferDescriptor buffer) {
mEvsManager.returnFrameBuffer(buffer);
}
}