/* * Copyright (C) 2009 The Android Open Source Project * 2015 The CyanogenMod 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.android.camera; import static com.android.camera.util.CameraUtil.Assert; import android.content.Context; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; import com.android.camera.app.CameraApp; import com.android.camera.CameraManager.CameraProxy; import org.codeaurora.snapcam.R; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; /** * The class is used to hold an {@code android.hardware.Camera} instance. * *

The {@code open()} and {@code release()} calls are similar to the ones * in {@code android.hardware.Camera}. The difference is if {@code keep()} is * called before {@code release()}, CameraHolder will try to hold the {@code * android.hardware.Camera} instance for a while, so if {@code open()} is * called soon after, we can avoid the cost of {@code open()} in {@code * android.hardware.Camera}. * *

This is used in switching between different modules. */ public class CameraHolder { private static final String TAG = "CameraHolder"; private static final int KEEP_CAMERA_TIMEOUT = 3000; // 3 seconds private CameraProxy mCameraDevice; private long mKeepBeforeTime; // Keep the Camera before this time. private final Handler mHandler; private boolean mCameraOpened; // true if camera is opened private final int mNumberOfCameras; private int mCameraId = -1; // current camera id private int mBackCameraId = -1; private int mFrontCameraId = -1; private CameraInfo[] mInfo; private static CameraProxy mMockCamera[]; private static CameraInfo mMockCameraInfo[]; private static Context mContext; private static boolean mCam2On = false; private ArrayList mCharacteristics = new ArrayList<>(); /* Debug double-open issue */ private static final boolean DEBUG_OPEN_RELEASE = false; private static class OpenReleaseState { long time; int id; String device; String[] stack; } private static ArrayList sOpenReleaseStates = new ArrayList(); private static SimpleDateFormat sDateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS"); private static synchronized void collectState(int id, CameraProxy device) { OpenReleaseState s = new OpenReleaseState(); s.time = System.currentTimeMillis(); s.id = id; if (device == null) { s.device = "(null)"; } else { s.device = device.toString(); } StackTraceElement[] stack = Thread.currentThread().getStackTrace(); String[] lines = new String[stack.length]; for (int i = 0; i < stack.length; i++) { lines[i] = stack[i].toString(); } s.stack = lines; if (sOpenReleaseStates.size() > 10) { sOpenReleaseStates.remove(0); } sOpenReleaseStates.add(s); } private static synchronized void dumpStates() { for (int i = sOpenReleaseStates.size() - 1; i >= 0; i--) { OpenReleaseState s = sOpenReleaseStates.get(i); String date = sDateFormat.format(new Date(s.time)); Log.d(TAG, "State " + i + " at " + date); Log.d(TAG, "mCameraId = " + s.id + ", mCameraDevice = " + s.device); Log.d(TAG, "Stack:"); for (int j = 0; j < s.stack.length; j++) { Log.d(TAG, " " + s.stack[j]); } } } // We store the camera parameters when we actually open the device, // so we can restore them in the subsequent open() requests by the user. // This prevents the parameters set by PhotoModule used by VideoModule // inadvertently. private Parameters mParameters; // Use a singleton. private static CameraHolder sHolder; public static synchronized CameraHolder instance() { if (sHolder == null) { sHolder = new CameraHolder(); } return sHolder; } // need init first of all public static void setCamera2Mode(Context context, boolean cam2On) { mContext = context; mCam2On = cam2On; } private static final int RELEASE_CAMERA = 1; private class MyHandler extends Handler { MyHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch(msg.what) { case RELEASE_CAMERA: synchronized (CameraHolder.this) { // In 'CameraHolder.open', the 'RELEASE_CAMERA' message // will be removed if it is found in the queue. However, // there is a chance that this message has been handled // before being removed. So, we need to add a check // here: if (!mCameraOpened) release(); } break; } } } public static void injectMockCamera(CameraInfo[] info, CameraProxy[] camera) { mMockCameraInfo = info; mMockCamera = camera; sHolder = new CameraHolder(); } private CameraHolder() { HandlerThread ht = new HandlerThread("CameraHolder"); ht.start(); mHandler = new MyHandler(ht.getLooper()); android.hardware.camera2.CameraManager manager = (android.hardware.camera2.CameraManager) mContext.getSystemService( Context.CAMERA_SERVICE); String[] cameraIdList = null; try { cameraIdList = manager.getCameraIdList(); mInfo = new CameraInfo[cameraIdList.length]; for (int i = 0; i < cameraIdList.length; i++) { String cameraId = cameraIdList[i]; CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); Log.d(TAG,"cameraIdList size ="+cameraIdList.length); int facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (facing == CameraCharacteristics.LENS_FACING_FRONT) { CaptureModule.FRONT_ID = i; mFrontCameraId = i; } else if (mBackCameraId != -1) { mBackCameraId = i; } addCameraInfo(i, characteristics); mCharacteristics.add(i, characteristics); } } catch (CameraAccessException e) { e.printStackTrace(); } mNumberOfCameras = cameraIdList == null ? 0 : cameraIdList.length; // get the first (smallest) back and first front camera id for (int i = 0; i < mNumberOfCameras; i++) { if (mBackCameraId == -1 && mInfo[i].facing == CameraCharacteristics.LENS_FACING_BACK) { mBackCameraId = i; } else if (mFrontCameraId == -1 && mInfo[i].facing == CameraCharacteristics.LENS_FACING_FRONT) { mFrontCameraId = i; } } } private void addCameraInfo(int index, CameraCharacteristics characteristics) { mInfo[index] = new CameraInfo(); mInfo[index].facing = characteristics.get(CameraCharacteristics.LENS_FACING); mInfo[index].orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); } public CameraCharacteristics getCameraCharacteristics(int id) { return mCharacteristics.get(id); } public int getNumberOfCameras() { return mNumberOfCameras; } public CameraInfo[] getCameraInfo() { return mInfo; } public synchronized CameraProxy open( Handler handler, int cameraId, CameraManager.CameraOpenErrorCallback cb) { Context context = CameraApp.getContext(); if (DEBUG_OPEN_RELEASE) { collectState(cameraId, mCameraDevice); if (mCameraOpened) { Log.e(TAG, "double open"); dumpStates(); } } Assert(!mCameraOpened); if (mCameraDevice != null && mCameraId != cameraId) { mCameraDevice.release(); mCameraDevice = null; mCameraId = -1; } if (mCameraDevice == null) { Log.v(TAG, "open camera " + cameraId); if (mMockCameraInfo == null) { mCameraDevice = CameraManagerFactory .getAndroidCameraManager().cameraOpen(handler, cameraId, cb); } else { if (mMockCamera != null) { mCameraDevice = mMockCamera[cameraId]; } else { Log.e(TAG, "MockCameraInfo found, but no MockCamera provided."); mCameraDevice = null; } } if (mCameraDevice == null) { Log.e(TAG, "fail to connect Camera:" + mCameraId + ", aborting."); return null; } mCameraId = cameraId; mParameters = mCameraDevice.getCamera().getParameters(); // Manufacturer specific key values String manufacturerKeyValues = context.getResources().getString(R.string.manufacturer_key_values); if (manufacturerKeyValues != null && !manufacturerKeyValues.isEmpty()) { String[] keyValuesArray = manufacturerKeyValues.split(";"); for (String kvPair : keyValuesArray) { String[] manufacturerParamPair = kvPair.split("="); if (!manufacturerParamPair[0].isEmpty() && !manufacturerParamPair[1].isEmpty()) { Log.d(TAG, "Set manufacturer specific parameter " + manufacturerParamPair[0] + "=" + manufacturerParamPair[1]); mParameters.set(manufacturerParamPair[0], manufacturerParamPair[1]); } } mCameraDevice.setParameters(mParameters); } } else { if (!mCameraDevice.reconnect(handler, cb)) { Log.e(TAG, "fail to reconnect Camera:" + mCameraId + ", aborting."); return null; } mCameraDevice.setParameters(mParameters); } mCameraOpened = true; mHandler.removeMessages(RELEASE_CAMERA); mKeepBeforeTime = 0; return mCameraDevice; } /** * Tries to open the hardware camera. If the camera is being used or * unavailable then return {@code null}. */ public synchronized CameraProxy tryOpen( Handler handler, int cameraId, CameraManager.CameraOpenErrorCallback cb) { return (!mCameraOpened ? open(handler, cameraId, cb) : null); } public synchronized void release() { if (DEBUG_OPEN_RELEASE) { collectState(mCameraId, mCameraDevice); } if (mCameraDevice == null) return; long now = System.currentTimeMillis(); if (now < mKeepBeforeTime) { if (mCameraOpened) { mCameraOpened = false; mCameraDevice.stopPreview(); } mHandler.sendEmptyMessageDelayed(RELEASE_CAMERA, mKeepBeforeTime - now); return; } strongRelease(); } public synchronized void strongRelease() { if (mCameraDevice == null) return; mCameraOpened = false; mCameraDevice.release(); mCameraDevice = null; // We must set this to null because it has a reference to Camera. // Camera has references to the listeners. mParameters = null; mCameraId = -1; } public void keep() { keep(KEEP_CAMERA_TIMEOUT); } public synchronized void keep(int time) { // We allow mCameraOpened in either state for the convenience of the // calling activity. The activity may not have a chance to call open() // before the user switches to another activity. mKeepBeforeTime = System.currentTimeMillis() + time; } public int getBackCameraId() { return mBackCameraId; } public int getFrontCameraId() { return mFrontCameraId; } public class CameraInfo { public static final int CAMERA_FACING_FRONT = CameraCharacteristics.LENS_FACING_FRONT; public static final int CAMERA_FACING_BACK = CameraCharacteristics.LENS_FACING_BACK; public int facing; public int orientation; } }