Files
frameworks_base/graphics/java/android/graphics/drawable/DrawableContainer.java
Mathew Inwood 31755f94e1 Limit access to suspected false positives.
Members modified herein are suspected to be false positives: i.e. things
that were added to the greylist in P, but subsequent data analysis
suggests that they are not, in fact, used after all.

Add a maxTargetSdk=P to these APIs. This is lower-risk that simply
removing these things from the greylist, as none of out data sources are
perfect nor complete.

For APIs that are not supported yet by annotations, move them to
hiddenapi-greylist-max-p.txt instead which has the same effect.

Exempted-From-Owner-Approval: Automatic changes to the codebase
affecting only @UnsupportedAppUsage annotations, themselves added
without requiring owners approval earlier.

Bug: 115609023
Test: m
Change-Id: I020a9c09672ebcae64c5357abc4993e07e744687
2018-12-28 11:50:04 +00:00

1283 lines
40 KiB
Java

/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics.drawable;
import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.os.Build;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.SparseArray;
import android.view.View;
/**
* A helper class that contains several {@link Drawable}s and selects which one to use.
*
* You can subclass it to create your own DrawableContainers or directly use one its child classes.
*/
public class DrawableContainer extends Drawable implements Drawable.Callback {
private static final boolean DEBUG = false;
private static final String TAG = "DrawableContainer";
/**
* To be proper, we should have a getter for dither (and alpha, etc.)
* so that proxy classes like this can save/restore their delegates'
* values, but we don't have getters. Since we do have setters
* (e.g. setDither), which this proxy forwards on, we have to have some
* default/initial setting.
*
* The initial setting for dither is now true, since it almost always seems
* to improve the quality at negligible cost.
*/
private static final boolean DEFAULT_DITHER = true;
@UnsupportedAppUsage
private DrawableContainerState mDrawableContainerState;
private Rect mHotspotBounds;
private Drawable mCurrDrawable;
@UnsupportedAppUsage
private Drawable mLastDrawable;
private int mAlpha = 0xFF;
/** Whether setAlpha() has been called at least once. */
private boolean mHasAlpha;
private int mCurIndex = -1;
private int mLastIndex = -1;
private boolean mMutated;
// Animations.
private Runnable mAnimationRunnable;
private long mEnterAnimationEnd;
private long mExitAnimationEnd;
/** Callback that blocks invalidation. Used for drawable initialization. */
private BlockInvalidateCallback mBlockInvalidateCallback;
// overrides from Drawable
@Override
public void draw(Canvas canvas) {
if (mCurrDrawable != null) {
mCurrDrawable.draw(canvas);
}
if (mLastDrawable != null) {
mLastDrawable.draw(canvas);
}
}
@Override
public @Config int getChangingConfigurations() {
return super.getChangingConfigurations()
| mDrawableContainerState.getChangingConfigurations();
}
private boolean needsMirroring() {
return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
}
@Override
public boolean getPadding(Rect padding) {
final Rect r = mDrawableContainerState.getConstantPadding();
boolean result;
if (r != null) {
padding.set(r);
result = (r.left | r.top | r.bottom | r.right) != 0;
} else {
if (mCurrDrawable != null) {
result = mCurrDrawable.getPadding(padding);
} else {
result = super.getPadding(padding);
}
}
if (needsMirroring()) {
final int left = padding.left;
final int right = padding.right;
padding.left = right;
padding.right = left;
}
return result;
}
@Override
public Insets getOpticalInsets() {
if (mCurrDrawable != null) {
return mCurrDrawable.getOpticalInsets();
}
return Insets.NONE;
}
@Override
public void getOutline(@NonNull Outline outline) {
if (mCurrDrawable != null) {
mCurrDrawable.getOutline(outline);
}
}
@Override
public void setAlpha(int alpha) {
if (!mHasAlpha || mAlpha != alpha) {
mHasAlpha = true;
mAlpha = alpha;
if (mCurrDrawable != null) {
if (mEnterAnimationEnd == 0) {
mCurrDrawable.setAlpha(alpha);
} else {
animate(false);
}
}
}
}
@Override
public int getAlpha() {
return mAlpha;
}
@Override
public void setDither(boolean dither) {
if (mDrawableContainerState.mDither != dither) {
mDrawableContainerState.mDither = dither;
if (mCurrDrawable != null) {
mCurrDrawable.setDither(mDrawableContainerState.mDither);
}
}
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mDrawableContainerState.mHasColorFilter = true;
if (mDrawableContainerState.mColorFilter != colorFilter) {
mDrawableContainerState.mColorFilter = colorFilter;
if (mCurrDrawable != null) {
mCurrDrawable.setColorFilter(colorFilter);
}
}
}
@Override
public void setTintList(ColorStateList tint) {
mDrawableContainerState.mHasTintList = true;
if (mDrawableContainerState.mTintList != tint) {
mDrawableContainerState.mTintList = tint;
if (mCurrDrawable != null) {
mCurrDrawable.setTintList(tint);
}
}
}
@Override
public void setTintMode(Mode tintMode) {
mDrawableContainerState.mHasTintMode = true;
if (mDrawableContainerState.mTintMode != tintMode) {
mDrawableContainerState.mTintMode = tintMode;
if (mCurrDrawable != null) {
mCurrDrawable.setTintMode(tintMode);
}
}
}
/**
* Change the global fade duration when a new drawable is entering
* the scene.
*
* @param ms The amount of time to fade in milliseconds.
*/
public void setEnterFadeDuration(int ms) {
mDrawableContainerState.mEnterFadeDuration = ms;
}
/**
* Change the global fade duration when a new drawable is leaving
* the scene.
*
* @param ms The amount of time to fade in milliseconds.
*/
public void setExitFadeDuration(int ms) {
mDrawableContainerState.mExitFadeDuration = ms;
}
@Override
protected void onBoundsChange(Rect bounds) {
if (mLastDrawable != null) {
mLastDrawable.setBounds(bounds);
}
if (mCurrDrawable != null) {
mCurrDrawable.setBounds(bounds);
}
}
@Override
public boolean isStateful() {
return mDrawableContainerState.isStateful();
}
/** @hide */
@Override
public boolean hasFocusStateSpecified() {
if (mCurrDrawable != null) {
return mCurrDrawable.hasFocusStateSpecified();
}
if (mLastDrawable != null) {
return mLastDrawable.hasFocusStateSpecified();
}
return false;
}
@Override
public void setAutoMirrored(boolean mirrored) {
if (mDrawableContainerState.mAutoMirrored != mirrored) {
mDrawableContainerState.mAutoMirrored = mirrored;
if (mCurrDrawable != null) {
mCurrDrawable.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
}
}
}
@Override
public boolean isAutoMirrored() {
return mDrawableContainerState.mAutoMirrored;
}
@Override
public void jumpToCurrentState() {
boolean changed = false;
if (mLastDrawable != null) {
mLastDrawable.jumpToCurrentState();
mLastDrawable = null;
mLastIndex = -1;
changed = true;
}
if (mCurrDrawable != null) {
mCurrDrawable.jumpToCurrentState();
if (mHasAlpha) {
mCurrDrawable.setAlpha(mAlpha);
}
}
if (mExitAnimationEnd != 0) {
mExitAnimationEnd = 0;
changed = true;
}
if (mEnterAnimationEnd != 0) {
mEnterAnimationEnd = 0;
changed = true;
}
if (changed) {
invalidateSelf();
}
}
@Override
public void setHotspot(float x, float y) {
if (mCurrDrawable != null) {
mCurrDrawable.setHotspot(x, y);
}
}
@Override
public void setHotspotBounds(int left, int top, int right, int bottom) {
if (mHotspotBounds == null) {
mHotspotBounds = new Rect(left, top, right, bottom);
} else {
mHotspotBounds.set(left, top, right, bottom);
}
if (mCurrDrawable != null) {
mCurrDrawable.setHotspotBounds(left, top, right, bottom);
}
}
@Override
public void getHotspotBounds(Rect outRect) {
if (mHotspotBounds != null) {
outRect.set(mHotspotBounds);
} else {
super.getHotspotBounds(outRect);
}
}
@Override
protected boolean onStateChange(int[] state) {
if (mLastDrawable != null) {
return mLastDrawable.setState(state);
}
if (mCurrDrawable != null) {
return mCurrDrawable.setState(state);
}
return false;
}
@Override
protected boolean onLevelChange(int level) {
if (mLastDrawable != null) {
return mLastDrawable.setLevel(level);
}
if (mCurrDrawable != null) {
return mCurrDrawable.setLevel(level);
}
return false;
}
@Override
public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
// Let the container handle setting its own layout direction. Otherwise,
// we're accessing potentially unused states.
return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex());
}
@Override
public int getIntrinsicWidth() {
if (mDrawableContainerState.isConstantSize()) {
return mDrawableContainerState.getConstantWidth();
}
return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
}
@Override
public int getIntrinsicHeight() {
if (mDrawableContainerState.isConstantSize()) {
return mDrawableContainerState.getConstantHeight();
}
return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
}
@Override
public int getMinimumWidth() {
if (mDrawableContainerState.isConstantSize()) {
return mDrawableContainerState.getConstantMinimumWidth();
}
return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
}
@Override
public int getMinimumHeight() {
if (mDrawableContainerState.isConstantSize()) {
return mDrawableContainerState.getConstantMinimumHeight();
}
return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
// This may have been called as the result of a tint changing, in
// which case we may need to refresh the cached statefulness or
// opacity.
if (mDrawableContainerState != null) {
mDrawableContainerState.invalidateCache();
}
if (who == mCurrDrawable && getCallback() != null) {
getCallback().invalidateDrawable(this);
}
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
if (who == mCurrDrawable && getCallback() != null) {
getCallback().scheduleDrawable(this, what, when);
}
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
if (who == mCurrDrawable && getCallback() != null) {
getCallback().unscheduleDrawable(this, what);
}
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
if (mLastDrawable != null) {
mLastDrawable.setVisible(visible, restart);
}
if (mCurrDrawable != null) {
mCurrDrawable.setVisible(visible, restart);
}
return changed;
}
@Override
public int getOpacity() {
return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
mDrawableContainerState.getOpacity();
}
/** @hide */
public void setCurrentIndex(int index) {
selectDrawable(index);
}
/** @hide */
public int getCurrentIndex() {
return mCurIndex;
}
/**
* Sets the currently displayed drawable by index.
* <p>
* If an invalid index is specified, the current drawable will be set to
* {@code null} and the index will be set to {@code -1}.
*
* @param index the index of the drawable to display
* @return {@code true} if the drawable changed, {@code false} otherwise
*/
public boolean selectDrawable(int index) {
if (index == mCurIndex) {
return false;
}
final long now = SystemClock.uptimeMillis();
if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + index
+ ": exit=" + mDrawableContainerState.mExitFadeDuration
+ " enter=" + mDrawableContainerState.mEnterFadeDuration);
if (mDrawableContainerState.mExitFadeDuration > 0) {
if (mLastDrawable != null) {
mLastDrawable.setVisible(false, false);
}
if (mCurrDrawable != null) {
mLastDrawable = mCurrDrawable;
mLastIndex = mCurIndex;
mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
} else {
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
}
} else if (mCurrDrawable != null) {
mCurrDrawable.setVisible(false, false);
}
if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
final Drawable d = mDrawableContainerState.getChild(index);
mCurrDrawable = d;
mCurIndex = index;
if (d != null) {
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
}
initializeDrawableForDisplay(d);
}
} else {
mCurrDrawable = null;
mCurIndex = -1;
}
if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
if (mAnimationRunnable == null) {
mAnimationRunnable = new Runnable() {
@Override public void run() {
animate(true);
invalidateSelf();
}
};
} else {
unscheduleSelf(mAnimationRunnable);
}
// Compute first frame and schedule next animation.
animate(true);
}
invalidateSelf();
return true;
}
/**
* Initializes a drawable for display in this container.
*
* @param d The drawable to initialize.
*/
private void initializeDrawableForDisplay(Drawable d) {
if (mBlockInvalidateCallback == null) {
mBlockInvalidateCallback = new BlockInvalidateCallback();
}
// Temporary fix for suspending callbacks during initialization. We
// don't want any of these setters causing an invalidate() since that
// may call back into DrawableContainer.
d.setCallback(mBlockInvalidateCallback.wrap(d.getCallback()));
try {
if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
d.setAlpha(mAlpha);
}
if (mDrawableContainerState.mHasColorFilter) {
// Color filter always overrides tint.
d.setColorFilter(mDrawableContainerState.mColorFilter);
} else {
if (mDrawableContainerState.mHasTintList) {
d.setTintList(mDrawableContainerState.mTintList);
}
if (mDrawableContainerState.mHasTintMode) {
d.setTintMode(mDrawableContainerState.mTintMode);
}
}
d.setVisible(isVisible(), true);
d.setDither(mDrawableContainerState.mDither);
d.setState(getState());
d.setLevel(getLevel());
d.setBounds(getBounds());
d.setLayoutDirection(getLayoutDirection());
d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
final Rect hotspotBounds = mHotspotBounds;
if (hotspotBounds != null) {
d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
hotspotBounds.right, hotspotBounds.bottom);
}
} finally {
d.setCallback(mBlockInvalidateCallback.unwrap());
}
}
void animate(boolean schedule) {
mHasAlpha = true;
final long now = SystemClock.uptimeMillis();
boolean animating = false;
if (mCurrDrawable != null) {
if (mEnterAnimationEnd != 0) {
if (mEnterAnimationEnd <= now) {
mCurrDrawable.setAlpha(mAlpha);
mEnterAnimationEnd = 0;
} else {
int animAlpha = (int)((mEnterAnimationEnd-now)*255)
/ mDrawableContainerState.mEnterFadeDuration;
mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
animating = true;
}
}
} else {
mEnterAnimationEnd = 0;
}
if (mLastDrawable != null) {
if (mExitAnimationEnd != 0) {
if (mExitAnimationEnd <= now) {
mLastDrawable.setVisible(false, false);
mLastDrawable = null;
mLastIndex = -1;
mExitAnimationEnd = 0;
} else {
int animAlpha = (int)((mExitAnimationEnd-now)*255)
/ mDrawableContainerState.mExitFadeDuration;
mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
animating = true;
}
}
} else {
mExitAnimationEnd = 0;
}
if (schedule && animating) {
scheduleSelf(mAnimationRunnable, now + 1000 / 60);
}
}
@Override
public Drawable getCurrent() {
return mCurrDrawable;
}
/**
* Updates the source density based on the resources used to inflate
* density-dependent values. Implementing classes should call this method
* during inflation.
*
* @param res the resources used to inflate density-dependent values
* @hide
*/
protected final void updateDensity(Resources res) {
mDrawableContainerState.updateDensity(res);
}
@Override
public void applyTheme(Theme theme) {
mDrawableContainerState.applyTheme(theme);
}
@Override
public boolean canApplyTheme() {
return mDrawableContainerState.canApplyTheme();
}
@Override
public ConstantState getConstantState() {
if (mDrawableContainerState.canConstantState()) {
mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
return mDrawableContainerState;
}
return null;
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
final DrawableContainerState clone = cloneConstantState();
clone.mutate();
setConstantState(clone);
mMutated = true;
}
return this;
}
/**
* Returns a shallow copy of the container's constant state to be used as
* the base state for {@link #mutate()}.
*
* @return a shallow copy of the constant state
*/
DrawableContainerState cloneConstantState() {
return mDrawableContainerState;
}
/**
* @hide
*/
public void clearMutated() {
super.clearMutated();
mDrawableContainerState.clearMutated();
mMutated = false;
}
/**
* A ConstantState that can contain several {@link Drawable}s.
*
* This class was made public to enable testing, and its visibility may change in a future
* release.
*/
public abstract static class DrawableContainerState extends ConstantState {
final DrawableContainer mOwner;
Resources mSourceRes;
int mDensity = DisplayMetrics.DENSITY_DEFAULT;
@Config int mChangingConfigurations;
@Config int mChildrenChangingConfigurations;
SparseArray<ConstantState> mDrawableFutures;
@UnsupportedAppUsage
Drawable[] mDrawables;
int mNumChildren;
boolean mVariablePadding = false;
boolean mCheckedPadding;
@UnsupportedAppUsage
Rect mConstantPadding;
boolean mConstantSize = false;
boolean mCheckedConstantSize;
int mConstantWidth;
int mConstantHeight;
int mConstantMinimumWidth;
int mConstantMinimumHeight;
boolean mCheckedOpacity;
int mOpacity;
boolean mCheckedStateful;
boolean mStateful;
boolean mCheckedConstantState;
boolean mCanConstantState;
boolean mDither = DEFAULT_DITHER;
boolean mMutated;
int mLayoutDirection;
int mEnterFadeDuration = 0;
int mExitFadeDuration = 0;
boolean mAutoMirrored;
ColorFilter mColorFilter;
@UnsupportedAppUsage
boolean mHasColorFilter;
ColorStateList mTintList;
Mode mTintMode;
boolean mHasTintList;
boolean mHasTintMode;
/**
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
Resources res) {
mOwner = owner;
mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
if (orig != null) {
mChangingConfigurations = orig.mChangingConfigurations;
mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
mCheckedConstantState = true;
mCanConstantState = true;
mVariablePadding = orig.mVariablePadding;
mConstantSize = orig.mConstantSize;
mDither = orig.mDither;
mMutated = orig.mMutated;
mLayoutDirection = orig.mLayoutDirection;
mEnterFadeDuration = orig.mEnterFadeDuration;
mExitFadeDuration = orig.mExitFadeDuration;
mAutoMirrored = orig.mAutoMirrored;
mColorFilter = orig.mColorFilter;
mHasColorFilter = orig.mHasColorFilter;
mTintList = orig.mTintList;
mTintMode = orig.mTintMode;
mHasTintList = orig.mHasTintList;
mHasTintMode = orig.mHasTintMode;
if (orig.mDensity == mDensity) {
if (orig.mCheckedPadding) {
mConstantPadding = new Rect(orig.mConstantPadding);
mCheckedPadding = true;
}
if (orig.mCheckedConstantSize) {
mConstantWidth = orig.mConstantWidth;
mConstantHeight = orig.mConstantHeight;
mConstantMinimumWidth = orig.mConstantMinimumWidth;
mConstantMinimumHeight = orig.mConstantMinimumHeight;
mCheckedConstantSize = true;
}
}
if (orig.mCheckedOpacity) {
mOpacity = orig.mOpacity;
mCheckedOpacity = true;
}
if (orig.mCheckedStateful) {
mStateful = orig.mStateful;
mCheckedStateful = true;
}
// Postpone cloning children and futures until we're absolutely
// sure that we're done computing values for the original state.
final Drawable[] origDr = orig.mDrawables;
mDrawables = new Drawable[origDr.length];
mNumChildren = orig.mNumChildren;
final SparseArray<ConstantState> origDf = orig.mDrawableFutures;
if (origDf != null) {
mDrawableFutures = origDf.clone();
} else {
mDrawableFutures = new SparseArray<>(mNumChildren);
}
// Create futures for drawables with constant states. If a
// drawable doesn't have a constant state, then we can't clone
// it and we'll have to reference the original.
final int N = mNumChildren;
for (int i = 0; i < N; i++) {
if (origDr[i] != null) {
final ConstantState cs = origDr[i].getConstantState();
if (cs != null) {
mDrawableFutures.put(i, cs);
} else {
mDrawables[i] = origDr[i];
}
}
}
} else {
mDrawables = new Drawable[10];
mNumChildren = 0;
}
}
@Override
public @Config int getChangingConfigurations() {
return mChangingConfigurations | mChildrenChangingConfigurations;
}
/**
* Adds the drawable to the end of the list of contained drawables.
*
* @param dr the drawable to add
* @return the position of the drawable within the container
*/
public final int addChild(Drawable dr) {
final int pos = mNumChildren;
if (pos >= mDrawables.length) {
growArray(pos, pos+10);
}
dr.mutate();
dr.setVisible(false, true);
dr.setCallback(mOwner);
mDrawables[pos] = dr;
mNumChildren++;
mChildrenChangingConfigurations |= dr.getChangingConfigurations();
invalidateCache();
mConstantPadding = null;
mCheckedPadding = false;
mCheckedConstantSize = false;
mCheckedConstantState = false;
return pos;
}
/**
* Invalidates the cached opacity and statefulness.
*/
void invalidateCache() {
mCheckedOpacity = false;
mCheckedStateful = false;
}
final int getCapacity() {
return mDrawables.length;
}
private void createAllFutures() {
if (mDrawableFutures != null) {
final int futureCount = mDrawableFutures.size();
for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
final int index = mDrawableFutures.keyAt(keyIndex);
final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
}
mDrawableFutures = null;
}
}
private Drawable prepareDrawable(Drawable child) {
child.setLayoutDirection(mLayoutDirection);
child = child.mutate();
child.setCallback(mOwner);
return child;
}
public final int getChildCount() {
return mNumChildren;
}
/*
* @deprecated Use {@link #getChild} instead.
*/
public final Drawable[] getChildren() {
// Create all futures for backwards compatibility.
createAllFutures();
return mDrawables;
}
public final Drawable getChild(int index) {
final Drawable result = mDrawables[index];
if (result != null) {
return result;
}
// Prepare future drawable if necessary.
if (mDrawableFutures != null) {
final int keyIndex = mDrawableFutures.indexOfKey(index);
if (keyIndex >= 0) {
final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
mDrawables[index] = prepared;
mDrawableFutures.removeAt(keyIndex);
if (mDrawableFutures.size() == 0) {
mDrawableFutures = null;
}
return prepared;
}
}
return null;
}
final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
boolean changed = false;
// No need to call createAllFutures, since future drawables will
// change layout direction when they are prepared.
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
if (drawables[i] != null) {
final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
if (i == currentIndex) {
changed = childChanged;
}
}
}
mLayoutDirection = layoutDirection;
return changed;
}
/**
* Updates the source density based on the resources used to inflate
* density-dependent values.
*
* @param res the resources used to inflate density-dependent values
*/
final void updateDensity(Resources res) {
if (res != null) {
mSourceRes = res;
// The density may have changed since the last update (if any). Any
// dimension-type attributes will need their default values scaled.
final int targetDensity = Drawable.resolveDensity(res, mDensity);
final int sourceDensity = mDensity;
mDensity = targetDensity;
if (sourceDensity != targetDensity) {
mCheckedConstantSize = false;
mCheckedPadding = false;
}
}
}
final void applyTheme(Theme theme) {
if (theme != null) {
createAllFutures();
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
if (drawables[i] != null && drawables[i].canApplyTheme()) {
drawables[i].applyTheme(theme);
// Update cached mask of child changing configurations.
mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
}
}
updateDensity(theme.getResources());
}
}
@Override
public boolean canApplyTheme() {
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
final Drawable d = drawables[i];
if (d != null) {
if (d.canApplyTheme()) {
return true;
}
} else {
final ConstantState future = mDrawableFutures.get(i);
if (future != null && future.canApplyTheme()) {
return true;
}
}
}
return false;
}
private void mutate() {
// No need to call createAllFutures, since future drawables will
// mutate when they are prepared.
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
if (drawables[i] != null) {
drawables[i].mutate();
}
}
mMutated = true;
}
final void clearMutated() {
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
if (drawables[i] != null) {
drawables[i].clearMutated();
}
}
mMutated = false;
}
/**
* A boolean value indicating whether to use the maximum padding value
* of all frames in the set (false), or to use the padding value of the
* frame being shown (true). Default value is false.
*/
public final void setVariablePadding(boolean variable) {
mVariablePadding = variable;
}
public final Rect getConstantPadding() {
if (mVariablePadding) {
return null;
}
if ((mConstantPadding != null) || mCheckedPadding) {
return mConstantPadding;
}
createAllFutures();
Rect r = null;
final Rect t = new Rect();
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
if (drawables[i].getPadding(t)) {
if (r == null) r = new Rect(0, 0, 0, 0);
if (t.left > r.left) r.left = t.left;
if (t.top > r.top) r.top = t.top;
if (t.right > r.right) r.right = t.right;
if (t.bottom > r.bottom) r.bottom = t.bottom;
}
}
mCheckedPadding = true;
return (mConstantPadding = r);
}
public final void setConstantSize(boolean constant) {
mConstantSize = constant;
}
public final boolean isConstantSize() {
return mConstantSize;
}
public final int getConstantWidth() {
if (!mCheckedConstantSize) {
computeConstantSize();
}
return mConstantWidth;
}
public final int getConstantHeight() {
if (!mCheckedConstantSize) {
computeConstantSize();
}
return mConstantHeight;
}
public final int getConstantMinimumWidth() {
if (!mCheckedConstantSize) {
computeConstantSize();
}
return mConstantMinimumWidth;
}
public final int getConstantMinimumHeight() {
if (!mCheckedConstantSize) {
computeConstantSize();
}
return mConstantMinimumHeight;
}
protected void computeConstantSize() {
mCheckedConstantSize = true;
createAllFutures();
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
mConstantWidth = mConstantHeight = -1;
mConstantMinimumWidth = mConstantMinimumHeight = 0;
for (int i = 0; i < N; i++) {
final Drawable dr = drawables[i];
int s = dr.getIntrinsicWidth();
if (s > mConstantWidth) mConstantWidth = s;
s = dr.getIntrinsicHeight();
if (s > mConstantHeight) mConstantHeight = s;
s = dr.getMinimumWidth();
if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
s = dr.getMinimumHeight();
if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
}
}
public final void setEnterFadeDuration(int duration) {
mEnterFadeDuration = duration;
}
public final int getEnterFadeDuration() {
return mEnterFadeDuration;
}
public final void setExitFadeDuration(int duration) {
mExitFadeDuration = duration;
}
public final int getExitFadeDuration() {
return mExitFadeDuration;
}
public final int getOpacity() {
if (mCheckedOpacity) {
return mOpacity;
}
createAllFutures();
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
for (int i = 1; i < N; i++) {
op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
}
mOpacity = op;
mCheckedOpacity = true;
return op;
}
public final boolean isStateful() {
if (mCheckedStateful) {
return mStateful;
}
createAllFutures();
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
boolean isStateful = false;
for (int i = 0; i < N; i++) {
if (drawables[i].isStateful()) {
isStateful = true;
break;
}
}
mStateful = isStateful;
mCheckedStateful = true;
return isStateful;
}
public void growArray(int oldSize, int newSize) {
Drawable[] newDrawables = new Drawable[newSize];
System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
mDrawables = newDrawables;
}
public synchronized boolean canConstantState() {
if (mCheckedConstantState) {
return mCanConstantState;
}
createAllFutures();
mCheckedConstantState = true;
final int N = mNumChildren;
final Drawable[] drawables = mDrawables;
for (int i = 0; i < N; i++) {
if (drawables[i].getConstantState() == null) {
mCanConstantState = false;
return false;
}
}
mCanConstantState = true;
return true;
}
}
protected void setConstantState(DrawableContainerState state) {
mDrawableContainerState = state;
// The locally cached drawables may have changed.
if (mCurIndex >= 0) {
mCurrDrawable = state.getChild(mCurIndex);
if (mCurrDrawable != null) {
initializeDrawableForDisplay(mCurrDrawable);
}
}
// Clear out the last drawable. We don't have enough information to
// propagate local state from the past.
mLastIndex = -1;
mLastDrawable = null;
}
/**
* Callback that blocks drawable invalidation.
*/
private static class BlockInvalidateCallback implements Drawable.Callback {
private Drawable.Callback mCallback;
public BlockInvalidateCallback wrap(Drawable.Callback callback) {
mCallback = callback;
return this;
}
public Drawable.Callback unwrap() {
final Drawable.Callback callback = mCallback;
mCallback = null;
return callback;
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
// Ignore invalidation.
}
@Override
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
if (mCallback != null) {
mCallback.scheduleDrawable(who, what, when);
}
}
@Override
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
if (mCallback != null) {
mCallback.unscheduleDrawable(who, what);
}
}
}
}