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 
17 package com.android.camera;
18 
19 import android.content.ContentResolver;
20 import android.location.Location;
21 import android.net.Uri;
22 import android.util.Log;
23 
24 import 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.
41 class 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 {
onMediaSaved(Uri uri)50         public void onMediaSaved(Uri uri);
51     }
52 
MediaSaver(ContentResolver resolver)53     public MediaSaver(ContentResolver resolver) {
54         mContentResolver = resolver;
55         mQueue = new ArrayList<SaveRequest>();
56         start();
57     }
58 
59     // Runs in main thread
queueFull()60     public synchronized boolean queueFull() {
61         return (mQueue.size() >= SAVE_QUEUE_LIMIT);
62     }
63 
64     // Runs in main thread
addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, OnMediaSavedListener l)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
run()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
finish()124     public void finish() {
125         synchronized (this) {
126             mStop = true;
127             notifyAll();
128         }
129     }
130 
131     // Runs in saver thread
storeImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation)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