1/* 2 * Copyright (C) 2009 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 17package com.android.camera; 18 19import android.content.ContentResolver; 20import android.graphics.Bitmap; 21import android.graphics.BitmapFactory; 22import android.provider.MediaStore.Images; 23import android.provider.MediaStore.Video; 24import android.util.Log; 25 26import java.io.FileDescriptor; 27import java.util.WeakHashMap; 28 29/** 30 * This class provides several utilities to cancel bitmap decoding. 31 * 32 * The function decodeFileDescriptor() is used to decode a bitmap. During 33 * decoding if another thread wants to cancel it, it calls the function 34 * cancelThreadDecoding() specifying the Thread which is in decoding. 35 * 36 * cancelThreadDecoding() is sticky until allowThreadDecoding() is called. 37 */ 38public class BitmapManager { 39 private static final String TAG = "BitmapManager"; 40 private static enum State {CANCEL, ALLOW} 41 private static class ThreadStatus { 42 public State mState = State.ALLOW; 43 public BitmapFactory.Options mOptions; 44 public boolean mThumbRequesting; 45 @Override 46 public String toString() { 47 String s; 48 if (mState == State.CANCEL) { 49 s = "Cancel"; 50 } else if (mState == State.ALLOW) { 51 s = "Allow"; 52 } else { 53 s = "?"; 54 } 55 s = "thread state = " + s + ", options = " + mOptions; 56 return s; 57 } 58 } 59 60 private final WeakHashMap<Thread, ThreadStatus> mThreadStatus = 61 new WeakHashMap<Thread, ThreadStatus>(); 62 63 private static BitmapManager sManager = null; 64 65 private BitmapManager() { 66 } 67 68 /** 69 * Get thread status and create one if specified. 70 */ 71 private synchronized ThreadStatus getOrCreateThreadStatus(Thread t) { 72 ThreadStatus status = mThreadStatus.get(t); 73 if (status == null) { 74 status = new ThreadStatus(); 75 mThreadStatus.put(t, status); 76 } 77 return status; 78 } 79 80 /** 81 * The following three methods are used to keep track of 82 * BitmapFaction.Options used for decoding and cancelling. 83 */ 84 private synchronized void setDecodingOptions(Thread t, 85 BitmapFactory.Options options) { 86 getOrCreateThreadStatus(t).mOptions = options; 87 } 88 89 synchronized void removeDecodingOptions(Thread t) { 90 ThreadStatus status = mThreadStatus.get(t); 91 status.mOptions = null; 92 } 93 94 /** 95 * The following three methods are used to keep track of which thread 96 * is being disabled for bitmap decoding. 97 */ 98 public synchronized boolean canThreadDecoding(Thread t) { 99 ThreadStatus status = mThreadStatus.get(t); 100 if (status == null) { 101 // allow decoding by default 102 return true; 103 } 104 105 boolean result = (status.mState != State.CANCEL); 106 return result; 107 } 108 109 public synchronized void allowThreadDecoding(Thread t) { 110 getOrCreateThreadStatus(t).mState = State.ALLOW; 111 } 112 113 public synchronized void cancelThreadDecoding(Thread t, ContentResolver cr) { 114 ThreadStatus status = getOrCreateThreadStatus(t); 115 status.mState = State.CANCEL; 116 if (status.mOptions != null) { 117 status.mOptions.requestCancelDecode(); 118 } 119 120 // Wake up threads in waiting list 121 notifyAll(); 122 123 // Since our cancel request can arrive MediaProvider earlier than getThumbnail request, 124 // we use mThumbRequesting flag to make sure our request does cancel the request. 125 try { 126 synchronized (status) { 127 while (status.mThumbRequesting) { 128 Images.Thumbnails.cancelThumbnailRequest(cr, -1, t.getId()); 129 Video.Thumbnails.cancelThumbnailRequest(cr, -1, t.getId()); 130 status.wait(200); 131 } 132 } 133 } catch (InterruptedException ex) { 134 // ignore it. 135 } 136 } 137 138 public Bitmap getThumbnail(ContentResolver cr, long origId, int kind, 139 BitmapFactory.Options options, boolean isVideo) { 140 Thread t = Thread.currentThread(); 141 ThreadStatus status = getOrCreateThreadStatus(t); 142 143 if (!canThreadDecoding(t)) { 144 Log.d(TAG, "Thread " + t + " is not allowed to decode."); 145 return null; 146 } 147 148 try { 149 synchronized (status) { 150 status.mThumbRequesting = true; 151 } 152 if (isVideo) { 153 return Video.Thumbnails.getThumbnail(cr, origId, t.getId(), 154 kind, null); 155 } else { 156 return Images.Thumbnails.getThumbnail(cr, origId, t.getId(), 157 kind, null); 158 } 159 } finally { 160 synchronized (status) { 161 status.mThumbRequesting = false; 162 status.notifyAll(); 163 } 164 } 165 } 166 167 public static synchronized BitmapManager instance() { 168 if (sManager == null) { 169 sManager = new BitmapManager(); 170 } 171 return sManager; 172 } 173 174 /** 175 * The real place to delegate bitmap decoding to BitmapFactory. 176 */ 177 public Bitmap decodeFileDescriptor(FileDescriptor fd, 178 BitmapFactory.Options options) { 179 if (options.mCancel) { 180 return null; 181 } 182 183 Thread thread = Thread.currentThread(); 184 if (!canThreadDecoding(thread)) { 185 Log.d(TAG, "Thread " + thread + " is not allowed to decode."); 186 return null; 187 } 188 189 setDecodingOptions(thread, options); 190 Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, options); 191 192 removeDecodingOptions(thread); 193 return b; 194 } 195} 196