summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/MediaSaver.java
blob: a3d582e1c964f428925f75973d40b00aa6adb062 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.camera;

import android.content.ContentResolver;
import android.location.Location;
import android.net.Uri;
import android.util.Log;

import java.util.ArrayList;

// We use a queue to store the SaveRequests that have not been completed
// yet. The main thread puts the request into the queue. The saver thread
// gets it from the queue, does the work, and removes it from the queue.
//
// The main thread needs to wait for the saver thread to finish all the work
// in the queue, when the activity's onPause() is called, we need to finish
// all the work, so other programs (like Gallery) can see all the images.
//
// If the queue becomes too long, adding a new request will block the main
// thread until the queue length drops below the threshold (QUEUE_LIMIT).
// If we don't do this, we may face several problems: (1) We may OOM
// because we are holding all the jpeg data in memory. (2) We may ANR
// when we need to wait for saver thread finishing all the work (in
// onPause() or gotoGallery()) because the time to finishing a long queue
// of work may be too long.
class MediaSaver extends Thread {
    private static final int SAVE_QUEUE_LIMIT = 3;
    private static final String TAG = "MediaSaver";

    private ArrayList<SaveRequest> mQueue;
    private boolean mStop;
    private ContentResolver mContentResolver;

    public interface OnMediaSavedListener {
        public void onMediaSaved(Uri uri);
    }

    public MediaSaver(ContentResolver resolver) {
        mContentResolver = resolver;
        mQueue = new ArrayList<SaveRequest>();
        start();
    }

    // Runs in main thread
    public synchronized boolean queueFull() {
        return (mQueue.size() >= SAVE_QUEUE_LIMIT);
    }

    // Runs in main thread
    public void addImage(final byte[] data, String title, long date, Location loc,
                         int width, int height, int orientation, OnMediaSavedListener l) {
        SaveRequest r = new SaveRequest();
        r.data = data;
        r.date = date;
        r.title = title;
        r.loc = (loc == null) ? null : new Location(loc);  // make a copy
        r.width = width;
        r.height = height;
        r.orientation = orientation;
        r.listener = l;
        synchronized (this) {
            while (mQueue.size() >= SAVE_QUEUE_LIMIT) {
                try {
                    wait();
                } catch (InterruptedException ex) {
                    // ignore.
                }
            }
            mQueue.add(r);
            notifyAll();  // Tell saver thread there is new work to do.
        }
    }

    // Runs in saver thread
    @Override
    public void run() {
        while (true) {
            SaveRequest r;
            synchronized (this) {
                if (mQueue.isEmpty()) {
                    notifyAll();  // notify main thread in waitDone

                    // Note that we can only stop after we saved all images
                    // in the queue.
                    if (mStop) break;

                    try {
                        wait();
                    } catch (InterruptedException ex) {
                        // ignore.
                    }
                    continue;
                }
                if (mStop) break;
                r = mQueue.remove(0);
                notifyAll();  // the main thread may wait in addImage
            }
            Uri uri = storeImage(r.data, r.title, r.date, r.loc, r.width, r.height,
                    r.orientation);
            r.listener.onMediaSaved(uri);
        }
        if (!mQueue.isEmpty()) {
            Log.e(TAG, "Media saver thread stopped with " + mQueue.size() + " images unsaved");
            mQueue.clear();
        }
    }

    // Runs in main thread
    public void finish() {
        synchronized (this) {
            mStop = true;
            notifyAll();
        }
    }

    // Runs in saver thread
    private Uri storeImage(final byte[] data, String title, long date,
                           Location loc, int width, int height, int orientation) {
        Uri uri = Storage.addImage(mContentResolver, title, date, loc,
                                   orientation, data, width, height);
        return uri;
    }

    // Each SaveRequest remembers the data needed to save an image.
    private static class SaveRequest {
        byte[] data;
        String title;
        long date;
        Location loc;
        int width, height;
        int orientation;
        OnMediaSavedListener listener;
    }
}