1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics.drawable;
18
19import android.content.res.Resources;
20import android.graphics.*;
21
22public class DrawableContainer extends Drawable implements Drawable.Callback {
23
24    /**
25     * To be proper, we should have a getter for dither (and alpha, etc.)
26     * so that proxy classes like this can save/restore their delegates'
27     * values, but we don't have getters. Since we do have setters
28     * (e.g. setDither), which this proxy forwards on, we have to have some
29     * default/initial setting.
30     *
31     * The initial setting for dither is now true, since it almost always seems
32     * to improve the quality at negligible cost.
33     */
34    private static final boolean DEFAULT_DITHER = true;
35    private DrawableContainerState mDrawableContainerState;
36    private Drawable mCurrDrawable;
37    private int mAlpha = 0xFF;
38    private ColorFilter mColorFilter;
39
40    private int mCurIndex = -1;
41    private boolean mMutated;
42
43    // overrides from Drawable
44
45    @Override
46    public void draw(Canvas canvas) {
47        if (mCurrDrawable != null) {
48            mCurrDrawable.draw(canvas);
49        }
50    }
51
52    @Override
53    public int getChangingConfigurations() {
54        return super.getChangingConfigurations()
55                | mDrawableContainerState.mChangingConfigurations
56                | mDrawableContainerState.mChildrenChangingConfigurations;
57    }
58
59    @Override
60    public boolean getPadding(Rect padding) {
61        final Rect r = mDrawableContainerState.getConstantPadding();
62        if (r != null) {
63            padding.set(r);
64            return true;
65        }
66        if (mCurrDrawable != null) {
67            return mCurrDrawable.getPadding(padding);
68        } else {
69            return super.getPadding(padding);
70        }
71    }
72
73    @Override
74    public void setAlpha(int alpha) {
75        if (mAlpha != alpha) {
76            mAlpha = alpha;
77            if (mCurrDrawable != null) {
78                mCurrDrawable.setAlpha(alpha);
79            }
80        }
81    }
82
83    @Override
84    public void setDither(boolean dither) {
85        if (mDrawableContainerState.mDither != dither) {
86            mDrawableContainerState.mDither = dither;
87            if (mCurrDrawable != null) {
88                mCurrDrawable.setDither(mDrawableContainerState.mDither);
89            }
90        }
91    }
92
93    @Override
94    public void setColorFilter(ColorFilter cf) {
95        if (mColorFilter != cf) {
96            mColorFilter = cf;
97            if (mCurrDrawable != null) {
98                mCurrDrawable.setColorFilter(cf);
99            }
100        }
101    }
102
103    @Override
104    protected void onBoundsChange(Rect bounds) {
105        if (mCurrDrawable != null) {
106            mCurrDrawable.setBounds(bounds);
107        }
108    }
109
110    @Override
111    public boolean isStateful() {
112        return mDrawableContainerState.isStateful();
113    }
114
115    @Override
116    protected boolean onStateChange(int[] state) {
117        if (mCurrDrawable != null) {
118            return mCurrDrawable.setState(state);
119        }
120        return false;
121    }
122
123    @Override
124    protected boolean onLevelChange(int level) {
125        if (mCurrDrawable != null) {
126            return mCurrDrawable.setLevel(level);
127        }
128        return false;
129    }
130
131    @Override
132    public int getIntrinsicWidth() {
133        if (mDrawableContainerState.isConstantSize()) {
134            return mDrawableContainerState.getConstantWidth();
135        }
136        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
137    }
138
139    @Override
140    public int getIntrinsicHeight() {
141        if (mDrawableContainerState.isConstantSize()) {
142            return mDrawableContainerState.getConstantHeight();
143        }
144        return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
145    }
146
147    @Override
148    public int getMinimumWidth() {
149        if (mDrawableContainerState.isConstantSize()) {
150            return mDrawableContainerState.getConstantMinimumWidth();
151        }
152        return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
153    }
154
155    @Override
156    public int getMinimumHeight() {
157        if (mDrawableContainerState.isConstantSize()) {
158            return mDrawableContainerState.getConstantMinimumHeight();
159        }
160        return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
161    }
162
163    public void invalidateDrawable(Drawable who)
164    {
165        if (who == mCurrDrawable && mCallback != null) {
166            mCallback.invalidateDrawable(this);
167        }
168    }
169
170    public void scheduleDrawable(Drawable who, Runnable what, long when)
171    {
172        if (who == mCurrDrawable && mCallback != null) {
173            mCallback.scheduleDrawable(this, what, when);
174        }
175    }
176
177    public void unscheduleDrawable(Drawable who, Runnable what)
178    {
179        if (who == mCurrDrawable && mCallback != null) {
180            mCallback.unscheduleDrawable(this, what);
181        }
182    }
183
184    @Override
185    public boolean setVisible(boolean visible, boolean restart) {
186        boolean changed = super.setVisible(visible, restart);
187        if (mCurrDrawable != null) {
188            mCurrDrawable.setVisible(visible, restart);
189        }
190        return changed;
191    }
192
193    @Override
194    public int getOpacity() {
195        return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
196                mDrawableContainerState.getOpacity();
197    }
198
199    public boolean selectDrawable(int idx)
200    {
201        if (idx == mCurIndex) {
202            return false;
203        }
204        if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
205            Drawable d = mDrawableContainerState.mDrawables[idx];
206            if (mCurrDrawable != null) {
207                mCurrDrawable.setVisible(false, false);
208            }
209            mCurrDrawable = d;
210            mCurIndex = idx;
211            if (d != null) {
212                d.setVisible(isVisible(), true);
213                d.setAlpha(mAlpha);
214                d.setDither(mDrawableContainerState.mDither);
215                d.setColorFilter(mColorFilter);
216                d.setState(getState());
217                d.setLevel(getLevel());
218                d.setBounds(getBounds());
219            }
220        } else {
221            if (mCurrDrawable != null) {
222                mCurrDrawable.setVisible(false, false);
223            }
224            mCurrDrawable = null;
225            mCurIndex = -1;
226        }
227        invalidateSelf();
228        return true;
229    }
230
231    @Override
232    public Drawable getCurrent() {
233        return mCurrDrawable;
234    }
235
236    @Override
237    public ConstantState getConstantState() {
238        if (mDrawableContainerState.canConstantState()) {
239            mDrawableContainerState.mChangingConfigurations = super.getChangingConfigurations();
240            return mDrawableContainerState;
241        }
242        return null;
243    }
244
245    @Override
246    public Drawable mutate() {
247        if (!mMutated && super.mutate() == this) {
248            final int N = mDrawableContainerState.getChildCount();
249            final Drawable[] drawables = mDrawableContainerState.getChildren();
250            for (int i = 0; i < N; i++) {
251                if (drawables[i] != null) drawables[i].mutate();
252            }
253            mMutated = true;
254        }
255        return this;
256    }
257
258    public abstract static class DrawableContainerState extends ConstantState {
259        final DrawableContainer mOwner;
260
261        int         mChangingConfigurations;
262        int         mChildrenChangingConfigurations;
263
264        Drawable[]  mDrawables;
265        int         mNumChildren;
266
267        boolean     mVariablePadding = false;
268        Rect        mConstantPadding = null;
269
270        boolean     mConstantSize = false;
271        boolean     mComputedConstantSize = false;
272        int         mConstantWidth;
273        int         mConstantHeight;
274        int         mConstantMinimumWidth;
275        int         mConstantMinimumHeight;
276
277        boolean     mHaveOpacity = false;
278        int         mOpacity;
279
280        boolean     mHaveStateful = false;
281        boolean     mStateful;
282
283        boolean     mCheckedConstantState;
284        boolean     mCanConstantState;
285
286        boolean     mPaddingChecked = false;
287
288        boolean     mDither = DEFAULT_DITHER;
289
290        DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
291                Resources res) {
292            mOwner = owner;
293
294            if (orig != null) {
295                mChangingConfigurations = orig.mChangingConfigurations;
296                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
297
298                final Drawable[] origDr = orig.mDrawables;
299
300                mDrawables = new Drawable[origDr.length];
301                mNumChildren = orig.mNumChildren;
302
303                final int N = mNumChildren;
304                for (int i=0; i<N; i++) {
305                    if (res != null) {
306                        mDrawables[i] = origDr[i].getConstantState().newDrawable(res);
307                    } else {
308                        mDrawables[i] = origDr[i].getConstantState().newDrawable();
309                    }
310                    mDrawables[i].setCallback(owner);
311                }
312
313                mCheckedConstantState = mCanConstantState = true;
314                mVariablePadding = orig.mVariablePadding;
315                if (orig.mConstantPadding != null) {
316                    mConstantPadding = new Rect(orig.mConstantPadding);
317                }
318                mConstantSize = orig.mConstantSize;
319                mComputedConstantSize = orig.mComputedConstantSize;
320                mConstantWidth = orig.mConstantWidth;
321                mConstantHeight = orig.mConstantHeight;
322
323                mHaveOpacity = orig.mHaveOpacity;
324                mOpacity = orig.mOpacity;
325                mHaveStateful = orig.mHaveStateful;
326                mStateful = orig.mStateful;
327
328                mDither = orig.mDither;
329
330            } else {
331                mDrawables = new Drawable[10];
332                mNumChildren = 0;
333                mCheckedConstantState = mCanConstantState = false;
334            }
335        }
336
337        @Override
338        public int getChangingConfigurations() {
339            return mChangingConfigurations;
340        }
341
342        public final int addChild(Drawable dr) {
343            final int pos = mNumChildren;
344
345            if (pos >= mDrawables.length) {
346                growArray(pos, pos+10);
347            }
348
349            dr.setVisible(false, true);
350            dr.setCallback(mOwner);
351
352            mDrawables[pos] = dr;
353            mNumChildren++;
354            mChildrenChangingConfigurations |= dr.getChangingConfigurations();
355            mHaveOpacity = false;
356            mHaveStateful = false;
357
358            mConstantPadding = null;
359            mPaddingChecked = false;
360            mComputedConstantSize = false;
361
362            return pos;
363        }
364
365        public final int getChildCount() {
366            return mNumChildren;
367        }
368
369        public final Drawable[] getChildren() {
370            return mDrawables;
371        }
372
373        /** A boolean value indicating whether to use the maximum padding value of
374          * all frames in the set (false), or to use the padding value of the frame
375          * being shown (true). Default value is false.
376          */
377        public final void setVariablePadding(boolean variable) {
378            mVariablePadding = variable;
379        }
380
381        public final Rect getConstantPadding() {
382            if (mVariablePadding) {
383                return null;
384            }
385            if (mConstantPadding != null || mPaddingChecked) {
386                return mConstantPadding;
387            }
388
389            Rect r = null;
390            final Rect t = new Rect();
391            final int N = getChildCount();
392            final Drawable[] drawables = mDrawables;
393            for (int i = 0; i < N; i++) {
394                if (drawables[i].getPadding(t)) {
395                    if (r == null) r = new Rect(0, 0, 0, 0);
396                    if (t.left > r.left) r.left = t.left;
397                    if (t.top > r.top) r.top = t.top;
398                    if (t.right > r.right) r.right = t.right;
399                    if (t.bottom > r.bottom) r.bottom = t.bottom;
400                }
401            }
402            mPaddingChecked = true;
403            return (mConstantPadding = r);
404        }
405
406        public final void setConstantSize(boolean constant) {
407            mConstantSize = constant;
408        }
409
410        public final boolean isConstantSize() {
411            return mConstantSize;
412        }
413
414        public final int getConstantWidth() {
415            if (!mComputedConstantSize) {
416                computeConstantSize();
417            }
418
419            return mConstantWidth;
420        }
421
422        public final int getConstantHeight() {
423            if (!mComputedConstantSize) {
424                computeConstantSize();
425            }
426
427            return mConstantHeight;
428        }
429
430        public final int getConstantMinimumWidth() {
431            if (!mComputedConstantSize) {
432                computeConstantSize();
433            }
434
435            return mConstantMinimumWidth;
436        }
437
438        public final int getConstantMinimumHeight() {
439            if (!mComputedConstantSize) {
440                computeConstantSize();
441            }
442
443            return mConstantMinimumHeight;
444        }
445
446        private void computeConstantSize() {
447            mComputedConstantSize = true;
448
449            final int N = getChildCount();
450            final Drawable[] drawables = mDrawables;
451            mConstantWidth = mConstantHeight = 0;
452            mConstantMinimumWidth = mConstantMinimumHeight = 0;
453            for (int i = 0; i < N; i++) {
454                Drawable dr = drawables[i];
455                int s = dr.getIntrinsicWidth();
456                if (s > mConstantWidth) mConstantWidth = s;
457                s = dr.getIntrinsicHeight();
458                if (s > mConstantHeight) mConstantHeight = s;
459                s = dr.getMinimumWidth();
460                if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
461                s = dr.getMinimumHeight();
462                if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
463            }
464        }
465
466        public final int getOpacity() {
467            if (mHaveOpacity) {
468                return mOpacity;
469            }
470
471            final int N = getChildCount();
472            final Drawable[] drawables = mDrawables;
473            int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
474            for (int i = 1; i < N; i++) {
475                op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
476            }
477            mOpacity = op;
478            mHaveOpacity = true;
479            return op;
480        }
481
482        public final boolean isStateful() {
483            if (mHaveStateful) {
484                return mStateful;
485            }
486
487            boolean stateful = false;
488            final int N = getChildCount();
489            for (int i = 0; i < N; i++) {
490                if (mDrawables[i].isStateful()) {
491                    stateful = true;
492                    break;
493                }
494            }
495
496            mStateful = stateful;
497            mHaveStateful = true;
498            return stateful;
499        }
500
501        public void growArray(int oldSize, int newSize) {
502            Drawable[] newDrawables = new Drawable[newSize];
503            System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
504            mDrawables = newDrawables;
505        }
506
507        public synchronized boolean canConstantState() {
508            if (!mCheckedConstantState) {
509                mCanConstantState = true;
510                final int N = mNumChildren;
511                for (int i=0; i<N; i++) {
512                    if (mDrawables[i].getConstantState() == null) {
513                        mCanConstantState = false;
514                        break;
515                    }
516                }
517                mCheckedConstantState = true;
518            }
519
520            return mCanConstantState;
521        }
522    }
523
524    protected void setConstantState(DrawableContainerState state)
525    {
526        mDrawableContainerState = state;
527    }
528}
529