1package com.android.mail.bitmap;
2
3import android.graphics.Canvas;
4import android.graphics.ColorFilter;
5import android.graphics.PixelFormat;
6import android.graphics.Rect;
7import android.graphics.drawable.Drawable;
8
9import com.android.bitmap.Trace;
10
11import java.util.ArrayList;
12import java.util.List;
13
14/**
15 * A drawable that contains up to 4 other smaller drawables in a regular grid. This class attempts
16 * to reuse inner drawables when feasible to promote object reuse. This design goal makes the API
17 * to reuse an instance a little awkward: you must first zero out the count, then set it to a new
18 * value, then populate the entries with {@link #getOrCreateDrawable(int)} and a drawable subclass
19 * bind() method of your design.
20 */
21public abstract class CompositeDrawable<T extends Drawable> extends Drawable
22        implements Drawable.Callback {
23
24    protected final List<T> mDrawables;
25    protected int mCount;
26
27    public CompositeDrawable(int maxDivisions) {
28        if (maxDivisions >= 4) {
29            throw new IllegalArgumentException("CompositeDrawable only supports 4 divisions");
30        }
31        mDrawables = new ArrayList<T>(maxDivisions);
32        for (int i = 0; i < maxDivisions; i++) {
33            mDrawables.add(i, null);
34        }
35        mCount = 0;
36    }
37
38    protected abstract T createDivisionDrawable();
39
40    public void setCount(int count) {
41        // zero out the composite bounds, which will propagate to the division drawables
42        // this invalidates any old division bounds, which may change with the count
43        setBounds(0, 0, 0, 0);
44        mCount = count;
45    }
46
47    public int getCount() {
48        return mCount;
49    }
50
51    public T getOrCreateDrawable(int i) {
52        if (i >= mCount) {
53            throw new IllegalArgumentException("bad index: " + i);
54        }
55
56        T result = mDrawables.get(i);
57        if (result == null) {
58            Trace.beginSection("create division drawable");
59            result = createDivisionDrawable();
60            mDrawables.set(i, result);
61            result.setCallback(this);
62            // Make sure drawables created after the bounds were already set have their bounds
63            // set initially (the other unaffected drawables basically de-bounce this).
64            onBoundsChange(getBounds());
65            Trace.endSection();
66        }
67        return result;
68    }
69
70    @Override
71    protected void onBoundsChange(Rect bounds) {
72        final int w = bounds.width();
73        final int h = bounds.height();
74        final int mw = w / 2;
75        final int mh = h / 2;
76        switch (mCount) {
77            case 1:
78                // 1 bitmap: passthrough
79                applyBounds(0, 0, 0, w, h);
80                break;
81            case 2:
82                // 2 bitmaps split vertically
83                applyBounds(0, 0, 0, mw, h);
84                applyBounds(1, mw, 0, w, h);
85                break;
86            case 3:
87                // 1st is tall on the left, 2nd/3rd stacked vertically on the right
88                applyBounds(0, 0, 0, mw, h);
89                applyBounds(1, mw, 0, w, mh);
90                applyBounds(2, mw, mh, w, h);
91                break;
92            case 4:
93                // 4 bitmaps in a 2x2 grid
94                applyBounds(0, 0, 0, mw, mh);
95                applyBounds(1, mw, 0, w, mh);
96                applyBounds(2, 0, mh, mw, h);
97                applyBounds(3, mw, mh, w, h);
98                break;
99        }
100    }
101
102    private void applyBounds(int index, int left, int top, int right, int bottom) {
103        final T d = mDrawables.get(index);
104        if (d == null) {
105            return;
106        }
107        d.setBounds(left, top, right, bottom);
108    }
109
110    @Override
111    public void draw(Canvas canvas) {
112        for (int i = 0; i < mCount; i++) {
113            mDrawables.get(i).draw(canvas);
114        }
115    }
116
117    @Override
118    public void setAlpha(int alpha) {
119        for (int i = 0; i < mCount; i++) {
120            mDrawables.get(i).setAlpha(alpha);
121        }
122    }
123
124    @Override
125    public void setColorFilter(ColorFilter cf) {
126        for (int i = 0; i < mCount; i++) {
127            mDrawables.get(i).setColorFilter(cf);
128        }
129    }
130
131    @Override
132    public int getOpacity() {
133        int opacity = PixelFormat.OPAQUE;
134        for (int i = 0; i < mCount; i++) {
135            if (mDrawables.get(i).getOpacity() != PixelFormat.OPAQUE) {
136                opacity = PixelFormat.TRANSLUCENT;
137                break;
138            }
139        }
140        return opacity;
141    }
142
143    @Override
144    public void invalidateDrawable(Drawable who) {
145        invalidateSelf();
146    }
147
148    @Override
149    public void scheduleDrawable(Drawable who, Runnable what, long when) {
150        scheduleSelf(what, when);
151    }
152
153    @Override
154    public void unscheduleDrawable(Drawable who, Runnable what) {
155        unscheduleSelf(what);
156    }
157
158}
159