1 /*
2  * Copyright (C) 2014 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 
17 package com.android.camera.one.v2.imagesaver;
18 
19 import com.android.camera.one.v2.camera2proxy.ImageProxy;
20 import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy;
21 import com.google.common.base.Optional;
22 import com.google.common.util.concurrent.ListenableFuture;
23 
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 
29 import javax.annotation.Nullable;
30 import javax.annotation.ParametersAreNonnullByDefault;
31 
32 /**
33  * Saves the last image in a burst.
34  */
35 @ParametersAreNonnullByDefault
36 public class MostRecentImageSaver implements ImageSaver {
37     private final SingleImageSaver mSingleImageSaver;
38     private final Map<Long, ImageProxy> mThumbnails;
39     private final Map<Long, MetadataImage> mFullSizeImages;
40 
MostRecentImageSaver(SingleImageSaver singleImageSaver)41     public MostRecentImageSaver(SingleImageSaver singleImageSaver) {
42         mSingleImageSaver = singleImageSaver;
43         mThumbnails = new HashMap<>();
44         mFullSizeImages = new HashMap<>();
45     }
46 
47     @Override
addThumbnail(ImageProxy imageProxy)48     public void addThumbnail(ImageProxy imageProxy) {
49         mThumbnails.put(imageProxy.getTimestamp(), imageProxy);
50         closeOlderImages();
51     }
52 
53     @Override
addFullSizeImage(ImageProxy imageProxy, ListenableFuture<TotalCaptureResultProxy> metadata)54     public void addFullSizeImage(ImageProxy imageProxy,
55             ListenableFuture<TotalCaptureResultProxy> metadata) {
56         mFullSizeImages.put(imageProxy.getTimestamp(), new MetadataImage(imageProxy, metadata));
57         closeOlderImages();
58     }
59 
60     @Override
close()61     public void close() {
62         try {
63             MetadataImage fullSize = getLastImage();
64             if (fullSize != null) {
65                 // Pop the image out of the map so that closeAllImages() does
66                 // not close it.
67                 mFullSizeImages.remove(fullSize.getTimestamp());
68             } else {
69                 return;
70             }
71 
72             ImageProxy thumbnail = getThumbnail(fullSize.getTimestamp());
73             if (thumbnail != null) {
74                 // Pop the image out of the map so that closeAllImages() does
75                 // not close it.
76                 mThumbnails.remove(thumbnail.getTimestamp());
77             }
78 
79             mSingleImageSaver.saveAndCloseImage(fullSize, Optional.fromNullable(thumbnail),
80                     fullSize.getMetadata());
81         } finally {
82             closeAllImages();
83         }
84     }
85 
closeAllImages()86     private void closeAllImages() {
87         for (ImageProxy image : mThumbnails.values()) {
88             image.close();
89         }
90 
91         for (ImageProxy image : mFullSizeImages.values()) {
92             image.close();
93         }
94     }
95 
closeOlderImages(long threshold, Map<Long, ? extends ImageProxy> imageMap)96     private void closeOlderImages(long threshold, Map<Long, ? extends ImageProxy> imageMap) {
97         List<Long> toRemove = new ArrayList<>();
98         for (long imageTimestamp : imageMap.keySet()) {
99             if (imageTimestamp < threshold) {
100                 imageMap.get(imageTimestamp).close();
101                 toRemove.add(imageTimestamp);
102             }
103         }
104         for (Long timestamp : toRemove) {
105             imageMap.remove(timestamp);
106         }
107     }
108 
closeOlderImages()109     private void closeOlderImages() {
110         Optional<Long> timestampThreshold = getMostRecentFullSizeImageTimestamp();
111         if (timestampThreshold.isPresent()) {
112             closeOlderImages(timestampThreshold.get(), mFullSizeImages);
113             closeOlderImages(timestampThreshold.get(), mThumbnails);
114         }
115     }
116 
getMostRecentFullSizeImageTimestamp()117     private Optional<Long> getMostRecentFullSizeImageTimestamp() {
118         if (mFullSizeImages.isEmpty()) {
119             return Optional.absent();
120         }
121         boolean pairFound = false;
122         long oldestTimestamp = 0;
123         for (ImageProxy image : mFullSizeImages.values()) {
124             long timestamp = image.getTimestamp();
125             if (!pairFound || timestamp > oldestTimestamp) {
126                 oldestTimestamp = timestamp;
127                 pairFound = true;
128             }
129         }
130         if (!pairFound) {
131             return Optional.absent();
132         } else {
133             return Optional.of(oldestTimestamp);
134         }
135     }
136 
137     @Nullable
getLastImage()138     private MetadataImage getLastImage() {
139         if (mFullSizeImages.isEmpty()) {
140             return null;
141         }
142         MetadataImage lastImage = null;
143         for (MetadataImage image : mFullSizeImages.values()) {
144             if (lastImage == null || image.getTimestamp() > lastImage.getTimestamp()) {
145                 lastImage = image;
146             }
147         }
148         return lastImage;
149     }
150 
151     @Nullable
getThumbnail(long timestamp)152     private ImageProxy getThumbnail(long timestamp) {
153         return mThumbnails.get(timestamp);
154     }
155 }
156