blob: 4d17ec332f202192574222ae3b320e43e49b08b2 [file] [log] [blame]
/*
* Copyright (C) 2007 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.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import java.io.FileDescriptor;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
/**
* This class provides several utilities to cancel/prevent potential heavy
* loading of CPU in bitmap decoding.
*
* To use this class, be sure to call setAllowDecoding(true) in onResume
* and turn it off by cancelAllDecoding() in onPause, and replace all
* BitmapFactory.decodeFileDescriptor with BitmapManager.decodeFileDescriptor.
*
* Individual thread can be controlled by {allow/cancel}ThreadDecoding.
*/
public class BitmapManager {
private static final String TAG = "BitmapManager";
private static enum State {RUNNING, CANCEL, WAIT}
private static class ThreadStatus {
public State state = State.WAIT;
public BitmapFactory.Options options;
@Override
public String toString() {
String s;
if (state == State.RUNNING) {
s = "Running";
} else if (state == State.CANCEL) {
s = "Cancel";
} else {
s = "Wait";
}
s = "thread state = " + s + ", options = " + options;
return s;
}
}
private final WeakHashMap<Thread, ThreadStatus> mThreadStatus =
new WeakHashMap<Thread, ThreadStatus>();
private boolean mAllowDecoding = false;
private boolean mLocked = false;
private boolean mCheckResourceLock = true;
private static BitmapManager sManager;
public static BitmapManager instance() {
if (sManager == null) {
sManager = new BitmapManager();
}
return sManager;
}
private BitmapManager() {
}
public synchronized void setCheckResourceLock(boolean b) {
mCheckResourceLock = b;
}
/**
* Get thread status and create one if specified.
*/
private synchronized ThreadStatus getThreadStatus(Thread t,
boolean createNew) {
ThreadStatus status = mThreadStatus.get(t);
if (status == null && createNew) {
status = new ThreadStatus();
mThreadStatus.put(t, status);
}
return status;
}
/**
* Since Bitmap related operations are resource-intensive (CPU/Memory),
* we can't affort too many threads running at the same time, so only
* thread that acquire the lock can proceed, others have to wait in the
* queue. It can also be canceled and removed by other thread to avoid
* starvation.
*/
public synchronized boolean acquireResourceLock() {
Thread t = Thread.currentThread();
ThreadStatus status = getThreadStatus(t, true);
while (mLocked) {
try {
wait();
// remove canceled thread
if (status.state == State.CANCEL) {
return false;
}
} catch (InterruptedException ex) {
Log.e(TAG, ex.toString());
}
}
status.state = State.RUNNING;
mLocked = true;
return true;
}
/**
* Make sure "acquire/release" are pairing correctly
*/
public synchronized void releaseResourceLock() {
Thread t = Thread.currentThread();
mLocked = false;
notifyAll();
}
/**
* The following three methods are used to keep track of
* BitmapFaction.Options used for decoding and cancelling.
*/
private synchronized void setDecodingOptions(Thread t,
BitmapFactory.Options options) {
getThreadStatus(t, true).options = options;
}
synchronized BitmapFactory.Options getDecodingOptions(Thread t) {
ThreadStatus status = mThreadStatus.get(t);
return status != null ? status.options : null;
}
synchronized void removeDecodingOptions(Thread t) {
ThreadStatus status = mThreadStatus.get(t);
status.options = null;
}
/**
* The following three methods are used to keep track of which thread
* is being disabled for bitmap decoding.
*/
public synchronized boolean canThreadDecoding(Thread t) {
ThreadStatus status = mThreadStatus.get(t);
if (status == null) {
Log.v(TAG, "can't find status for thread " + t);
return false;
}
boolean result = (status.state == State.RUNNING) ||
(status.state != State.CANCEL && !mCheckResourceLock);
return result;
}
public synchronized void allowThreadDecoding(Thread t) {
getThreadStatus(t, true).state = State.WAIT;
}
public synchronized void cancelThreadDecoding(Thread t) {
ThreadStatus status = getThreadStatus(t, true);
status.state = State.CANCEL;
if (status.options != null) {
status.options.requestCancelDecode();
}
// Wake up threads in waiting list
notifyAll();
}
/**
* The following four methods are used to control global switch of
* bitmap decoding.
*/
public synchronized void cancelAllDecoding() {
allowAllDecoding(false);
for (ThreadStatus status : mThreadStatus.values()) {
status.state = State.CANCEL;
if (status.options != null) {
status.options.requestCancelDecode();
}
}
// Wake up all threads in the waiting list
notifyAll();
}
public synchronized void allowAllDecoding() {
allowAllDecoding(true);
}
public synchronized void allowAllDecoding(boolean reset) {
mAllowDecoding = true;
if (reset) {
mThreadStatus.clear();
}
}
public synchronized boolean canDecode() {
return mAllowDecoding;
}
/**
* A debugging routine.
*/
public synchronized void dump() {
Iterator<Map.Entry<Thread, ThreadStatus>> i =
mThreadStatus.entrySet().iterator();
while (i.hasNext()) {
Map.Entry<Thread, ThreadStatus> entry = i.next();
Log.v(TAG, "[Dump] Thread " + entry.getKey() + " ("
+ entry.getKey().getId()
+ ")'s status is " + entry.getValue());
}
}
/**
* The real place to delegate bitmap decoding to BitmapFactory.
*/
public Bitmap decodeFileDescriptor(FileDescriptor fd,
BitmapFactory.Options options) {
// Does the global switch turn on?
if (!canDecode() || options.mCancel) {
return null;
}
// Can current thread decode?
Thread thread = Thread.currentThread();
if (!canThreadDecoding(thread)) {
return null;
}
setDecodingOptions(thread, options);
Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, options);
// In case legacy code cancel it in traditional way
if (options.mCancel) {
cancelThreadDecoding(thread);
}
removeDecodingOptions(thread);
return b;
}
}