/* * 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 mQueue; private boolean mStop; private ContentResolver mContentResolver; public interface OnMediaSavedListener { public void onMediaSaved(Uri uri); } public MediaSaver(ContentResolver resolver) { mContentResolver = resolver; mQueue = new ArrayList(); 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; } }