1/*
2 * Copyright (C) 2015 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.one.v2.photo;
18
19import static com.android.camera.one.v2.core.ResponseListeners.forFrameExposure;
20import static com.android.camera.one.v2.core.ResponseListeners.forPartialMetadata;
21
22import android.annotation.TargetApi;
23import android.hardware.camera2.CameraAccessException;
24import android.hardware.camera2.CaptureRequest;
25import android.os.Build;
26
27import com.android.camera.async.BufferQueue;
28import com.android.camera.async.Updatable;
29import com.android.camera.one.v2.autofocus.AETriggerResult;
30import com.android.camera.one.v2.autofocus.AFTriggerResult;
31import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionClosedException;
32import com.android.camera.one.v2.camera2proxy.ImageProxy;
33import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy;
34import com.android.camera.one.v2.core.FrameServer;
35import com.android.camera.one.v2.core.Request;
36import com.android.camera.one.v2.core.RequestBuilder;
37import com.android.camera.one.v2.core.RequestTemplate;
38import com.android.camera.one.v2.core.ResourceAcquisitionFailedException;
39import com.android.camera.one.v2.imagesaver.ImageSaver;
40import com.android.camera.one.v2.sharedimagereader.ManagedImageReader;
41import com.android.camera.one.v2.sharedimagereader.imagedistributor.ImageStream;
42import com.google.common.util.concurrent.ListenableFuture;
43
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.List;
47
48import javax.annotation.ParametersAreNonnullByDefault;
49
50/**
51 * Captures a burst after waiting for AF and AE convergence.
52 */
53@ParametersAreNonnullByDefault
54@TargetApi(Build.VERSION_CODES.LOLLIPOP)
55class ConvergedImageCaptureCommand implements ImageCaptureCommand {
56    private final ManagedImageReader mImageReader;
57    private final FrameServer mFrameServer;
58    private final RequestBuilder.Factory mScanRequestTemplate;
59    private final RequestBuilder.Factory mRepeatingRequestBuilder;
60    private final int mRepeatingRequestTemplate;
61    private final int mStillCaptureRequestTemplate;
62    private final List<RequestBuilder.Factory> mBurst;
63
64    private final boolean mWaitForAEConvergence;
65    private final boolean mWaitForAFConvergence;
66
67    /**
68     * Transforms a request template by resetting focus and exposure modes.
69     */
70    private static RequestBuilder.Factory resetFocusExposureModes(RequestBuilder.Factory template) {
71        RequestTemplate result = new RequestTemplate(template);
72        result.setParam(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
73        result.setParam(CaptureRequest.CONTROL_AF_MODE,
74                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
75        result.setParam(CaptureRequest.CONTROL_AF_TRIGGER,
76                CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
77        result.setParam(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
78                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
79        return result;
80    }
81
82    /**
83     * @param imageReader Creates the {@link ImageStream} used for capturing
84     *            images to be saved.
85     * @param frameServer Used for interacting with the camera device.
86     * @param repeatingRequestBuilder Creates request builders to use for
87     *            repeating requests sent during the scanning phase and after
88     *            capture is complete.
89     * @param repeatingRequestTemplate The template type to use for repeating
90     *            requests.
91     * @param burst Creates request builders to use for each image captured from
92     * @param waitForAEConvergence
93     * @param waitForAFConvergence
94     */
95    public ConvergedImageCaptureCommand(ManagedImageReader imageReader, FrameServer frameServer,
96            RequestBuilder.Factory repeatingRequestBuilder,
97            int repeatingRequestTemplate, int stillCaptureRequestTemplate,
98            List<RequestBuilder.Factory> burst, boolean waitForAEConvergence,
99            boolean waitForAFConvergence) {
100        mImageReader = imageReader;
101        mFrameServer = frameServer;
102        mRepeatingRequestBuilder = repeatingRequestBuilder;
103        mRepeatingRequestTemplate = repeatingRequestTemplate;
104        mStillCaptureRequestTemplate = stillCaptureRequestTemplate;
105        mBurst = burst;
106        mWaitForAEConvergence = waitForAEConvergence;
107        mWaitForAFConvergence = waitForAFConvergence;
108
109        mScanRequestTemplate = resetFocusExposureModes(repeatingRequestBuilder);
110    }
111
112    /**
113     * Sends a request to take a picture and blocks until it completes.
114     */
115    @Override
116    public void run(Updatable<Void> imageExposureUpdatable, ImageSaver imageSaver) throws
117            InterruptedException, CameraAccessException, CameraCaptureSessionClosedException,
118            ResourceAcquisitionFailedException {
119        try (FrameServer.Session session = mFrameServer.createExclusiveSession()) {
120            try (ImageStream imageStream = mImageReader.createPreallocatedStream(mBurst.size())) {
121                if (mWaitForAFConvergence) {
122                    waitForAFConvergence(session);
123                }
124                if (mWaitForAEConvergence) {
125                    waitForAEConvergence(session);
126                }
127                captureBurst(session, imageStream, imageExposureUpdatable, imageSaver);
128            } finally {
129                // Always reset the repeating stream to ensure AF/AE are not
130                // locked when this exits.
131                // Note that this may still throw if the camera or session is
132                // closed.
133                resetRepeating(session);
134            }
135        } finally {
136            imageSaver.close();
137        }
138    }
139
140    private void waitForAFConvergence(FrameServer.Session session) throws CameraAccessException,
141            InterruptedException, ResourceAcquisitionFailedException,
142            CameraCaptureSessionClosedException {
143        AFTriggerResult afStateMachine = new AFTriggerResult();
144
145        RequestBuilder triggerBuilder = mScanRequestTemplate.create(mRepeatingRequestTemplate);
146        triggerBuilder.setParam(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest
147                .CONTROL_AF_TRIGGER_START);
148        triggerBuilder.addResponseListener(forPartialMetadata(afStateMachine));
149
150        RequestBuilder idleBuilder = mScanRequestTemplate.create(mRepeatingRequestTemplate);
151        idleBuilder.addResponseListener(forPartialMetadata(afStateMachine));
152
153        session.submitRequest(Arrays.asList(idleBuilder.build()),
154                FrameServer.RequestType.REPEATING);
155
156        session.submitRequest(Arrays.asList(triggerBuilder.build()),
157                FrameServer.RequestType.NON_REPEATING);
158
159        // Block until the AF trigger is complete
160        afStateMachine.get();
161    }
162
163    private void waitForAEConvergence(FrameServer.Session session) throws CameraAccessException,
164            InterruptedException, ResourceAcquisitionFailedException,
165            CameraCaptureSessionClosedException {
166        AETriggerResult aeStateMachine = new AETriggerResult();
167
168        RequestBuilder triggerBuilder = mScanRequestTemplate.create(mRepeatingRequestTemplate);
169        triggerBuilder.setParam(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
170                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
171        triggerBuilder.addResponseListener(forPartialMetadata(aeStateMachine));
172
173        RequestBuilder idleBuilder = mScanRequestTemplate.create(mRepeatingRequestTemplate);
174        idleBuilder.addResponseListener(forPartialMetadata(aeStateMachine));
175
176        session.submitRequest(Arrays.asList(idleBuilder.build()),
177                FrameServer.RequestType.REPEATING);
178
179        session.submitRequest(Arrays.asList(triggerBuilder.build()),
180                FrameServer.RequestType.NON_REPEATING);
181
182        // Wait until the ae state converges to a result.
183        aeStateMachine.get();
184    }
185
186    private void captureBurst(FrameServer.Session session, ImageStream imageStream, Updatable<Void>
187            imageExposureUpdatable, ImageSaver imageSaver) throws CameraAccessException,
188            InterruptedException, ResourceAcquisitionFailedException,
189            CameraCaptureSessionClosedException {
190        List<Request> burstRequest = new ArrayList<>(mBurst.size());
191        List<ListenableFuture<TotalCaptureResultProxy>> metadata = new ArrayList<>(mBurst.size());
192        boolean first = true;
193        for (RequestBuilder.Factory builderTemplate : mBurst) {
194            RequestBuilder builder = builderTemplate.create(mStillCaptureRequestTemplate);
195
196            builder.setParam(CaptureRequest.CONTROL_AF_MODE, CaptureRequest
197                    .CONTROL_AF_MODE_CONTINUOUS_PICTURE);
198            builder.setParam(CaptureRequest.CONTROL_CAPTURE_INTENT,
199                    CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
200
201            if (first) {
202                first = false;
203                builder.addResponseListener(forFrameExposure(imageExposureUpdatable));
204            }
205
206            MetadataFuture metadataFuture = new MetadataFuture();
207            builder.addResponseListener(metadataFuture);
208            metadata.add(metadataFuture.getMetadata());
209
210            builder.addStream(imageStream);
211
212            burstRequest.add(builder.build());
213        }
214
215        session.submitRequest(burstRequest, FrameServer.RequestType.NON_REPEATING);
216
217        for (int i = 0; i < mBurst.size(); i++) {
218            try {
219                ImageProxy image = imageStream.getNext();
220                imageSaver.addFullSizeImage(image, metadata.get(i));
221            } catch (BufferQueue.BufferQueueClosedException e) {
222                // No more images will be available, so just quit.
223                return;
224            }
225        }
226    }
227
228    private void resetRepeating(FrameServer.Session session) throws InterruptedException,
229            CameraCaptureSessionClosedException, CameraAccessException,
230            ResourceAcquisitionFailedException {
231        RequestBuilder repeatingBuilder = mRepeatingRequestBuilder.create
232                (mRepeatingRequestTemplate);
233        session.submitRequest(Arrays.asList(repeatingBuilder.build()),
234                FrameServer.RequestType.REPEATING);
235
236        RequestBuilder triggerCancelBuilder = mRepeatingRequestBuilder
237                .create(mRepeatingRequestTemplate);
238        triggerCancelBuilder.setParam(CaptureRequest.CONTROL_AF_TRIGGER,
239                CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
240        session.submitRequest(Arrays.asList(triggerCancelBuilder.build()),
241                FrameServer.RequestType.NON_REPEATING);
242
243        // Some devices (e.g. N6) implicitly lock AE after sending an
244        // AE_PRECAPTURE trigger. (see bug: 19265647)
245        // The implicit lock is released when a request with
246        // INTENT_STILL_CAPTURE is taken.
247
248        // However, if we never get to that point (because the command was
249        // interrupted before the request for a photo was sent), then we must be
250        // sure to cancel this implicit AE lock to resume normal AE behavior.
251        // Sending a request for an explicit AE lock (followed, implicitly, by a
252        // request from the current repeating request, which has AE lock off)
253        // fixes the issue and results in normal AE behavior.
254        RequestBuilder hackAETriggerCancelBuilder = mRepeatingRequestBuilder.create
255                (mRepeatingRequestTemplate);
256        hackAETriggerCancelBuilder.setParam(CaptureRequest.CONTROL_AE_LOCK, true);
257
258        session.submitRequest(Arrays.asList(hackAETriggerCancelBuilder.build()),
259                FrameServer.RequestType.NON_REPEATING);
260    }
261}
262