1666ea1b28a76aeba74744148b15099254d918671Owen Lin/*
2666ea1b28a76aeba74744148b15099254d918671Owen Lin * Copyright (C) 2009 The Android Open Source Project
3666ea1b28a76aeba74744148b15099254d918671Owen Lin *
4666ea1b28a76aeba74744148b15099254d918671Owen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5666ea1b28a76aeba74744148b15099254d918671Owen Lin * you may not use this file except in compliance with the License.
6666ea1b28a76aeba74744148b15099254d918671Owen Lin * You may obtain a copy of the License at
7666ea1b28a76aeba74744148b15099254d918671Owen Lin *
8666ea1b28a76aeba74744148b15099254d918671Owen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9666ea1b28a76aeba74744148b15099254d918671Owen Lin *
10666ea1b28a76aeba74744148b15099254d918671Owen Lin * Unless required by applicable law or agreed to in writing, software
11666ea1b28a76aeba74744148b15099254d918671Owen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12666ea1b28a76aeba74744148b15099254d918671Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13666ea1b28a76aeba74744148b15099254d918671Owen Lin * See the License for the specific language governing permissions and
14666ea1b28a76aeba74744148b15099254d918671Owen Lin * limitations under the License.
15666ea1b28a76aeba74744148b15099254d918671Owen Lin */
16666ea1b28a76aeba74744148b15099254d918671Owen Lin
17666ea1b28a76aeba74744148b15099254d918671Owen Linpackage com.android.camera;
18666ea1b28a76aeba74744148b15099254d918671Owen Lin
19d6c2fb7a38fcdb58742fcfffd67a4594487ec71cOwen Linimport com.android.gallery.R;
20d6c2fb7a38fcdb58742fcfffd67a4594487ec71cOwen Lin
21666ea1b28a76aeba74744148b15099254d918671Owen Linimport static com.android.camera.Util.Assert;
22666ea1b28a76aeba74744148b15099254d918671Owen Lin
23666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.app.Activity;
24666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.content.Context;
25666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Bitmap;
26666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Canvas;
27666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Paint;
28666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Rect;
29666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.drawable.Drawable;
30666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.media.AudioManager;
31666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.os.Handler;
32666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.util.AttributeSet;
33666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.util.DisplayMetrics;
34666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.GestureDetector;
35666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.KeyEvent;
36666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.MotionEvent;
37666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.View;
38666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.ViewConfiguration;
39666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.GestureDetector.SimpleOnGestureListener;
40666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.widget.Scroller;
41666ea1b28a76aeba74744148b15099254d918671Owen Lin
42666ea1b28a76aeba74744148b15099254d918671Owen Linimport com.android.camera.gallery.IImage;
43666ea1b28a76aeba74744148b15099254d918671Owen Linimport com.android.camera.gallery.IImageList;
44666ea1b28a76aeba74744148b15099254d918671Owen Lin
45666ea1b28a76aeba74744148b15099254d918671Owen Linimport java.util.HashMap;
46666ea1b28a76aeba74744148b15099254d918671Owen Lin
47666ea1b28a76aeba74744148b15099254d918671Owen Linclass GridViewSpecial extends View {
48666ea1b28a76aeba74744148b15099254d918671Owen Lin    @SuppressWarnings("unused")
49666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final String TAG = "GridViewSpecial";
50666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final float MAX_FLING_VELOCITY = 2500;
51666ea1b28a76aeba74744148b15099254d918671Owen Lin
52666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static interface Listener {
53666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onImageClicked(int index);
54666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onImageTapped(int index);
55666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onLayoutComplete(boolean changed);
56666ea1b28a76aeba74744148b15099254d918671Owen Lin
57666ea1b28a76aeba74744148b15099254d918671Owen Lin        /**
58666ea1b28a76aeba74744148b15099254d918671Owen Lin         * Invoked when the <code>GridViewSpecial</code> scrolls.
59666ea1b28a76aeba74744148b15099254d918671Owen Lin         *
60666ea1b28a76aeba74744148b15099254d918671Owen Lin         * @param scrollPosition the position of the scroller in the range
61666ea1b28a76aeba74744148b15099254d918671Owen Lin         *         [0, 1], when 0 means on the top and 1 means on the buttom
62666ea1b28a76aeba74744148b15099254d918671Owen Lin         */
63666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onScroll(float scrollPosition);
64666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
65666ea1b28a76aeba74744148b15099254d918671Owen Lin
66666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static interface DrawAdapter {
67666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void drawImage(Canvas canvas, IImage image,
68666ea1b28a76aeba74744148b15099254d918671Owen Lin                Bitmap b, int xPos, int yPos, int w, int h);
69666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void drawDecoration(Canvas canvas, IImage image,
70666ea1b28a76aeba74744148b15099254d918671Owen Lin                int xPos, int yPos, int w, int h);
71666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean needsDecoration();
72666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
73666ea1b28a76aeba74744148b15099254d918671Owen Lin
74666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int INDEX_NONE = -1;
75666ea1b28a76aeba74744148b15099254d918671Owen Lin
76666ea1b28a76aeba74744148b15099254d918671Owen Lin    // There are two cell size we will use. It can be set by setSizeChoice().
77666ea1b28a76aeba74744148b15099254d918671Owen Lin    // The mLeftEdgePadding fields is filled in onLayout(). See the comments
78666ea1b28a76aeba74744148b15099254d918671Owen Lin    // in onLayout() for details.
79666ea1b28a76aeba74744148b15099254d918671Owen Lin    static class LayoutSpec {
80666ea1b28a76aeba74744148b15099254d918671Owen Lin        LayoutSpec(int w, int h, int intercellSpacing, int leftEdgePadding,
81666ea1b28a76aeba74744148b15099254d918671Owen Lin                DisplayMetrics metrics) {
82666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCellWidth = dpToPx(w, metrics);
83666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCellHeight = dpToPx(h, metrics);
84666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCellSpacing = dpToPx(intercellSpacing, metrics);
85666ea1b28a76aeba74744148b15099254d918671Owen Lin            mLeftEdgePadding = dpToPx(leftEdgePadding, metrics);
86666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
87666ea1b28a76aeba74744148b15099254d918671Owen Lin        int mCellWidth, mCellHeight;
88666ea1b28a76aeba74744148b15099254d918671Owen Lin        int mCellSpacing;
89666ea1b28a76aeba74744148b15099254d918671Owen Lin        int mLeftEdgePadding;
90666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
91666ea1b28a76aeba74744148b15099254d918671Owen Lin
92666ea1b28a76aeba74744148b15099254d918671Owen Lin    private LayoutSpec [] mCellSizeChoices;
93666ea1b28a76aeba74744148b15099254d918671Owen Lin
94666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void initCellSize() {
95666ea1b28a76aeba74744148b15099254d918671Owen Lin        Activity a = (Activity) getContext();
96666ea1b28a76aeba74744148b15099254d918671Owen Lin        DisplayMetrics metrics = new DisplayMetrics();
97666ea1b28a76aeba74744148b15099254d918671Owen Lin        a.getWindowManager().getDefaultDisplay().getMetrics(metrics);
98666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCellSizeChoices = new LayoutSpec[] {
99666ea1b28a76aeba74744148b15099254d918671Owen Lin            new LayoutSpec(67, 67, 8, 0, metrics),
100666ea1b28a76aeba74744148b15099254d918671Owen Lin            new LayoutSpec(92, 92, 8, 0, metrics),
101666ea1b28a76aeba74744148b15099254d918671Owen Lin        };
102666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
103666ea1b28a76aeba74744148b15099254d918671Owen Lin
104666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Converts dp to pixel.
105666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static int dpToPx(int dp, DisplayMetrics metrics) {
106666ea1b28a76aeba74744148b15099254d918671Owen Lin        return (int) (metrics.density * dp);
107666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
108666ea1b28a76aeba74744148b15099254d918671Owen Lin
109666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are set in init().
110666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Handler mHandler = new Handler();
111666ea1b28a76aeba74744148b15099254d918671Owen Lin    private GestureDetector mGestureDetector;
112666ea1b28a76aeba74744148b15099254d918671Owen Lin    private ImageBlockManager mImageBlockManager;
113666ea1b28a76aeba74744148b15099254d918671Owen Lin
114666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are set in set*() functions.
115666ea1b28a76aeba74744148b15099254d918671Owen Lin    private ImageLoader mLoader;
116666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Listener mListener = null;
117666ea1b28a76aeba74744148b15099254d918671Owen Lin    private DrawAdapter mDrawAdapter = null;
118666ea1b28a76aeba74744148b15099254d918671Owen Lin    private IImageList mAllImages = ImageManager.makeEmptyImageList();
119666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mSizeChoice = 1;  // default is big cell size
120666ea1b28a76aeba74744148b15099254d918671Owen Lin
121666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are set in onLayout().
122666ea1b28a76aeba74744148b15099254d918671Owen Lin    private LayoutSpec mSpec;
123666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mColumns;
124666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mMaxScrollY;
125666ea1b28a76aeba74744148b15099254d918671Owen Lin
126666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We can handle events only if onLayout() is completed.
127666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean mLayoutComplete = false;
128666ea1b28a76aeba74744148b15099254d918671Owen Lin
129666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Selection state
130666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mCurrentSelection = INDEX_NONE;
131666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mCurrentPressState = 0;
132666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int TAPPING_FLAG = 1;
133666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int CLICKING_FLAG = 2;
134666ea1b28a76aeba74744148b15099254d918671Owen Lin
135666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are cached derived information.
136666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mCount;  // Cache mImageList.getCount();
137666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mRows;  // Cache (mCount + mColumns - 1) / mColumns
138666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mBlockHeight; // Cache mSpec.mCellSpacing + mSpec.mCellHeight
139666ea1b28a76aeba74744148b15099254d918671Owen Lin
140666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean mRunning = false;
141666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Scroller mScroller = null;
142666ea1b28a76aeba74744148b15099254d918671Owen Lin
143666ea1b28a76aeba74744148b15099254d918671Owen Lin    public GridViewSpecial(Context context, AttributeSet attrs) {
144666ea1b28a76aeba74744148b15099254d918671Owen Lin        super(context, attrs);
145666ea1b28a76aeba74744148b15099254d918671Owen Lin        init(context);
146666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
147666ea1b28a76aeba74744148b15099254d918671Owen Lin
148666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void init(Context context) {
149666ea1b28a76aeba74744148b15099254d918671Owen Lin        setVerticalScrollBarEnabled(true);
150666ea1b28a76aeba74744148b15099254d918671Owen Lin        initializeScrollbars(context.obtainStyledAttributes(
151666ea1b28a76aeba74744148b15099254d918671Owen Lin                android.R.styleable.View));
152666ea1b28a76aeba74744148b15099254d918671Owen Lin        mGestureDetector = new GestureDetector(context,
153666ea1b28a76aeba74744148b15099254d918671Owen Lin                new MyGestureDetector());
154666ea1b28a76aeba74744148b15099254d918671Owen Lin        setFocusableInTouchMode(true);
155666ea1b28a76aeba74744148b15099254d918671Owen Lin        initCellSize();
156666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
157666ea1b28a76aeba74744148b15099254d918671Owen Lin
158666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Runnable mRedrawCallback = new Runnable() {
159666ea1b28a76aeba74744148b15099254d918671Owen Lin                public void run() {
160666ea1b28a76aeba74744148b15099254d918671Owen Lin                    invalidate();
161666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
162666ea1b28a76aeba74744148b15099254d918671Owen Lin            };
163666ea1b28a76aeba74744148b15099254d918671Owen Lin
164666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setLoader(ImageLoader loader) {
165666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
166666ea1b28a76aeba74744148b15099254d918671Owen Lin        mLoader = loader;
167666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
168666ea1b28a76aeba74744148b15099254d918671Owen Lin
169666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setListener(Listener listener) {
170666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
171666ea1b28a76aeba74744148b15099254d918671Owen Lin        mListener = listener;
172666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
173666ea1b28a76aeba74744148b15099254d918671Owen Lin
174666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setDrawAdapter(DrawAdapter adapter) {
175666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
176666ea1b28a76aeba74744148b15099254d918671Owen Lin        mDrawAdapter = adapter;
177666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
178666ea1b28a76aeba74744148b15099254d918671Owen Lin
179666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setImageList(IImageList list) {
180666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
181666ea1b28a76aeba74744148b15099254d918671Owen Lin        mAllImages = list;
182666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCount = mAllImages.getCount();
183666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
184666ea1b28a76aeba74744148b15099254d918671Owen Lin
185666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setSizeChoice(int choice) {
186666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
187666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mSizeChoice == choice) return;
188666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSizeChoice = choice;
189666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
190666ea1b28a76aeba74744148b15099254d918671Owen Lin
191666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
192666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void onLayout(boolean changed, int left, int top,
193666ea1b28a76aeba74744148b15099254d918671Owen Lin                         int right, int bottom) {
194666ea1b28a76aeba74744148b15099254d918671Owen Lin        super.onLayout(changed, left, top, right, bottom);
195666ea1b28a76aeba74744148b15099254d918671Owen Lin
196666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!mRunning) {
197666ea1b28a76aeba74744148b15099254d918671Owen Lin            return;
198666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
199666ea1b28a76aeba74744148b15099254d918671Owen Lin
200666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSpec = mCellSizeChoices[mSizeChoice];
201666ea1b28a76aeba74744148b15099254d918671Owen Lin
202666ea1b28a76aeba74744148b15099254d918671Owen Lin        int width = right - left;
203666ea1b28a76aeba74744148b15099254d918671Owen Lin
204666ea1b28a76aeba74744148b15099254d918671Owen Lin        // The width is divided into following parts:
205666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
206666ea1b28a76aeba74744148b15099254d918671Owen Lin        // LeftEdgePadding CellWidth (CellSpacing CellWidth)* RightEdgePadding
207666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
208666ea1b28a76aeba74744148b15099254d918671Owen Lin        // We determine number of cells (columns) first, then the left and right
209666ea1b28a76aeba74744148b15099254d918671Owen Lin        // padding are derived. We make left and right paddings the same size.
210666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
211666ea1b28a76aeba74744148b15099254d918671Owen Lin        // The height is divided into following parts:
212666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
213666ea1b28a76aeba74744148b15099254d918671Owen Lin        // CellSpacing (CellHeight CellSpacing)+
214666ea1b28a76aeba74744148b15099254d918671Owen Lin
215666ea1b28a76aeba74744148b15099254d918671Owen Lin        mColumns = 1 + (width - mSpec.mCellWidth)
216666ea1b28a76aeba74744148b15099254d918671Owen Lin                / (mSpec.mCellWidth + mSpec.mCellSpacing);
217666ea1b28a76aeba74744148b15099254d918671Owen Lin
218666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSpec.mLeftEdgePadding = (width
219666ea1b28a76aeba74744148b15099254d918671Owen Lin                - ((mColumns - 1) * mSpec.mCellSpacing)
220666ea1b28a76aeba74744148b15099254d918671Owen Lin                - (mColumns * mSpec.mCellWidth)) / 2;
221666ea1b28a76aeba74744148b15099254d918671Owen Lin
222666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRows = (mCount + mColumns - 1) / mColumns;
223666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBlockHeight = mSpec.mCellSpacing + mSpec.mCellHeight;
224666ea1b28a76aeba74744148b15099254d918671Owen Lin        mMaxScrollY = mSpec.mCellSpacing + (mRows * mBlockHeight)
225666ea1b28a76aeba74744148b15099254d918671Owen Lin                - (bottom - top);
226666ea1b28a76aeba74744148b15099254d918671Owen Lin
227666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Put mScrollY in the valid range. This matters if mMaxScrollY is
228666ea1b28a76aeba74744148b15099254d918671Owen Lin        // changed. For example, orientation changed from portrait to landscape.
229666ea1b28a76aeba74744148b15099254d918671Owen Lin        mScrollY = Math.max(0, Math.min(mMaxScrollY, mScrollY));
230666ea1b28a76aeba74744148b15099254d918671Owen Lin
231666ea1b28a76aeba74744148b15099254d918671Owen Lin        generateOutlineBitmap();
232666ea1b28a76aeba74744148b15099254d918671Owen Lin
233666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mImageBlockManager != null) {
234666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager.recycle();
235666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
236666ea1b28a76aeba74744148b15099254d918671Owen Lin
237666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageBlockManager = new ImageBlockManager(mHandler, mRedrawCallback,
238666ea1b28a76aeba74744148b15099254d918671Owen Lin                mAllImages, mLoader, mDrawAdapter, mSpec, mColumns, width,
239666ea1b28a76aeba74744148b15099254d918671Owen Lin                mOutline[OUTLINE_EMPTY]);
240666ea1b28a76aeba74744148b15099254d918671Owen Lin
241666ea1b28a76aeba74744148b15099254d918671Owen Lin        mListener.onLayoutComplete(changed);
242666ea1b28a76aeba74744148b15099254d918671Owen Lin
243666ea1b28a76aeba74744148b15099254d918671Owen Lin        moveDataWindow();
244666ea1b28a76aeba74744148b15099254d918671Owen Lin
245666ea1b28a76aeba74744148b15099254d918671Owen Lin        mLayoutComplete = true;
246666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
247666ea1b28a76aeba74744148b15099254d918671Owen Lin
248666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
249666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected int computeVerticalScrollRange() {
250666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mMaxScrollY + getHeight();
251666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
252666ea1b28a76aeba74744148b15099254d918671Owen Lin
253666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We cache the three outlines from NinePatch to Bitmap to speed up
254666ea1b28a76aeba74744148b15099254d918671Owen Lin    // drawing. The cache must be updated if the cell size is changed.
255666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int OUTLINE_EMPTY = 0;
256666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int OUTLINE_PRESSED = 1;
257666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int OUTLINE_SELECTED = 2;
258666ea1b28a76aeba74744148b15099254d918671Owen Lin
259666ea1b28a76aeba74744148b15099254d918671Owen Lin    public Bitmap mOutline[] = new Bitmap[3];
260666ea1b28a76aeba74744148b15099254d918671Owen Lin
261666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void generateOutlineBitmap() {
262666ea1b28a76aeba74744148b15099254d918671Owen Lin        int w = mSpec.mCellWidth;
263666ea1b28a76aeba74744148b15099254d918671Owen Lin        int h = mSpec.mCellHeight;
264666ea1b28a76aeba74744148b15099254d918671Owen Lin
265666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = 0; i < mOutline.length; i++) {
266666ea1b28a76aeba74744148b15099254d918671Owen Lin            mOutline[i] = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
267666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
268666ea1b28a76aeba74744148b15099254d918671Owen Lin
269666ea1b28a76aeba74744148b15099254d918671Owen Lin        Drawable cellOutline;
270666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline = GridViewSpecial.this.getResources()
271666ea1b28a76aeba74744148b15099254d918671Owen Lin                .getDrawable(android.R.drawable.gallery_thumb);
272666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setBounds(0, 0, w, h);
273666ea1b28a76aeba74744148b15099254d918671Owen Lin        Canvas canvas = new Canvas();
274666ea1b28a76aeba74744148b15099254d918671Owen Lin
275666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.setBitmap(mOutline[OUTLINE_EMPTY]);
276666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setState(EMPTY_STATE_SET);
277666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.draw(canvas);
278666ea1b28a76aeba74744148b15099254d918671Owen Lin
279666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.setBitmap(mOutline[OUTLINE_PRESSED]);
280666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setState(
281666ea1b28a76aeba74744148b15099254d918671Owen Lin                PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET);
282666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.draw(canvas);
283666ea1b28a76aeba74744148b15099254d918671Owen Lin
284666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.setBitmap(mOutline[OUTLINE_SELECTED]);
285666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setState(ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET);
286666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.draw(canvas);
287666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
288666ea1b28a76aeba74744148b15099254d918671Owen Lin
289666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void moveDataWindow() {
290666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Calculate visible region according to scroll position.
291666ea1b28a76aeba74744148b15099254d918671Owen Lin        int startRow = (mScrollY - mSpec.mCellSpacing) / mBlockHeight;
292666ea1b28a76aeba74744148b15099254d918671Owen Lin        int endRow = (mScrollY + getHeight() - mSpec.mCellSpacing - 1)
293666ea1b28a76aeba74744148b15099254d918671Owen Lin                / mBlockHeight + 1;
294666ea1b28a76aeba74744148b15099254d918671Owen Lin
295666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Limit startRow and endRow to the valid range.
296666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Make sure we handle the mRows == 0 case right.
297666ea1b28a76aeba74744148b15099254d918671Owen Lin        startRow = Math.max(Math.min(startRow, mRows - 1), 0);
298666ea1b28a76aeba74744148b15099254d918671Owen Lin        endRow = Math.max(Math.min(endRow, mRows), 0);
299666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageBlockManager.setVisibleRows(startRow, endRow);
300666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
301666ea1b28a76aeba74744148b15099254d918671Owen Lin
302666ea1b28a76aeba74744148b15099254d918671Owen Lin    // In MyGestureDetector we have to check canHandleEvent() because
303666ea1b28a76aeba74744148b15099254d918671Owen Lin    // GestureDetector could queue events and fire them later. At that time
304666ea1b28a76aeba74744148b15099254d918671Owen Lin    // stop() may have already been called and we can't handle the events.
305666ea1b28a76aeba74744148b15099254d918671Owen Lin    private class MyGestureDetector extends SimpleOnGestureListener {
306666ea1b28a76aeba74744148b15099254d918671Owen Lin        private AudioManager mAudioManager;
307666ea1b28a76aeba74744148b15099254d918671Owen Lin
308666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
309666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onDown(MotionEvent e) {
310666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
311666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mScroller != null && !mScroller.isFinished()) {
312666ea1b28a76aeba74744148b15099254d918671Owen Lin                mScroller.forceFinished(true);
313666ea1b28a76aeba74744148b15099254d918671Owen Lin                return false;
314666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
315666ea1b28a76aeba74744148b15099254d918671Owen Lin            int index = computeSelectedIndex(e.getX(), e.getY());
316666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (index >= 0 && index < mCount) {
317666ea1b28a76aeba74744148b15099254d918671Owen Lin                setSelectedIndex(index);
318666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
319666ea1b28a76aeba74744148b15099254d918671Owen Lin                setSelectedIndex(INDEX_NONE);
320666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
321666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
322666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
323666ea1b28a76aeba74744148b15099254d918671Owen Lin
324666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
325666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onFling(MotionEvent e1, MotionEvent e2,
326666ea1b28a76aeba74744148b15099254d918671Owen Lin                float velocityX, float velocityY) {
327666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
328666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (velocityY > MAX_FLING_VELOCITY) {
329666ea1b28a76aeba74744148b15099254d918671Owen Lin                velocityY = MAX_FLING_VELOCITY;
330666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else if (velocityY < -MAX_FLING_VELOCITY) {
331666ea1b28a76aeba74744148b15099254d918671Owen Lin                velocityY = -MAX_FLING_VELOCITY;
332666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
333666ea1b28a76aeba74744148b15099254d918671Owen Lin
334666ea1b28a76aeba74744148b15099254d918671Owen Lin            setSelectedIndex(INDEX_NONE);
335666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller = new Scroller(getContext());
336666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller.fling(0, mScrollY, 0, -(int) velocityY, 0, 0, 0,
337666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mMaxScrollY);
338666ea1b28a76aeba74744148b15099254d918671Owen Lin            computeScroll();
339666ea1b28a76aeba74744148b15099254d918671Owen Lin
340666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
341666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
342666ea1b28a76aeba74744148b15099254d918671Owen Lin
343666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
344666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onLongPress(MotionEvent e) {
345666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return;
346666ea1b28a76aeba74744148b15099254d918671Owen Lin            performLongClick();
347666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
348666ea1b28a76aeba74744148b15099254d918671Owen Lin
349666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
350666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onScroll(MotionEvent e1, MotionEvent e2,
351666ea1b28a76aeba74744148b15099254d918671Owen Lin                                float distanceX, float distanceY) {
352666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
353666ea1b28a76aeba74744148b15099254d918671Owen Lin            setSelectedIndex(INDEX_NONE);
354666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollBy(0, (int) distanceY);
355666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidate();
356666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
357666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
358666ea1b28a76aeba74744148b15099254d918671Owen Lin
359666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
360666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onSingleTapConfirmed(MotionEvent e) {
361666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
362666ea1b28a76aeba74744148b15099254d918671Owen Lin            int index = computeSelectedIndex(e.getX(), e.getY());
363666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (index >= 0 && index < mCount) {
364666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Play click sound.
365666ea1b28a76aeba74744148b15099254d918671Owen Lin                if (mAudioManager == null) {
366666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mAudioManager = (AudioManager) getContext()
367666ea1b28a76aeba74744148b15099254d918671Owen Lin                            .getSystemService(Context.AUDIO_SERVICE);
368666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
369666ea1b28a76aeba74744148b15099254d918671Owen Lin                mAudioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
370666ea1b28a76aeba74744148b15099254d918671Owen Lin
371666ea1b28a76aeba74744148b15099254d918671Owen Lin                mListener.onImageTapped(index);
372666ea1b28a76aeba74744148b15099254d918671Owen Lin                return true;
373666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
374666ea1b28a76aeba74744148b15099254d918671Owen Lin            return false;
375666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
376666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
377666ea1b28a76aeba74744148b15099254d918671Owen Lin
378666ea1b28a76aeba74744148b15099254d918671Owen Lin    public int getCurrentSelection() {
379666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mCurrentSelection;
380666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
381666ea1b28a76aeba74744148b15099254d918671Owen Lin
382666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void invalidateImage(int index) {
383666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (index != INDEX_NONE) {
384666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager.invalidateImage(index);
385666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
386666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
387666ea1b28a76aeba74744148b15099254d918671Owen Lin
388666ea1b28a76aeba74744148b15099254d918671Owen Lin    /**
389666ea1b28a76aeba74744148b15099254d918671Owen Lin     *
390666ea1b28a76aeba74744148b15099254d918671Owen Lin     * @param index <code>INDEX_NONE</code> (-1) means remove selection.
391666ea1b28a76aeba74744148b15099254d918671Owen Lin     */
392666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setSelectedIndex(int index) {
393666ea1b28a76aeba74744148b15099254d918671Owen Lin        // A selection box will be shown for the image that being selected,
394666ea1b28a76aeba74744148b15099254d918671Owen Lin        // (by finger or by the dpad center key). The selection box can be drawn
395666ea1b28a76aeba74744148b15099254d918671Owen Lin        // in two colors. One color (yellow) is used when the the image is
396666ea1b28a76aeba74744148b15099254d918671Owen Lin        // still being tapped or clicked (the finger is still on the touch
397666ea1b28a76aeba74744148b15099254d918671Owen Lin        // screen or the dpad center key is not released). Another color
398666ea1b28a76aeba74744148b15099254d918671Owen Lin        // (orange) is used after the finger leaves touch screen or the dpad
399666ea1b28a76aeba74744148b15099254d918671Owen Lin        // center key is released.
400666ea1b28a76aeba74744148b15099254d918671Owen Lin
401666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentSelection == index) {
402666ea1b28a76aeba74744148b15099254d918671Owen Lin            return;
403666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
404666ea1b28a76aeba74744148b15099254d918671Owen Lin        // This happens when the last picture is deleted.
405666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCurrentSelection = Math.min(index, mCount - 1);
406666ea1b28a76aeba74744148b15099254d918671Owen Lin
407666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentSelection != INDEX_NONE) {
408666ea1b28a76aeba74744148b15099254d918671Owen Lin            ensureVisible(mCurrentSelection);
409666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
410666ea1b28a76aeba74744148b15099254d918671Owen Lin        invalidate();
411666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
412666ea1b28a76aeba74744148b15099254d918671Owen Lin
413666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollToImage(int index) {
414666ea1b28a76aeba74744148b15099254d918671Owen Lin        Rect r = getRectForPosition(index);
415666ea1b28a76aeba74744148b15099254d918671Owen Lin        scrollTo(0, r.top);
416666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
417666ea1b28a76aeba74744148b15099254d918671Owen Lin
418666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollToVisible(int index) {
419666ea1b28a76aeba74744148b15099254d918671Owen Lin        Rect r = getRectForPosition(index);
420666ea1b28a76aeba74744148b15099254d918671Owen Lin        int top = getScrollY();
421666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bottom = getScrollY() + getHeight();
422666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (r.bottom > bottom) {
423666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollTo(0, r.bottom - getHeight());
424666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else if (r.top < top) {
425666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollTo(0, r.top);
426666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
427666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
428666ea1b28a76aeba74744148b15099254d918671Owen Lin
429666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void ensureVisible(int pos) {
430666ea1b28a76aeba74744148b15099254d918671Owen Lin        Rect r = getRectForPosition(pos);
431666ea1b28a76aeba74744148b15099254d918671Owen Lin        int top = getScrollY();
432666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bot = top + getHeight();
433666ea1b28a76aeba74744148b15099254d918671Owen Lin
434666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (r.bottom > bot) {
435666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller = new Scroller(getContext());
436666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller.startScroll(mScrollX, mScrollY, 0,
437666ea1b28a76aeba74744148b15099254d918671Owen Lin                    r.bottom - getHeight() - mScrollY, 200);
438666ea1b28a76aeba74744148b15099254d918671Owen Lin            computeScroll();
439666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else if (r.top < top) {
440666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller = new Scroller(getContext());
441666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller.startScroll(mScrollX, mScrollY, 0, r.top - mScrollY, 200);
442666ea1b28a76aeba74744148b15099254d918671Owen Lin            computeScroll();
443666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
444666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
445666ea1b28a76aeba74744148b15099254d918671Owen Lin
446666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void start() {
447666ea1b28a76aeba74744148b15099254d918671Owen Lin        // These must be set before start().
448666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mLoader != null);
449666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mListener != null);
450666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mDrawAdapter != null);
451666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRunning = true;
452666ea1b28a76aeba74744148b15099254d918671Owen Lin        requestLayout();
453666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
454666ea1b28a76aeba74744148b15099254d918671Owen Lin
455666ea1b28a76aeba74744148b15099254d918671Owen Lin    // If the the underlying data is changed, for example,
456666ea1b28a76aeba74744148b15099254d918671Owen Lin    // an image is deleted, or the size choice is changed,
457666ea1b28a76aeba74744148b15099254d918671Owen Lin    // The following sequence is needed:
458666ea1b28a76aeba74744148b15099254d918671Owen Lin    //
459666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.stop();
460666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.set...(...);
461666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.set...(...);
462666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.start();
463666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void stop() {
464666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Remove the long press callback from the queue if we are going to
465666ea1b28a76aeba74744148b15099254d918671Owen Lin        // stop.
466666ea1b28a76aeba74744148b15099254d918671Owen Lin        mHandler.removeCallbacks(mLongPressCallback);
467666ea1b28a76aeba74744148b15099254d918671Owen Lin        mScroller = null;
468666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mImageBlockManager != null) {
469666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager.recycle();
470666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager = null;
471666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
472666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRunning = false;
473666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCurrentSelection = INDEX_NONE;
474666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
475666ea1b28a76aeba74744148b15099254d918671Owen Lin
476666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
477666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void onDraw(Canvas canvas) {
478666ea1b28a76aeba74744148b15099254d918671Owen Lin        super.onDraw(canvas);
479666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) return;
480666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageBlockManager.doDraw(canvas, getWidth(), getHeight(), mScrollY);
481666ea1b28a76aeba74744148b15099254d918671Owen Lin        paintDecoration(canvas);
482666ea1b28a76aeba74744148b15099254d918671Owen Lin        paintSelection(canvas);
483666ea1b28a76aeba74744148b15099254d918671Owen Lin        moveDataWindow();
484666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
485666ea1b28a76aeba74744148b15099254d918671Owen Lin
486666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
487666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void computeScroll() {
488666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mScroller != null) {
489666ea1b28a76aeba74744148b15099254d918671Owen Lin            boolean more = mScroller.computeScrollOffset();
490666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollTo(0, mScroller.getCurrY());
491666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (more) {
492666ea1b28a76aeba74744148b15099254d918671Owen Lin                invalidate();  // So we draw again
493666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
494666ea1b28a76aeba74744148b15099254d918671Owen Lin                mScroller = null;
495666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
496666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else {
497666ea1b28a76aeba74744148b15099254d918671Owen Lin            super.computeScroll();
498666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
499666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
500666ea1b28a76aeba74744148b15099254d918671Owen Lin
501666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Return the rectange for the thumbnail in the given position.
502666ea1b28a76aeba74744148b15099254d918671Owen Lin    Rect getRectForPosition(int pos) {
503666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = pos / mColumns;
504666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = pos - (row * mColumns);
505666ea1b28a76aeba74744148b15099254d918671Owen Lin
506666ea1b28a76aeba74744148b15099254d918671Owen Lin        int left = mSpec.mLeftEdgePadding
507666ea1b28a76aeba74744148b15099254d918671Owen Lin                + (col * (mSpec.mCellWidth + mSpec.mCellSpacing));
508666ea1b28a76aeba74744148b15099254d918671Owen Lin        int top = row * mBlockHeight;
509666ea1b28a76aeba74744148b15099254d918671Owen Lin
510666ea1b28a76aeba74744148b15099254d918671Owen Lin        return new Rect(left, top,
511666ea1b28a76aeba74744148b15099254d918671Owen Lin                left + mSpec.mCellWidth + mSpec.mCellSpacing,
512666ea1b28a76aeba74744148b15099254d918671Owen Lin                top + mSpec.mCellHeight + mSpec.mCellSpacing);
513666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
514666ea1b28a76aeba74744148b15099254d918671Owen Lin
515666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Inverse of getRectForPosition: from screen coordinate to image position.
516666ea1b28a76aeba74744148b15099254d918671Owen Lin    int computeSelectedIndex(float xFloat, float yFloat) {
517666ea1b28a76aeba74744148b15099254d918671Owen Lin        int x = (int) xFloat;
518666ea1b28a76aeba74744148b15099254d918671Owen Lin        int y = (int) yFloat;
519666ea1b28a76aeba74744148b15099254d918671Owen Lin
520666ea1b28a76aeba74744148b15099254d918671Owen Lin        int spacing = mSpec.mCellSpacing;
521666ea1b28a76aeba74744148b15099254d918671Owen Lin        int leftSpacing = mSpec.mLeftEdgePadding;
522666ea1b28a76aeba74744148b15099254d918671Owen Lin
523666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = (mScrollY + y - spacing) / (mSpec.mCellHeight + spacing);
524666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = Math.min(mColumns - 1,
525666ea1b28a76aeba74744148b15099254d918671Owen Lin                (x - leftSpacing) / (mSpec.mCellWidth + spacing));
526666ea1b28a76aeba74744148b15099254d918671Owen Lin        return (row * mColumns) + col;
527666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
528666ea1b28a76aeba74744148b15099254d918671Owen Lin
529666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
530666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean onTouchEvent(MotionEvent ev) {
531666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) {
532666ea1b28a76aeba74744148b15099254d918671Owen Lin            return false;
533666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
534666ea1b28a76aeba74744148b15099254d918671Owen Lin        switch (ev.getAction()) {
535666ea1b28a76aeba74744148b15099254d918671Owen Lin            case MotionEvent.ACTION_DOWN:
536666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCurrentPressState |= TAPPING_FLAG;
537666ea1b28a76aeba74744148b15099254d918671Owen Lin                invalidate();
538666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;
539666ea1b28a76aeba74744148b15099254d918671Owen Lin            case MotionEvent.ACTION_UP:
540666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCurrentPressState &= ~TAPPING_FLAG;
541666ea1b28a76aeba74744148b15099254d918671Owen Lin                invalidate();
542666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;
543666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
544666ea1b28a76aeba74744148b15099254d918671Owen Lin        mGestureDetector.onTouchEvent(ev);
545666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Consume all events
546666ea1b28a76aeba74744148b15099254d918671Owen Lin        return true;
547666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
548666ea1b28a76aeba74744148b15099254d918671Owen Lin
549666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
550666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollBy(int x, int y) {
551666ea1b28a76aeba74744148b15099254d918671Owen Lin        scrollTo(mScrollX + x, mScrollY + y);
552666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
553666ea1b28a76aeba74744148b15099254d918671Owen Lin
554666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollTo(float scrollPosition) {
555666ea1b28a76aeba74744148b15099254d918671Owen Lin        scrollTo(0, Math.round(scrollPosition * mMaxScrollY));
556666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
557666ea1b28a76aeba74744148b15099254d918671Owen Lin
558666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
559666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollTo(int x, int y) {
560666ea1b28a76aeba74744148b15099254d918671Owen Lin        y = Math.max(0, Math.min(mMaxScrollY, y));
561666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mSpec != null) {
562666ea1b28a76aeba74744148b15099254d918671Owen Lin            mListener.onScroll((float) mScrollY / mMaxScrollY);
563666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
564666ea1b28a76aeba74744148b15099254d918671Owen Lin        super.scrollTo(x, y);
565666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
566666ea1b28a76aeba74744148b15099254d918671Owen Lin
567666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean canHandleEvent() {
568666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mRunning && mLayoutComplete;
569666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
570666ea1b28a76aeba74744148b15099254d918671Owen Lin
571666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Runnable mLongPressCallback = new Runnable() {
572666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void run() {
573666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCurrentPressState &= ~CLICKING_FLAG;
574666ea1b28a76aeba74744148b15099254d918671Owen Lin            showContextMenu();
575666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
576666ea1b28a76aeba74744148b15099254d918671Owen Lin    };
577666ea1b28a76aeba74744148b15099254d918671Owen Lin
578666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
579666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean onKeyDown(int keyCode, KeyEvent event) {
580666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) return false;
581666ea1b28a76aeba74744148b15099254d918671Owen Lin        int sel = mCurrentSelection;
582666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (sel != INDEX_NONE) {
583666ea1b28a76aeba74744148b15099254d918671Owen Lin            switch (keyCode) {
584666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_RIGHT:
585666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (sel != mCount - 1 && (sel % mColumns < mColumns - 1)) {
586666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel += 1;
587666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
588666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
589666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_LEFT:
590666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (sel > 0 && (sel % mColumns != 0)) {
591666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel -= 1;
592666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
593666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
594666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_UP:
595666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (sel >= mColumns) {
596666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel -= mColumns;
597666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
598666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
599666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_DOWN:
600666ea1b28a76aeba74744148b15099254d918671Owen Lin                    sel = Math.min(mCount - 1, sel + mColumns);
601666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
602666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_CENTER:
603666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (event.getRepeatCount() == 0) {
604666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mCurrentPressState |= CLICKING_FLAG;
605666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mHandler.postDelayed(mLongPressCallback,
606666ea1b28a76aeba74744148b15099254d918671Owen Lin                                ViewConfiguration.getLongPressTimeout());
607666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
608666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
609666ea1b28a76aeba74744148b15099254d918671Owen Lin                default:
610666ea1b28a76aeba74744148b15099254d918671Owen Lin                    return super.onKeyDown(keyCode, event);
611666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
612666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else {
613666ea1b28a76aeba74744148b15099254d918671Owen Lin            switch (keyCode) {
614666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_RIGHT:
615666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_LEFT:
616666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_UP:
617666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_DOWN:
618666ea1b28a76aeba74744148b15099254d918671Owen Lin                        int startRow =
619666ea1b28a76aeba74744148b15099254d918671Owen Lin                                (mScrollY - mSpec.mCellSpacing) / mBlockHeight;
620666ea1b28a76aeba74744148b15099254d918671Owen Lin                        int topPos = startRow * mColumns;
621666ea1b28a76aeba74744148b15099254d918671Owen Lin                        Rect r = getRectForPosition(topPos);
622666ea1b28a76aeba74744148b15099254d918671Owen Lin                        if (r.top < getScrollY()) {
623666ea1b28a76aeba74744148b15099254d918671Owen Lin                            topPos += mColumns;
624666ea1b28a76aeba74744148b15099254d918671Owen Lin                        }
625666ea1b28a76aeba74744148b15099254d918671Owen Lin                        topPos = Math.min(mCount - 1, topPos);
626666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel = topPos;
627666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
628666ea1b28a76aeba74744148b15099254d918671Owen Lin                default:
629666ea1b28a76aeba74744148b15099254d918671Owen Lin                    return super.onKeyDown(keyCode, event);
630666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
631666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
632666ea1b28a76aeba74744148b15099254d918671Owen Lin        setSelectedIndex(sel);
633666ea1b28a76aeba74744148b15099254d918671Owen Lin        return true;
634666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
635666ea1b28a76aeba74744148b15099254d918671Owen Lin
636666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
637666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean onKeyUp(int keyCode, KeyEvent event) {
638666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) return false;
639666ea1b28a76aeba74744148b15099254d918671Owen Lin
640666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
641666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCurrentPressState &= ~CLICKING_FLAG;
642666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidate();
643666ea1b28a76aeba74744148b15099254d918671Owen Lin
644666ea1b28a76aeba74744148b15099254d918671Owen Lin            // The keyUp doesn't get called when the longpress menu comes up. We
645666ea1b28a76aeba74744148b15099254d918671Owen Lin            // only get here when the user lets go of the center key before the
646666ea1b28a76aeba74744148b15099254d918671Owen Lin            // longpress menu comes up.
647666ea1b28a76aeba74744148b15099254d918671Owen Lin            mHandler.removeCallbacks(mLongPressCallback);
648666ea1b28a76aeba74744148b15099254d918671Owen Lin
649666ea1b28a76aeba74744148b15099254d918671Owen Lin            // open the photo
650666ea1b28a76aeba74744148b15099254d918671Owen Lin            mListener.onImageClicked(mCurrentSelection);
651666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
652666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
653666ea1b28a76aeba74744148b15099254d918671Owen Lin        return super.onKeyUp(keyCode, event);
654666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
655666ea1b28a76aeba74744148b15099254d918671Owen Lin
656666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void paintDecoration(Canvas canvas) {
657666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!mDrawAdapter.needsDecoration()) return;
658666ea1b28a76aeba74744148b15099254d918671Owen Lin
659666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Calculate visible region according to scroll position.
660666ea1b28a76aeba74744148b15099254d918671Owen Lin        int startRow = (mScrollY - mSpec.mCellSpacing) / mBlockHeight;
661666ea1b28a76aeba74744148b15099254d918671Owen Lin        int endRow = (mScrollY + getHeight() - mSpec.mCellSpacing - 1)
662666ea1b28a76aeba74744148b15099254d918671Owen Lin                / mBlockHeight + 1;
663666ea1b28a76aeba74744148b15099254d918671Owen Lin
664666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Limit startRow and endRow to the valid range.
665666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Make sure we handle the mRows == 0 case right.
666666ea1b28a76aeba74744148b15099254d918671Owen Lin        startRow = Math.max(Math.min(startRow, mRows - 1), 0);
667666ea1b28a76aeba74744148b15099254d918671Owen Lin        endRow = Math.max(Math.min(endRow, mRows), 0);
668666ea1b28a76aeba74744148b15099254d918671Owen Lin
669666ea1b28a76aeba74744148b15099254d918671Owen Lin        int startIndex = startRow * mColumns;
670666ea1b28a76aeba74744148b15099254d918671Owen Lin        int endIndex = Math.min(endRow * mColumns, mCount);
671666ea1b28a76aeba74744148b15099254d918671Owen Lin
672666ea1b28a76aeba74744148b15099254d918671Owen Lin        int xPos = mSpec.mLeftEdgePadding;
673666ea1b28a76aeba74744148b15099254d918671Owen Lin        int yPos = mSpec.mCellSpacing + startRow * mBlockHeight;
674666ea1b28a76aeba74744148b15099254d918671Owen Lin        int off = 0;
675666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = startIndex; i < endIndex; i++) {
676666ea1b28a76aeba74744148b15099254d918671Owen Lin            IImage image = mAllImages.getImageAt(i);
677666ea1b28a76aeba74744148b15099254d918671Owen Lin
678666ea1b28a76aeba74744148b15099254d918671Owen Lin            mDrawAdapter.drawDecoration(canvas, image, xPos, yPos,
679666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mSpec.mCellWidth, mSpec.mCellHeight);
680666ea1b28a76aeba74744148b15099254d918671Owen Lin
681666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Calculate next position
682666ea1b28a76aeba74744148b15099254d918671Owen Lin            off += 1;
683666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (off == mColumns) {
684666ea1b28a76aeba74744148b15099254d918671Owen Lin                xPos = mSpec.mLeftEdgePadding;
685666ea1b28a76aeba74744148b15099254d918671Owen Lin                yPos += mBlockHeight;
686666ea1b28a76aeba74744148b15099254d918671Owen Lin                off = 0;
687666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
688666ea1b28a76aeba74744148b15099254d918671Owen Lin                xPos += mSpec.mCellWidth + mSpec.mCellSpacing;
689666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
690666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
691666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
692666ea1b28a76aeba74744148b15099254d918671Owen Lin
693666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void paintSelection(Canvas canvas) {
694666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentSelection == INDEX_NONE) return;
695666ea1b28a76aeba74744148b15099254d918671Owen Lin
696666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = mCurrentSelection / mColumns;
697666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = mCurrentSelection - (row * mColumns);
698666ea1b28a76aeba74744148b15099254d918671Owen Lin
699666ea1b28a76aeba74744148b15099254d918671Owen Lin        int spacing = mSpec.mCellSpacing;
700666ea1b28a76aeba74744148b15099254d918671Owen Lin        int leftSpacing = mSpec.mLeftEdgePadding;
701666ea1b28a76aeba74744148b15099254d918671Owen Lin        int xPos = leftSpacing + (col * (mSpec.mCellWidth + spacing));
702666ea1b28a76aeba74744148b15099254d918671Owen Lin        int yTop = spacing + (row * mBlockHeight);
703666ea1b28a76aeba74744148b15099254d918671Owen Lin
704666ea1b28a76aeba74744148b15099254d918671Owen Lin        int type = OUTLINE_SELECTED;
705666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentPressState != 0) {
706666ea1b28a76aeba74744148b15099254d918671Owen Lin            type = OUTLINE_PRESSED;
707666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
708666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawBitmap(mOutline[type], xPos, yTop, null);
709666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
710666ea1b28a76aeba74744148b15099254d918671Owen Lin}
711666ea1b28a76aeba74744148b15099254d918671Owen Lin
712666ea1b28a76aeba74744148b15099254d918671Owen Linclass ImageBlockManager {
713666ea1b28a76aeba74744148b15099254d918671Owen Lin    @SuppressWarnings("unused")
714666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final String TAG = "ImageBlockManager";
715666ea1b28a76aeba74744148b15099254d918671Owen Lin
716666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Number of rows we want to cache.
717666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Assume there are 6 rows per page, this caches 5 pages.
718666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int CACHE_ROWS = 30;
719666ea1b28a76aeba74744148b15099254d918671Owen Lin
720666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mCache maps from row number to the ImageBlock.
721666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final HashMap<Integer, ImageBlock> mCache;
722666ea1b28a76aeba74744148b15099254d918671Owen Lin
723666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are parameters set in the constructor.
724666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Handler mHandler;
725666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Runnable mRedrawCallback;  // Called after a row is loaded,
726666ea1b28a76aeba74744148b15099254d918671Owen Lin                                             // so GridViewSpecial can draw
727666ea1b28a76aeba74744148b15099254d918671Owen Lin                                             // again using the new images.
728666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final IImageList mImageList;
729666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final ImageLoader mLoader;
730666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final GridViewSpecial.DrawAdapter mDrawAdapter;
731666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final GridViewSpecial.LayoutSpec mSpec;
732666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mColumns;  // Columns per row.
733666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mBlockWidth;  // The width of an ImageBlock.
734666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Bitmap mOutline;  // The outline bitmap put on top of each
735666ea1b28a76aeba74744148b15099254d918671Owen Lin                                    // image.
736666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mCount;  // Cache mImageList.getCount().
737666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mRows;  // Cache (mCount + mColumns - 1) / mColumns
738666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mBlockHeight;  // The height of an ImageBlock.
739666ea1b28a76aeba74744148b15099254d918671Owen Lin
740666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Visible row range: [mStartRow, mEndRow). Set by setVisibleRows().
741666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mStartRow = 0;
742666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mEndRow = 0;
743666ea1b28a76aeba74744148b15099254d918671Owen Lin
744666ea1b28a76aeba74744148b15099254d918671Owen Lin    ImageBlockManager(Handler handler, Runnable redrawCallback,
745666ea1b28a76aeba74744148b15099254d918671Owen Lin            IImageList imageList, ImageLoader loader,
746666ea1b28a76aeba74744148b15099254d918671Owen Lin            GridViewSpecial.DrawAdapter adapter,
747666ea1b28a76aeba74744148b15099254d918671Owen Lin            GridViewSpecial.LayoutSpec spec,
748666ea1b28a76aeba74744148b15099254d918671Owen Lin            int columns, int blockWidth, Bitmap outline) {
749666ea1b28a76aeba74744148b15099254d918671Owen Lin        mHandler = handler;
750666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRedrawCallback = redrawCallback;
751666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageList = imageList;
752666ea1b28a76aeba74744148b15099254d918671Owen Lin        mLoader = loader;
753666ea1b28a76aeba74744148b15099254d918671Owen Lin        mDrawAdapter = adapter;
754666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSpec = spec;
755666ea1b28a76aeba74744148b15099254d918671Owen Lin        mColumns = columns;
756666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBlockWidth = blockWidth;
757666ea1b28a76aeba74744148b15099254d918671Owen Lin        mOutline = outline;
758666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBlockHeight = mSpec.mCellSpacing + mSpec.mCellHeight;
759666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCount = imageList.getCount();
760666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRows = (mCount + mColumns - 1) / mColumns;
761666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCache = new HashMap<Integer, ImageBlock>();
762666ea1b28a76aeba74744148b15099254d918671Owen Lin        mPendingRequest = 0;
763666ea1b28a76aeba74744148b15099254d918671Owen Lin        initGraphics();
764666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
765666ea1b28a76aeba74744148b15099254d918671Owen Lin
766666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Set the window of visible rows. Once set we will start to load them as
767666ea1b28a76aeba74744148b15099254d918671Owen Lin    // soon as possible (if they are not already in cache).
768666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setVisibleRows(int startRow, int endRow) {
769666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (startRow != mStartRow || endRow != mEndRow) {
770666ea1b28a76aeba74744148b15099254d918671Owen Lin            mStartRow = startRow;
771666ea1b28a76aeba74744148b15099254d918671Owen Lin            mEndRow = endRow;
772666ea1b28a76aeba74744148b15099254d918671Owen Lin            startLoading();
773666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
774666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
775666ea1b28a76aeba74744148b15099254d918671Owen Lin
776666ea1b28a76aeba74744148b15099254d918671Owen Lin    int mPendingRequest;  // Number of pending requests (sent to ImageLoader).
777666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We want to keep enough requests in ImageLoader's queue, but not too
778666ea1b28a76aeba74744148b15099254d918671Owen Lin    // many.
779666ea1b28a76aeba74744148b15099254d918671Owen Lin    static final int REQUESTS_LOW = 3;
780666ea1b28a76aeba74744148b15099254d918671Owen Lin    static final int REQUESTS_HIGH = 6;
781666ea1b28a76aeba74744148b15099254d918671Owen Lin
782666ea1b28a76aeba74744148b15099254d918671Owen Lin    // After clear requests currently in queue, start loading the thumbnails.
783666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We need to clear the queue first because the proper order of loading
784666ea1b28a76aeba74744148b15099254d918671Owen Lin    // may have changed (because the visible region changed, or some images
785666ea1b28a76aeba74744148b15099254d918671Owen Lin    // have been invalidated).
786666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void startLoading() {
787666ea1b28a76aeba74744148b15099254d918671Owen Lin        clearLoaderQueue();
788666ea1b28a76aeba74744148b15099254d918671Owen Lin        continueLoading();
789666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
790666ea1b28a76aeba74744148b15099254d918671Owen Lin
791666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void clearLoaderQueue() {
792666ea1b28a76aeba74744148b15099254d918671Owen Lin        int[] tags = mLoader.clearQueue();
793666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int pos : tags) {
794666ea1b28a76aeba74744148b15099254d918671Owen Lin            int row = pos / mColumns;
795666ea1b28a76aeba74744148b15099254d918671Owen Lin            int col = pos - row * mColumns;
796666ea1b28a76aeba74744148b15099254d918671Owen Lin            ImageBlock blk = mCache.get(row);
797666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert(blk != null);  // We won't reuse the block if it has pending
798666ea1b28a76aeba74744148b15099254d918671Owen Lin                                  // requests. See getEmptyBlock().
799666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.cancelRequest(col);
800666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
801666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
802666ea1b28a76aeba74744148b15099254d918671Owen Lin
803666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Scan the cache and send requests to ImageLoader if needed.
804666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void continueLoading() {
805666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Check if we still have enough requests in the queue.
806666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mPendingRequest >= REQUESTS_LOW) return;
807666ea1b28a76aeba74744148b15099254d918671Owen Lin
808666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Scan the visible rows.
809666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = mStartRow; i < mEndRow; i++) {
810666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (scanOne(i)) return;
811666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
812666ea1b28a76aeba74744148b15099254d918671Owen Lin
813666ea1b28a76aeba74744148b15099254d918671Owen Lin        int range = (CACHE_ROWS - (mEndRow - mStartRow)) / 2;
814666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Scan other rows.
815666ea1b28a76aeba74744148b15099254d918671Owen Lin        // d is the distance between the row and visible region.
816666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int d = 1; d <= range; d++) {
817666ea1b28a76aeba74744148b15099254d918671Owen Lin            int after = mEndRow - 1 + d;
818666ea1b28a76aeba74744148b15099254d918671Owen Lin            int before = mStartRow - d;
819666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (after >= mRows && before < 0) {
820666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;  // Nothing more the scan.
821666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
822666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (after < mRows && scanOne(after)) return;
823666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (before >= 0 && scanOne(before)) return;
824666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
825666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
826666ea1b28a76aeba74744148b15099254d918671Owen Lin
827666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Returns true if we can stop scanning.
828666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean scanOne(int i) {
829666ea1b28a76aeba74744148b15099254d918671Owen Lin        mPendingRequest += tryToLoad(i);
830666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mPendingRequest >= REQUESTS_HIGH;
831666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
832666ea1b28a76aeba74744148b15099254d918671Owen Lin
833666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Returns number of requests we issued for this row.
834666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int tryToLoad(int row) {
835666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(row >= 0 && row < mRows);
836666ea1b28a76aeba74744148b15099254d918671Owen Lin        ImageBlock blk = mCache.get(row);
837666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (blk == null) {
838666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Find an empty block
839666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk = getEmptyBlock();
840666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.setRow(row);
841666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.invalidate();
842666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCache.put(row, blk);
843666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
844666ea1b28a76aeba74744148b15099254d918671Owen Lin        return blk.loadImages();
845666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
846666ea1b28a76aeba74744148b15099254d918671Owen Lin
847666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Get an empty block for the cache.
848666ea1b28a76aeba74744148b15099254d918671Owen Lin    private ImageBlock getEmptyBlock() {
849666ea1b28a76aeba74744148b15099254d918671Owen Lin        // See if we can allocate a new block.
850666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCache.size() < CACHE_ROWS) {
851666ea1b28a76aeba74744148b15099254d918671Owen Lin            return new ImageBlock();
852666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
853666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Reclaim the old block with largest distance from the visible region.
854666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bestDistance = -1;
855666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bestIndex = -1;
856666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int index : mCache.keySet()) {
857666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Make sure we don't reclaim a block which still has pending
858666ea1b28a76aeba74744148b15099254d918671Owen Lin            // request.
859666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mCache.get(index).hasPendingRequests()) {
860666ea1b28a76aeba74744148b15099254d918671Owen Lin                continue;
861666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
862666ea1b28a76aeba74744148b15099254d918671Owen Lin            int dist = 0;
863666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (index >= mEndRow) {
864666ea1b28a76aeba74744148b15099254d918671Owen Lin                dist = index - mEndRow + 1;
865666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else if (index < mStartRow) {
866666ea1b28a76aeba74744148b15099254d918671Owen Lin                dist = mStartRow - index;
867666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
868666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Inside the visible region.
869666ea1b28a76aeba74744148b15099254d918671Owen Lin                continue;
870666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
871666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (dist > bestDistance) {
872666ea1b28a76aeba74744148b15099254d918671Owen Lin                bestDistance = dist;
873666ea1b28a76aeba74744148b15099254d918671Owen Lin                bestIndex = index;
874666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
875666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
876666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mCache.remove(bestIndex);
877666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
878666ea1b28a76aeba74744148b15099254d918671Owen Lin
879666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void invalidateImage(int index) {
880666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = index / mColumns;
881666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = index - (row * mColumns);
882666ea1b28a76aeba74744148b15099254d918671Owen Lin        ImageBlock blk = mCache.get(row);
883666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (blk == null) return;
884666ea1b28a76aeba74744148b15099254d918671Owen Lin        if ((blk.mCompletedMask & (1 << col)) != 0) {
885666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.mCompletedMask &= ~(1 << col);
886666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
887666ea1b28a76aeba74744148b15099254d918671Owen Lin        startLoading();
888666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
889666ea1b28a76aeba74744148b15099254d918671Owen Lin
890666ea1b28a76aeba74744148b15099254d918671Owen Lin    // After calling recycle(), the instance should not be used anymore.
891666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void recycle() {
892666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (ImageBlock blk : mCache.values()) {
893666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.recycle();
894666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
895666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCache.clear();
896666ea1b28a76aeba74744148b15099254d918671Owen Lin        mEmptyBitmap.recycle();
897666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
898666ea1b28a76aeba74744148b15099254d918671Owen Lin
899666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Draw the images to the given canvas.
900666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void doDraw(Canvas canvas, int thisWidth, int thisHeight,
901666ea1b28a76aeba74744148b15099254d918671Owen Lin            int scrollPos) {
902666ea1b28a76aeba74744148b15099254d918671Owen Lin        final int height = mBlockHeight;
903666ea1b28a76aeba74744148b15099254d918671Owen Lin
904666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Note that currentBlock could be negative.
905666ea1b28a76aeba74744148b15099254d918671Owen Lin        int currentBlock = (scrollPos < 0)
906666ea1b28a76aeba74744148b15099254d918671Owen Lin                ? ((scrollPos - height + 1) / height)
907666ea1b28a76aeba74744148b15099254d918671Owen Lin                : (scrollPos / height);
908666ea1b28a76aeba74744148b15099254d918671Owen Lin
909666ea1b28a76aeba74744148b15099254d918671Owen Lin        while (true) {
910666ea1b28a76aeba74744148b15099254d918671Owen Lin            final int yPos = currentBlock * height;
911666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (yPos >= scrollPos + thisHeight) {
912666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;
913666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
914666ea1b28a76aeba74744148b15099254d918671Owen Lin
915666ea1b28a76aeba74744148b15099254d918671Owen Lin            ImageBlock blk = mCache.get(currentBlock);
916666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (blk != null) {
917666ea1b28a76aeba74744148b15099254d918671Owen Lin                blk.doDraw(canvas, 0, yPos);
918666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
919666ea1b28a76aeba74744148b15099254d918671Owen Lin                drawEmptyBlock(canvas, 0, yPos, currentBlock);
920666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
921666ea1b28a76aeba74744148b15099254d918671Owen Lin
922666ea1b28a76aeba74744148b15099254d918671Owen Lin            currentBlock += 1;
923666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
924666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
925666ea1b28a76aeba74744148b15099254d918671Owen Lin
926666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Return number of columns in the given row. (This could be less than
927666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mColumns for the last row).
928666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int numColumns(int row) {
929666ea1b28a76aeba74744148b15099254d918671Owen Lin        return Math.min(mColumns, mCount - row * mColumns);
930666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
931666ea1b28a76aeba74744148b15099254d918671Owen Lin
932666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Draw a block which has not been loaded.
933666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void drawEmptyBlock(Canvas canvas, int xPos, int yPos, int row) {
934666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the background.
935666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawRect(xPos, yPos, xPos + mBlockWidth, yPos + mBlockHeight,
936666ea1b28a76aeba74744148b15099254d918671Owen Lin                mBackgroundPaint);
937666ea1b28a76aeba74744148b15099254d918671Owen Lin
938666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the empty images.
939666ea1b28a76aeba74744148b15099254d918671Owen Lin        int x = xPos + mSpec.mLeftEdgePadding;
940666ea1b28a76aeba74744148b15099254d918671Owen Lin        int y = yPos + mSpec.mCellSpacing;
941666ea1b28a76aeba74744148b15099254d918671Owen Lin        int cols = numColumns(row);
942666ea1b28a76aeba74744148b15099254d918671Owen Lin
943666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = 0; i < cols; i++) {
944666ea1b28a76aeba74744148b15099254d918671Owen Lin            canvas.drawBitmap(mEmptyBitmap, x, y, null);
945666ea1b28a76aeba74744148b15099254d918671Owen Lin            x += (mSpec.mCellWidth + mSpec.mCellSpacing);
946666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
947666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
948666ea1b28a76aeba74744148b15099254d918671Owen Lin
949666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mEmptyBitmap is what we draw if we the wanted block hasn't been loaded.
950666ea1b28a76aeba74744148b15099254d918671Owen Lin    // (If the user scrolls too fast). It is a gray image with normal outline.
951666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mBackgroundPaint is used to draw the (black) background outside
952666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mEmptyBitmap.
953666ea1b28a76aeba74744148b15099254d918671Owen Lin    Paint mBackgroundPaint;
954666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Bitmap mEmptyBitmap;
955666ea1b28a76aeba74744148b15099254d918671Owen Lin
956666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void initGraphics() {
957666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBackgroundPaint = new Paint();
958666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBackgroundPaint.setStyle(Paint.Style.FILL);
959666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBackgroundPaint.setColor(0xFF000000);  // black
960666ea1b28a76aeba74744148b15099254d918671Owen Lin        mEmptyBitmap = Bitmap.createBitmap(mSpec.mCellWidth, mSpec.mCellHeight,
961666ea1b28a76aeba74744148b15099254d918671Owen Lin                Bitmap.Config.RGB_565);
962666ea1b28a76aeba74744148b15099254d918671Owen Lin        Canvas canvas = new Canvas(mEmptyBitmap);
963666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawRGB(0xDD, 0xDD, 0xDD);
964666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawBitmap(mOutline, 0, 0, null);
965666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
966666ea1b28a76aeba74744148b15099254d918671Owen Lin
967666ea1b28a76aeba74744148b15099254d918671Owen Lin    // ImageBlock stores bitmap for one row. The loaded thumbnail images are
968666ea1b28a76aeba74744148b15099254d918671Owen Lin    // drawn to mBitmap. mBitmap is later used in onDraw() of GridViewSpecial.
969666ea1b28a76aeba74744148b15099254d918671Owen Lin    private class ImageBlock {
970666ea1b28a76aeba74744148b15099254d918671Owen Lin        private Bitmap mBitmap;
971666ea1b28a76aeba74744148b15099254d918671Owen Lin        private final Canvas mCanvas;
972666ea1b28a76aeba74744148b15099254d918671Owen Lin
973666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Columns which have been requested to the loader
974666ea1b28a76aeba74744148b15099254d918671Owen Lin        private int mRequestedMask;
975666ea1b28a76aeba74744148b15099254d918671Owen Lin
976666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Columns which have been completed from the loader
977666ea1b28a76aeba74744148b15099254d918671Owen Lin        private int mCompletedMask;
978666ea1b28a76aeba74744148b15099254d918671Owen Lin
979666ea1b28a76aeba74744148b15099254d918671Owen Lin        // The row number this block represents.
980666ea1b28a76aeba74744148b15099254d918671Owen Lin        private int mRow;
981666ea1b28a76aeba74744148b15099254d918671Owen Lin
982666ea1b28a76aeba74744148b15099254d918671Owen Lin        public ImageBlock() {
983666ea1b28a76aeba74744148b15099254d918671Owen Lin            mBitmap = Bitmap.createBitmap(mBlockWidth, mBlockHeight,
984666ea1b28a76aeba74744148b15099254d918671Owen Lin                    Bitmap.Config.RGB_565);
985666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCanvas = new Canvas(mBitmap);
986666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRow = -1;
987666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
988666ea1b28a76aeba74744148b15099254d918671Owen Lin
989666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void setRow(int row) {
990666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRow = row;
991666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
992666ea1b28a76aeba74744148b15099254d918671Owen Lin
993666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void invalidate() {
994666ea1b28a76aeba74744148b15099254d918671Owen Lin            // We do not change mRequestedMask or do cancelAllRequests()
995666ea1b28a76aeba74744148b15099254d918671Owen Lin            // because the data coming from pending requests are valid. (We only
996666ea1b28a76aeba74744148b15099254d918671Owen Lin            // invalidate data which has been drawn to the bitmap).
997666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCompletedMask = 0;
998666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
999666ea1b28a76aeba74744148b15099254d918671Owen Lin
1000666ea1b28a76aeba74744148b15099254d918671Owen Lin        // After recycle, the ImageBlock instance should not be accessed.
1001666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void recycle() {
1002666ea1b28a76aeba74744148b15099254d918671Owen Lin            cancelAllRequests();
1003666ea1b28a76aeba74744148b15099254d918671Owen Lin            mBitmap.recycle();
1004666ea1b28a76aeba74744148b15099254d918671Owen Lin            mBitmap = null;
1005666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1006666ea1b28a76aeba74744148b15099254d918671Owen Lin
1007666ea1b28a76aeba74744148b15099254d918671Owen Lin        private boolean isVisible() {
1008666ea1b28a76aeba74744148b15099254d918671Owen Lin            return mRow >= mStartRow && mRow < mEndRow;
1009666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1010666ea1b28a76aeba74744148b15099254d918671Owen Lin
1011666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Returns number of requests submitted to ImageLoader.
1012666ea1b28a76aeba74744148b15099254d918671Owen Lin        public int loadImages() {
1013666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert(mRow != -1);
1014666ea1b28a76aeba74744148b15099254d918671Owen Lin
1015666ea1b28a76aeba74744148b15099254d918671Owen Lin            int columns = numColumns(mRow);
1016666ea1b28a76aeba74744148b15099254d918671Owen Lin
1017666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Calculate what we need.
1018666ea1b28a76aeba74744148b15099254d918671Owen Lin            int needMask = ((1 << columns) - 1)
1019666ea1b28a76aeba74744148b15099254d918671Owen Lin                    & ~(mCompletedMask | mRequestedMask);
1020666ea1b28a76aeba74744148b15099254d918671Owen Lin
1021666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (needMask == 0) {
1022666ea1b28a76aeba74744148b15099254d918671Owen Lin                return 0;
1023666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1024666ea1b28a76aeba74744148b15099254d918671Owen Lin
1025666ea1b28a76aeba74744148b15099254d918671Owen Lin            int retVal = 0;
1026666ea1b28a76aeba74744148b15099254d918671Owen Lin            int base = mRow * mColumns;
1027666ea1b28a76aeba74744148b15099254d918671Owen Lin
1028666ea1b28a76aeba74744148b15099254d918671Owen Lin            for (int col = 0; col < columns; col++) {
1029666ea1b28a76aeba74744148b15099254d918671Owen Lin                if ((needMask & (1 << col)) == 0) {
1030666ea1b28a76aeba74744148b15099254d918671Owen Lin                    continue;
1031666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1032666ea1b28a76aeba74744148b15099254d918671Owen Lin
1033666ea1b28a76aeba74744148b15099254d918671Owen Lin                int pos = base + col;
1034666ea1b28a76aeba74744148b15099254d918671Owen Lin
1035666ea1b28a76aeba74744148b15099254d918671Owen Lin                final IImage image = mImageList.getImageAt(pos);
1036666ea1b28a76aeba74744148b15099254d918671Owen Lin                if (image != null) {
1037666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // This callback is passed to ImageLoader. It will invoke
1038666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // loadImageDone() in the main thread. We limit the callback
1039666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // thread to be in this very short function. All other
1040666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // processing is done in the main thread.
1041666ea1b28a76aeba74744148b15099254d918671Owen Lin                    final int colFinal = col;
1042666ea1b28a76aeba74744148b15099254d918671Owen Lin                    ImageLoader.LoadedCallback cb =
1043666ea1b28a76aeba74744148b15099254d918671Owen Lin                            new ImageLoader.LoadedCallback() {
1044666ea1b28a76aeba74744148b15099254d918671Owen Lin                                    public void run(final Bitmap b) {
1045666ea1b28a76aeba74744148b15099254d918671Owen Lin                                        mHandler.post(new Runnable() {
1046666ea1b28a76aeba74744148b15099254d918671Owen Lin                                            public void run() {
1047666ea1b28a76aeba74744148b15099254d918671Owen Lin                                                loadImageDone(image, b,
1048666ea1b28a76aeba74744148b15099254d918671Owen Lin                                                        colFinal);
1049666ea1b28a76aeba74744148b15099254d918671Owen Lin                                            }
1050666ea1b28a76aeba74744148b15099254d918671Owen Lin                                        });
1051666ea1b28a76aeba74744148b15099254d918671Owen Lin                                    }
1052666ea1b28a76aeba74744148b15099254d918671Owen Lin                                };
1053666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // Load Image
1054666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mLoader.getBitmap(image, cb, pos);
1055666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mRequestedMask |= (1 << col);
1056666ea1b28a76aeba74744148b15099254d918671Owen Lin                    retVal += 1;
1057666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1058666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1059666ea1b28a76aeba74744148b15099254d918671Owen Lin
1060666ea1b28a76aeba74744148b15099254d918671Owen Lin            return retVal;
1061666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1062666ea1b28a76aeba74744148b15099254d918671Owen Lin
1063666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Whether this block has pending requests.
1064666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean hasPendingRequests() {
1065666ea1b28a76aeba74744148b15099254d918671Owen Lin            return mRequestedMask != 0;
1066666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1067666ea1b28a76aeba74744148b15099254d918671Owen Lin
1068666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Called when an image is loaded.
1069666ea1b28a76aeba74744148b15099254d918671Owen Lin        private void loadImageDone(IImage image, Bitmap b,
1070666ea1b28a76aeba74744148b15099254d918671Owen Lin                int col) {
1071666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mBitmap == null) return;  // This block has been recycled.
1072666ea1b28a76aeba74744148b15099254d918671Owen Lin
1073666ea1b28a76aeba74744148b15099254d918671Owen Lin            int spacing = mSpec.mCellSpacing;
1074666ea1b28a76aeba74744148b15099254d918671Owen Lin            int leftSpacing = mSpec.mLeftEdgePadding;
1075666ea1b28a76aeba74744148b15099254d918671Owen Lin            final int yPos = spacing;
1076666ea1b28a76aeba74744148b15099254d918671Owen Lin            final int xPos = leftSpacing
1077666ea1b28a76aeba74744148b15099254d918671Owen Lin                    + (col * (mSpec.mCellWidth + spacing));
1078666ea1b28a76aeba74744148b15099254d918671Owen Lin
1079666ea1b28a76aeba74744148b15099254d918671Owen Lin            drawBitmap(image, b, xPos, yPos);
1080666ea1b28a76aeba74744148b15099254d918671Owen Lin
1081666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (b != null) {
1082666ea1b28a76aeba74744148b15099254d918671Owen Lin                b.recycle();
1083666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1084666ea1b28a76aeba74744148b15099254d918671Owen Lin
1085666ea1b28a76aeba74744148b15099254d918671Owen Lin            int mask = (1 << col);
1086666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert((mCompletedMask & mask) == 0);
1087666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert((mRequestedMask & mask) != 0);
1088666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRequestedMask &= ~mask;
1089666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCompletedMask |= mask;
1090666ea1b28a76aeba74744148b15099254d918671Owen Lin            mPendingRequest--;
1091666ea1b28a76aeba74744148b15099254d918671Owen Lin
1092666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (isVisible()) {
1093666ea1b28a76aeba74744148b15099254d918671Owen Lin                mRedrawCallback.run();
1094666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1095666ea1b28a76aeba74744148b15099254d918671Owen Lin
1096666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Kick start next block loading.
1097666ea1b28a76aeba74744148b15099254d918671Owen Lin            continueLoading();
1098666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1099666ea1b28a76aeba74744148b15099254d918671Owen Lin
1100666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the loaded bitmap to the block bitmap.
1101666ea1b28a76aeba74744148b15099254d918671Owen Lin        private void drawBitmap(
1102666ea1b28a76aeba74744148b15099254d918671Owen Lin                IImage image, Bitmap b, int xPos, int yPos) {
1103666ea1b28a76aeba74744148b15099254d918671Owen Lin            mDrawAdapter.drawImage(mCanvas, image, b, xPos, yPos,
1104666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mSpec.mCellWidth, mSpec.mCellHeight);
1105666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCanvas.drawBitmap(mOutline, xPos, yPos, null);
1106666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1107666ea1b28a76aeba74744148b15099254d918671Owen Lin
1108666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the block bitmap to the specified canvas.
1109666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void doDraw(Canvas canvas, int xPos, int yPos) {
1110666ea1b28a76aeba74744148b15099254d918671Owen Lin            int cols = numColumns(mRow);
1111666ea1b28a76aeba74744148b15099254d918671Owen Lin
1112666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (cols == mColumns) {
1113666ea1b28a76aeba74744148b15099254d918671Owen Lin                canvas.drawBitmap(mBitmap, xPos, yPos, null);
1114666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
1115666ea1b28a76aeba74744148b15099254d918671Owen Lin
1116666ea1b28a76aeba74744148b15099254d918671Owen Lin                // This must be the last row -- we draw only part of the block.
1117666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Draw the background.
1118666ea1b28a76aeba74744148b15099254d918671Owen Lin                canvas.drawRect(xPos, yPos, xPos + mBlockWidth,
1119666ea1b28a76aeba74744148b15099254d918671Owen Lin                        yPos + mBlockHeight, mBackgroundPaint);
1120666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Draw part of the block.
1121666ea1b28a76aeba74744148b15099254d918671Owen Lin                int w = mSpec.mLeftEdgePadding
1122666ea1b28a76aeba74744148b15099254d918671Owen Lin                        + cols * (mSpec.mCellWidth + mSpec.mCellSpacing);
1123666ea1b28a76aeba74744148b15099254d918671Owen Lin                Rect srcRect = new Rect(0, 0, w, mBlockHeight);
1124666ea1b28a76aeba74744148b15099254d918671Owen Lin                Rect dstRect = new Rect(srcRect);
1125666ea1b28a76aeba74744148b15099254d918671Owen Lin                dstRect.offset(xPos, yPos);
1126666ea1b28a76aeba74744148b15099254d918671Owen Lin                canvas.drawBitmap(mBitmap, srcRect, dstRect, null);
1127666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1128666ea1b28a76aeba74744148b15099254d918671Owen Lin
1129666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Draw the part which has not been loaded.
1130666ea1b28a76aeba74744148b15099254d918671Owen Lin            int isEmpty = ((1 << cols) - 1) & ~mCompletedMask;
1131666ea1b28a76aeba74744148b15099254d918671Owen Lin
1132666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (isEmpty != 0) {
1133666ea1b28a76aeba74744148b15099254d918671Owen Lin                int x = xPos + mSpec.mLeftEdgePadding;
1134666ea1b28a76aeba74744148b15099254d918671Owen Lin                int y = yPos + mSpec.mCellSpacing;
1135666ea1b28a76aeba74744148b15099254d918671Owen Lin
1136666ea1b28a76aeba74744148b15099254d918671Owen Lin                for (int i = 0; i < cols; i++) {
1137666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if ((isEmpty & (1 << i)) != 0) {
1138666ea1b28a76aeba74744148b15099254d918671Owen Lin                        canvas.drawBitmap(mEmptyBitmap, x, y, null);
1139666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
1140666ea1b28a76aeba74744148b15099254d918671Owen Lin                    x += (mSpec.mCellWidth + mSpec.mCellSpacing);
1141666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1142666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1143666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1144666ea1b28a76aeba74744148b15099254d918671Owen Lin
1145666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Mark a request as cancelled. The request has already been removed
1146666ea1b28a76aeba74744148b15099254d918671Owen Lin        // from the queue of ImageLoader, so we only need to mark the fact.
1147666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void cancelRequest(int col) {
1148666ea1b28a76aeba74744148b15099254d918671Owen Lin            int mask = (1 << col);
1149666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert((mRequestedMask & mask) != 0);
1150666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRequestedMask &= ~mask;
1151666ea1b28a76aeba74744148b15099254d918671Owen Lin            mPendingRequest--;
1152666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1153666ea1b28a76aeba74744148b15099254d918671Owen Lin
1154666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Try to cancel all pending requests for this block. After this
1155666ea1b28a76aeba74744148b15099254d918671Owen Lin        // completes there could still be requests not cancelled (because it is
1156666ea1b28a76aeba74744148b15099254d918671Owen Lin        // already in progress). We deal with that situation by setting mBitmap
1157666ea1b28a76aeba74744148b15099254d918671Owen Lin        // to null in recycle() and check this in loadImageDone().
1158666ea1b28a76aeba74744148b15099254d918671Owen Lin        private void cancelAllRequests() {
1159666ea1b28a76aeba74744148b15099254d918671Owen Lin            for (int i = 0; i < mColumns; i++) {
1160666ea1b28a76aeba74744148b15099254d918671Owen Lin                int mask = (1 << i);
1161666ea1b28a76aeba74744148b15099254d918671Owen Lin                if ((mRequestedMask & mask) != 0) {
1162666ea1b28a76aeba74744148b15099254d918671Owen Lin                    int pos = (mRow * mColumns) + i;
1163666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (mLoader.cancel(mImageList.getImageAt(pos))) {
1164666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mRequestedMask &= ~mask;
1165666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mPendingRequest--;
1166666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
1167666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1168666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1169666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1170666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
1171666ea1b28a76aeba74744148b15099254d918671Owen Lin}
1172