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 
17 package com.android.camera.burst;
18 
19 import android.os.AsyncTask;
20 import android.text.TextUtils;
21 
22 import com.android.camera.debug.Log;
23 import com.android.camera.debug.Log.Tag;
24 import com.android.camera.session.StackSaver;
25 
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.concurrent.TimeUnit;
30 
31 class BurstResultsSaver {
32     private static final Tag TAG = new Tag("BurstResultsSaver");
33 
34     /**
35      * The format string of burst media item file name (without extension).
36      * <p/>
37      * An media item file name has the following format: "Burst_" + artifact
38      * type + "_" + index of artifact + "_" + index of media item + "_" +
39      * timestamp
40      */
41     private static final String MEDIA_ITEM_FILENAME_FORMAT_STRING = "Burst_%s_%d_%d_%d";
42 
43     /**
44      * Generates sequential timestamp with 1 second difference.
45      */
46     private static class SequentialTimestampGenerator {
47         private long mSeedTimestampMillis;
48 
49         /**
50          * New instance of generator.
51          *
52          * @param seedTimestampMillis the timestamp in milliseconds for
53          *            initializing the generator.
54          */
SequentialTimestampGenerator(long seedTimestampMillis)55         public SequentialTimestampGenerator(long seedTimestampMillis) {
56             mSeedTimestampMillis = seedTimestampMillis;
57         }
58 
59         /**
60          * Returns the next timestamp.
61          */
getNextTimestampMillis()62         public synchronized long getNextTimestampMillis() {
63             mSeedTimestampMillis += TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
64             return mSeedTimestampMillis;
65         }
66     }
67 
logArtifactCount(final Map<String, Integer> artifactTypeCount)68     public static void logArtifactCount(final Map<String, Integer> artifactTypeCount) {
69         final String prefix = "Finished burst. Creating ";
70         List<String> artifactDescription = new ArrayList<String>();
71         for (Map.Entry<String, Integer> entry : artifactTypeCount.entrySet()) {
72             artifactDescription.add(entry.getValue() + " " + entry.getKey());
73         }
74 
75         String message = prefix + TextUtils.join(" and ", artifactDescription) + ".";
76         Log.d(TAG, message);
77     }
78 
79     /**
80      * Saves the burst result and on completion re-enables the shutter button.
81      *
82      * @param burstResult the result of the burst.
83      */
saveBurstResultsInBackground(final BurstResult burstResult, final StackSaver stackSaver, final Runnable onCompletetionCallback)84     public static void saveBurstResultsInBackground(final BurstResult burstResult,
85             final StackSaver stackSaver, final Runnable onCompletetionCallback) {
86         Log.i(TAG, "Saving results of of the burst.");
87 
88         AsyncTask<Void, String, Void> saveTask =
89                 new AsyncTask<Void, String, Void>() {
90                     @Override
91                     protected Void doInBackground(Void... arg0) {
92                         // The timestamp with which a media item is saved
93                         // determines its place in the film strip. The newer
94                         // items appear first.
95                         // We save artifacts and their corresponding media
96                         // items sequentially in the desired order. The order
97                         // of the artifacts is implicitly defined by
98                         // burstResult.getTypes() and the media items inside the
99                         // artifacts are assumed to be sorted in ascending order
100                         // by timestamps.
101                         // We create a timestamp-generator that generates
102                         // timestamps in order and use it to save timestamps.
103                         SequentialTimestampGenerator timestampGen =
104                                 new SequentialTimestampGenerator(System.currentTimeMillis());
105                         for (String artifactType : burstResult.getTypes()) {
106                             publishProgress(artifactType);
107                             saveArtifacts(stackSaver, burstResult, artifactType,
108                                     timestampGen);
109                         }
110                         return null;
111                     }
112 
113                     @Override
114                     protected void onPostExecute(Void result) {
115                         onCompletetionCallback.run();
116                     }
117 
118                     @Override
119                     protected void onProgressUpdate(String... artifactTypes) {
120                         logProgressUpdate(artifactTypes, burstResult);
121                     }
122                 };
123         saveTask.execute(null, null, null);
124     }
125 
126     /**
127      * Save individual artifacts for bursts.
128      */
saveArtifacts(final StackSaver stackSaver, final BurstResult burstResult, final String artifactType, SequentialTimestampGenerator timestampGenerator)129     private static void saveArtifacts(final StackSaver stackSaver, final BurstResult burstResult,
130             final String artifactType, SequentialTimestampGenerator timestampGenerator) {
131         List<BurstArtifact> artifactList = burstResult.getArtifactsByType(artifactType);
132         for (int artifactIndex = 0; artifactIndex < artifactList.size(); artifactIndex++) {
133             List<BurstMediaItem> mediaItems = artifactList.get(artifactIndex).getMediaItems();
134             for (int index = 0; index < mediaItems.size(); index++) {
135                 saveBurstMediaItem(stackSaver, mediaItems.get(index),
136                         artifactType, artifactIndex + 1, index + 1, timestampGenerator);
137             }
138         }
139     }
140 
saveBurstMediaItem(StackSaver stackSaver, BurstMediaItem mediaItem, String artifactType, int artifactIndex, int index, SequentialTimestampGenerator timestampGenerator)141     private static void saveBurstMediaItem(StackSaver stackSaver,
142             BurstMediaItem mediaItem,
143             String artifactType,
144             int artifactIndex,
145             int index,
146             SequentialTimestampGenerator timestampGenerator) {
147         // Use ordered timestamp for saving the media item, this way media
148         // items appear to be in the correct order when user swipes to the
149         // film strip.
150         long timestamp = timestampGenerator.getNextTimestampMillis();
151         final String title = String.format(MEDIA_ITEM_FILENAME_FORMAT_STRING,
152                 artifactType, artifactIndex, index, mediaItem.getTimestamp());
153         String mimeType = mediaItem.getMimeType();
154 
155         stackSaver.saveStackedImage(mediaItem.getFilePath(),
156                 title,
157                 mediaItem.getWidth(),
158                 mediaItem.getHeight(),
159                 0, // Artifacts returned from burst have upright orientation.
160                 timestamp,
161                 mimeType);
162     }
163 
logProgressUpdate(String[] artifactTypes, BurstResult burstResult)164     private static void logProgressUpdate(String[] artifactTypes, BurstResult burstResult) {
165         for (String artifactType : artifactTypes) {
166             List<BurstArtifact> artifacts =
167                     burstResult.getArtifactsByType(artifactType);
168             if (!artifacts.isEmpty()) {
169                 Log.d(TAG, "Saving " + artifacts.size()
170                         + " " + artifactType + "s.");
171             }
172         }
173     }
174 
175 }
176