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 { 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