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