1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import static com.android.camera.Util.Assert;
20
21import android.hardware.Camera.CameraInfo;
22import android.hardware.Camera.Parameters;
23import android.os.Build;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Looper;
27import android.os.Message;
28import android.util.Log;
29
30import java.io.IOException;
31
32/**
33 * The class is used to hold an {@code android.hardware.Camera} instance.
34 *
35 * <p>The {@code open()} and {@code release()} calls are similar to the ones
36 * in {@code android.hardware.Camera}. The difference is if {@code keep()} is
37 * called before {@code release()}, CameraHolder will try to hold the {@code
38 * android.hardware.Camera} instance for a while, so if {@code open()} is
39 * called soon after, we can avoid the cost of {@code open()} in {@code
40 * android.hardware.Camera}.
41 *
42 * <p>This is used in switching between {@code Camera} and {@code VideoCamera}
43 * activities.
44 */
45public class CameraHolder {
46    private static final String TAG = "CameraHolder";
47    private android.hardware.Camera mCameraDevice;
48    private long mKeepBeforeTime = 0;  // Keep the Camera before this time.
49    private final Handler mHandler;
50    private int mUsers = 0;  // number of open() - number of release()
51    private int mNumberOfCameras;
52    private int mCameraId = -1;  // current camera id
53    private int mBackCameraId = -1, mFrontCameraId = -1;
54    private CameraInfo[] mInfo;
55
56    // We store the camera parameters when we actually open the device,
57    // so we can restore them in the subsequent open() requests by the user.
58    // This prevents the parameters set by the Camera activity used by
59    // the VideoCamera activity inadvertently.
60    private Parameters mParameters;
61
62    // Use a singleton.
63    private static CameraHolder sHolder;
64    public static synchronized CameraHolder instance() {
65        if (sHolder == null) {
66            sHolder = new CameraHolder();
67        }
68        return sHolder;
69    }
70
71    private static final int RELEASE_CAMERA = 1;
72    private class MyHandler extends Handler {
73        MyHandler(Looper looper) {
74            super(looper);
75        }
76
77        @Override
78        public void handleMessage(Message msg) {
79            switch(msg.what) {
80                case RELEASE_CAMERA:
81                    synchronized (CameraHolder.this) {
82                        // In 'CameraHolder.open', the 'RELEASE_CAMERA' message
83                        // will be removed if it is found in the queue. However,
84                        // there is a chance that this message has been handled
85                        // before being removed. So, we need to add a check
86                        // here:
87                        if (CameraHolder.this.mUsers == 0) releaseCamera();
88                    }
89                    break;
90            }
91        }
92    }
93
94    private CameraHolder() {
95        HandlerThread ht = new HandlerThread("CameraHolder");
96        ht.start();
97        mHandler = new MyHandler(ht.getLooper());
98        mNumberOfCameras = android.hardware.Camera.getNumberOfCameras();
99        mInfo = new CameraInfo[mNumberOfCameras];
100        for (int i = 0; i < mNumberOfCameras; i++) {
101            mInfo[i] = new CameraInfo();
102            android.hardware.Camera.getCameraInfo(i, mInfo[i]);
103            if (mBackCameraId == -1 && mInfo[i].facing == CameraInfo.CAMERA_FACING_BACK) {
104                mBackCameraId = i;
105            }
106            if (mFrontCameraId == -1 && mInfo[i].facing == CameraInfo.CAMERA_FACING_FRONT) {
107                mFrontCameraId = i;
108            }
109        }
110    }
111
112    public int getNumberOfCameras() {
113        return mNumberOfCameras;
114    }
115
116    public CameraInfo[] getCameraInfo() {
117        return mInfo;
118    }
119
120    public synchronized android.hardware.Camera open(int cameraId)
121            throws CameraHardwareException {
122        Assert(mUsers == 0);
123        if (mCameraDevice != null && mCameraId != cameraId) {
124            mCameraDevice.release();
125            mCameraDevice = null;
126            mCameraId = -1;
127        }
128        if (mCameraDevice == null) {
129            try {
130                Log.v(TAG, "open camera " + cameraId);
131                mCameraDevice = android.hardware.Camera.open(cameraId);
132                mCameraId = cameraId;
133            } catch (RuntimeException e) {
134                Log.e(TAG, "fail to connect Camera", e);
135                throw new CameraHardwareException(e);
136            }
137            mParameters = mCameraDevice.getParameters();
138        } else {
139            try {
140                mCameraDevice.reconnect();
141            } catch (IOException e) {
142                Log.e(TAG, "reconnect failed.");
143                throw new CameraHardwareException(e);
144            }
145            mCameraDevice.setParameters(mParameters);
146        }
147        ++mUsers;
148        mHandler.removeMessages(RELEASE_CAMERA);
149        mKeepBeforeTime = 0;
150        return mCameraDevice;
151    }
152
153    /**
154     * Tries to open the hardware camera. If the camera is being used or
155     * unavailable then return {@code null}.
156     */
157    public synchronized android.hardware.Camera tryOpen(int cameraId) {
158        try {
159            return mUsers == 0 ? open(cameraId) : null;
160        } catch (CameraHardwareException e) {
161            // In eng build, we throw the exception so that test tool
162            // can detect it and report it
163            if ("eng".equals(Build.TYPE)) {
164                throw new RuntimeException(e);
165            }
166            return null;
167        }
168    }
169
170    public synchronized void release() {
171        Assert(mUsers == 1);
172        --mUsers;
173        mCameraDevice.stopPreview();
174        releaseCamera();
175    }
176
177    private synchronized void releaseCamera() {
178        Assert(mUsers == 0);
179        Assert(mCameraDevice != null);
180        long now = System.currentTimeMillis();
181        if (now < mKeepBeforeTime) {
182            mHandler.sendEmptyMessageDelayed(RELEASE_CAMERA,
183                    mKeepBeforeTime - now);
184            return;
185        }
186        mCameraDevice.release();
187        mCameraDevice = null;
188        // We must set this to null because it has a reference to Camera.
189        // Camera has references to the listeners.
190        mParameters = null;
191        mCameraId = -1;
192    }
193
194    public synchronized void keep() {
195        // We allow (mUsers == 0) for the convenience of the calling activity.
196        // The activity may not have a chance to call open() before the user
197        // choose the menu item to switch to another activity.
198        Assert(mUsers == 1 || mUsers == 0);
199        // Keep the camera instance for 3 seconds.
200        mKeepBeforeTime = System.currentTimeMillis() + 3000;
201    }
202
203    public int getBackCameraId() {
204        return mBackCameraId;
205    }
206
207    public int getFrontCameraId() {
208        return mFrontCameraId;
209    }
210}
211