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.burst;
18
19import android.os.AsyncTask;
20import android.text.TextUtils;
21
22import com.android.camera.debug.Log;
23import com.android.camera.debug.Log.Tag;
24import com.android.camera.session.StackSaver;
25
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Map;
29import java.util.concurrent.TimeUnit;
30
31class 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         */
55        public SequentialTimestampGenerator(long seedTimestampMillis) {
56            mSeedTimestampMillis = seedTimestampMillis;
57        }
58
59        /**
60         * Returns the next timestamp.
61         */
62        public synchronized long getNextTimestampMillis() {
63            mSeedTimestampMillis += TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
64            return mSeedTimestampMillis;
65        }
66    }
67
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     */
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     */
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
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
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