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.camera;
18
19import android.content.ContentResolver;
20import android.location.Location;
21import android.net.Uri;
22import android.util.Log;
23
24import java.util.ArrayList;
25
26// We use a queue to store the SaveRequests that have not been completed
27// yet. The main thread puts the request into the queue. The saver thread
28// gets it from the queue, does the work, and removes it from the queue.
29//
30// The main thread needs to wait for the saver thread to finish all the work
31// in the queue, when the activity's onPause() is called, we need to finish
32// all the work, so other programs (like Gallery) can see all the images.
33//
34// If the queue becomes too long, adding a new request will block the main
35// thread until the queue length drops below the threshold (QUEUE_LIMIT).
36// If we don't do this, we may face several problems: (1) We may OOM
37// because we are holding all the jpeg data in memory. (2) We may ANR
38// when we need to wait for saver thread finishing all the work (in
39// onPause() or gotoGallery()) because the time to finishing a long queue
40// of work may be too long.
41class MediaSaver extends Thread {
42    private static final int SAVE_QUEUE_LIMIT = 3;
43    private static final String TAG = "MediaSaver";
44
45    private ArrayList<SaveRequest> mQueue;
46    private boolean mStop;
47    private ContentResolver mContentResolver;
48
49    public interface OnMediaSavedListener {
50        public void onMediaSaved(Uri uri);
51    }
52
53    public MediaSaver(ContentResolver resolver) {
54        mContentResolver = resolver;
55        mQueue = new ArrayList<SaveRequest>();
56        start();
57    }
58
59    // Runs in main thread
60    public synchronized boolean queueFull() {
61        return (mQueue.size() >= SAVE_QUEUE_LIMIT);
62    }
63
64    // Runs in main thread
65    public void addImage(final byte[] data, String title, long date, Location loc,
66                         int width, int height, int orientation, OnMediaSavedListener l) {
67        SaveRequest r = new SaveRequest();
68        r.data = data;
69        r.date = date;
70        r.title = title;
71        r.loc = (loc == null) ? null : new Location(loc);  // make a copy
72        r.width = width;
73        r.height = height;
74        r.orientation = orientation;
75        r.listener = l;
76        synchronized (this) {
77            while (mQueue.size() >= SAVE_QUEUE_LIMIT) {
78                try {
79                    wait();
80                } catch (InterruptedException ex) {
81                    // ignore.
82                }
83            }
84            mQueue.add(r);
85            notifyAll();  // Tell saver thread there is new work to do.
86        }
87    }
88
89    // Runs in saver thread
90    @Override
91    public void run() {
92        while (true) {
93            SaveRequest r;
94            synchronized (this) {
95                if (mQueue.isEmpty()) {
96                    notifyAll();  // notify main thread in waitDone
97
98                    // Note that we can only stop after we saved all images
99                    // in the queue.
100                    if (mStop) break;
101
102                    try {
103                        wait();
104                    } catch (InterruptedException ex) {
105                        // ignore.
106                    }
107                    continue;
108                }
109                if (mStop) break;
110                r = mQueue.remove(0);
111                notifyAll();  // the main thread may wait in addImage
112            }
113            Uri uri = storeImage(r.data, r.title, r.date, r.loc, r.width, r.height,
114                    r.orientation);
115            r.listener.onMediaSaved(uri);
116        }
117        if (!mQueue.isEmpty()) {
118            Log.e(TAG, "Media saver thread stopped with " + mQueue.size() + " images unsaved");
119            mQueue.clear();
120        }
121    }
122
123    // Runs in main thread
124    public void finish() {
125        synchronized (this) {
126            mStop = true;
127            notifyAll();
128        }
129    }
130
131    // Runs in saver thread
132    private Uri storeImage(final byte[] data, String title, long date,
133                           Location loc, int width, int height, int orientation) {
134        Uri uri = Storage.addImage(mContentResolver, title, date, loc,
135                                   orientation, data, width, height);
136        return uri;
137    }
138
139    // Each SaveRequest remembers the data needed to save an image.
140    private static class SaveRequest {
141        byte[] data;
142        String title;
143        long date;
144        Location loc;
145        int width, height;
146        int orientation;
147        OnMediaSavedListener listener;
148    }
149}
150