1/*
2 * Copyright (C) 2013 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.ex.camera2.portability;
18
19import android.content.Context;
20import android.os.Build;
21
22import com.android.ex.camera2.portability.debug.Log;
23import com.android.ex.camera2.portability.util.SystemProperties;
24
25/**
26 * A factory class for {@link CameraAgent}.
27 *
28 * <p>The choice of framework API to use can be made automatically based on the
29 * system API level, explicitly forced by the client app, or overridden entirely
30 * by setting the system property com.camera2.portability.fwk_api to 1 or 2.</p>
31 */
32public class CameraAgentFactory {
33    private static final Log.Tag TAG = new Log.Tag("CamAgntFact");
34
35    /** Android release replacing the Camera class with the camera2 package. */
36    private static final int FIRST_SDK_WITH_API_2 = 21;
37
38    // The debugging override, which overrides *all* API level selections if set
39    // to API_LEVEL_OVERRIDE_API{1,2}; otherwise, this has no effect. Note that
40    // we check this once when the library is first loaded so that #recycle()
41    // doesn't try to clean up the wrong type of CameraAgent.
42    private static final String API_LEVEL_OVERRIDE_KEY = "camera2.portability.force_api";
43    private static final String API_LEVEL_OVERRIDE_DEFAULT = "0";
44    private static final String API_LEVEL_OVERRIDE_API1 = "1";
45    private static final String API_LEVEL_OVERRIDE_API2 = "2";
46    private static final String API_LEVEL_OVERRIDE_VALUE =
47            SystemProperties.get(API_LEVEL_OVERRIDE_KEY, API_LEVEL_OVERRIDE_DEFAULT);
48
49    private static CameraAgent sAndroidCameraAgent;
50    private static CameraAgent sAndroidCamera2Agent;
51    private static int sAndroidCameraAgentClientCount;
52    private static int sAndroidCamera2AgentClientCount;
53
54    /**
55     * Used to indicate which camera framework should be used.
56     */
57    public static enum CameraApi {
58        /** Automatically select based on the device's SDK level. */
59        AUTO,
60
61        /** Use the {@link android.hardware.Camera} class. */
62        API_1,
63
64        /** Use the {@link android.hardware.camera2} package. */
65        API_2
66    };
67
68    private static CameraApi highestSupportedApi() {
69        // TODO: Check SDK_INT instead of RELEASE before L launch
70        if (Build.VERSION.SDK_INT >= FIRST_SDK_WITH_API_2 || Build.VERSION.CODENAME.equals("L")) {
71            return CameraApi.API_2;
72        } else {
73            return CameraApi.API_1;
74        }
75    }
76
77    private static CameraApi validateApiChoice(CameraApi choice) {
78        if (API_LEVEL_OVERRIDE_VALUE.equals(API_LEVEL_OVERRIDE_API1)) {
79            Log.d(TAG, "API level overridden by system property: forced to 1");
80            return CameraApi.API_1;
81        } else if (API_LEVEL_OVERRIDE_VALUE.equals(API_LEVEL_OVERRIDE_API2)) {
82            Log.d(TAG, "API level overridden by system property: forced to 2");
83            return CameraApi.API_2;
84        }
85
86        if (choice == null) {
87            Log.w(TAG, "null API level request, so assuming AUTO");
88            choice = CameraApi.AUTO;
89        }
90        if (choice == CameraApi.AUTO) {
91            choice = highestSupportedApi();
92        }
93
94        return choice;
95    }
96
97    /**
98     * Returns the android camera implementation of
99     * {@link com.android.camera.cameradevice.CameraAgent}.
100     *
101     * <p>To clean up the resources allocated by this call, be sure to invoke
102     * {@link #recycle(boolean)} with the same {@code api} value provided
103     * here.</p>
104     *
105     * @param context The application context.
106     * @param api Which camera framework to use.
107     * @return The {@link CameraAgent} to control the camera device.
108     *
109     * @throws UnsupportedOperationException If {@code CameraApi.API_2} was
110     *                                       requested on an unsupported device.
111     */
112    public static synchronized CameraAgent getAndroidCameraAgent(Context context, CameraApi api) {
113        api = validateApiChoice(api);
114
115        if (api == CameraApi.API_1) {
116            if (sAndroidCameraAgent == null) {
117                sAndroidCameraAgent = new AndroidCameraAgentImpl();
118                sAndroidCameraAgentClientCount = 1;
119            } else {
120                ++sAndroidCameraAgentClientCount;
121            }
122            return sAndroidCameraAgent;
123        } else { // API_2
124            if (highestSupportedApi() == CameraApi.API_1) {
125                throw new UnsupportedOperationException("Camera API_2 unavailable on this device");
126            }
127
128            if (sAndroidCamera2Agent == null) {
129                sAndroidCamera2Agent = new AndroidCamera2AgentImpl(context);
130                sAndroidCamera2AgentClientCount = 1;
131            } else {
132                ++sAndroidCamera2AgentClientCount;
133            }
134            return sAndroidCamera2Agent;
135        }
136    }
137
138    /**
139     * Recycles the resources. Always call this method when the activity is
140     * stopped.
141     *
142     * @param api Which camera framework handle to recycle.
143     *
144     * @throws UnsupportedOperationException If {@code CameraApi.API_2} was
145     *                                       requested on an unsupported device.
146     */
147    public static synchronized void recycle(CameraApi api) {
148        api = validateApiChoice(api);
149
150        if (api == CameraApi.API_1) {
151            if (--sAndroidCameraAgentClientCount == 0 && sAndroidCameraAgent != null) {
152                sAndroidCameraAgent.recycle();
153                sAndroidCameraAgent = null;
154            }
155        } else { // API_2
156            if (highestSupportedApi() == CameraApi.API_1) {
157                throw new UnsupportedOperationException("Camera API_2 unavailable on this device");
158            }
159
160            if (--sAndroidCamera2AgentClientCount == 0 && sAndroidCamera2Agent != null) {
161                sAndroidCamera2Agent.recycle();
162                sAndroidCamera2Agent = null;
163            }
164        }
165    }
166}
167