| /* |
| * Copyright (C) 2009 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.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.provider.MediaStore.Images; |
| import android.provider.MediaStore.Video; |
| import android.util.Log; |
| |
| import java.io.FileDescriptor; |
| import java.util.WeakHashMap; |
| |
| /** |
| * This class provides several utilities to cancel bitmap decoding. |
| * |
| * The function decodeFileDescriptor() is used to decode a bitmap. During |
| * decoding if another thread wants to cancel it, it calls the function |
| * cancelThreadDecoding() specifying the Thread which is in decoding. |
| * |
| * cancelThreadDecoding() is sticky until allowThreadDecoding() is called. |
| */ |
| public class BitmapManager { |
| private static final String TAG = "BitmapManager"; |
| private static enum State {CANCEL, ALLOW} |
| private static class ThreadStatus { |
| public State mState = State.ALLOW; |
| public BitmapFactory.Options mOptions; |
| public boolean mThumbRequesting; |
| @Override |
| public String toString() { |
| String s; |
| if (mState == State.CANCEL) { |
| s = "Cancel"; |
| } else if (mState == State.ALLOW) { |
| s = "Allow"; |
| } else { |
| s = "?"; |
| } |
| s = "thread state = " + s + ", options = " + mOptions; |
| return s; |
| } |
| } |
| |
| private final WeakHashMap<Thread, ThreadStatus> mThreadStatus = |
| new WeakHashMap<Thread, ThreadStatus>(); |
| |
| private static BitmapManager sManager = null; |
| |
| private BitmapManager() { |
| } |
| |
| /** |
| * Get thread status and create one if specified. |
| */ |
| private synchronized ThreadStatus getOrCreateThreadStatus(Thread t) { |
| ThreadStatus status = mThreadStatus.get(t); |
| if (status == null) { |
| status = new ThreadStatus(); |
| mThreadStatus.put(t, status); |
| } |
| return status; |
| } |
| |
| /** |
| * 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) { |
| getOrCreateThreadStatus(t).mOptions = options; |
| } |
| |
| synchronized void removeDecodingOptions(Thread t) { |
| ThreadStatus status = mThreadStatus.get(t); |
| status.mOptions = 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) { |
| // allow decoding by default |
| return true; |
| } |
| |
| boolean result = (status.mState != State.CANCEL); |
| return result; |
| } |
| |
| public synchronized void allowThreadDecoding(Thread t) { |
| getOrCreateThreadStatus(t).mState = State.ALLOW; |
| } |
| |
| public synchronized void cancelThreadDecoding(Thread t, ContentResolver cr) { |
| ThreadStatus status = getOrCreateThreadStatus(t); |
| status.mState = State.CANCEL; |
| if (status.mOptions != null) { |
| status.mOptions.requestCancelDecode(); |
| } |
| |
| // Wake up threads in waiting list |
| notifyAll(); |
| |
| // Since our cancel request can arrive MediaProvider earlier than getThumbnail request, |
| // we use mThumbRequesting flag to make sure our request does cancel the request. |
| try { |
| synchronized (status) { |
| while (status.mThumbRequesting) { |
| Images.Thumbnails.cancelThumbnailRequest(cr, -1, t.getId()); |
| Video.Thumbnails.cancelThumbnailRequest(cr, -1, t.getId()); |
| status.wait(200); |
| } |
| } |
| } catch (InterruptedException ex) { |
| // ignore it. |
| } |
| } |
| |
| public Bitmap getThumbnail(ContentResolver cr, long origId, int kind, |
| BitmapFactory.Options options, boolean isVideo) { |
| Thread t = Thread.currentThread(); |
| ThreadStatus status = getOrCreateThreadStatus(t); |
| |
| if (!canThreadDecoding(t)) { |
| Log.d(TAG, "Thread " + t + " is not allowed to decode."); |
| return null; |
| } |
| |
| try { |
| synchronized (status) { |
| status.mThumbRequesting = true; |
| } |
| if (isVideo) { |
| return Video.Thumbnails.getThumbnail(cr, origId, t.getId(), |
| kind, null); |
| } else { |
| return Images.Thumbnails.getThumbnail(cr, origId, t.getId(), |
| kind, null); |
| } |
| } finally { |
| synchronized (status) { |
| status.mThumbRequesting = false; |
| status.notifyAll(); |
| } |
| } |
| } |
| |
| public static synchronized BitmapManager instance() { |
| if (sManager == null) { |
| sManager = new BitmapManager(); |
| } |
| return sManager; |
| } |
| |
| /** |
| * The real place to delegate bitmap decoding to BitmapFactory. |
| */ |
| public Bitmap decodeFileDescriptor(FileDescriptor fd, |
| BitmapFactory.Options options) { |
| if (options.mCancel) { |
| return null; |
| } |
| |
| Thread thread = Thread.currentThread(); |
| if (!canThreadDecoding(thread)) { |
| Log.d(TAG, "Thread " + thread + " is not allowed to decode."); |
| return null; |
| } |
| |
| setDecodingOptions(thread, options); |
| Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, options); |
| |
| removeDecodingOptions(thread); |
| return b; |
| } |
| } |