FileDocCategorySizeDatePackage
BackgroundManager.javaAPI DocAndroid 5.1 API30741Thu Mar 12 22:22:56 GMT 2015android.support.v17.leanback.app

BackgroundManager

public final class BackgroundManager extends Object
Supports background image continuity between multiple Activities.

An Activity should instantiate a BackgroundManager and {@link #attach} to the Activity's window. When the Activity is started, the background is initialized to the current background values stored in a continuity service. The background continuity service is updated as the background is updated.

At some point, for example when it is stopped, the Activity may release its background state.

When an Activity is resumed, if the BackgroundManager has not been released, the continuity service is updated from the BackgroundManager state. If the BackgroundManager was released, the BackgroundManager inherits the current state from the continuity service.

When the last Activity is destroyed, the background state is reset.

Backgrounds consist of several layers, from back to front:

  • the background Drawable of the theme
  • a solid color (set via {@link #setColor})
  • two Drawables, previous and current (set via {@link #setBitmap} or {@link #setDrawable}), which may be in transition

BackgroundManager holds references to potentially large bitmap Drawables. Call {@link #release} to release these references when the Activity is not visible.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final int
FULL_ALPHA
private static final int
DIM_ALPHA_ON_SOLID
private static final int
CHANGE_BG_DELAY_MS
private static final int
FADE_DURATION
private static final boolean
USE_SEPARATE_WINDOW
Using a separate window for backgrounds can improve graphics performance by leveraging hardware display layers. TODO: support a leanback configuration option.
private static final String
WINDOW_NAME
private static final String
FRAGMENT_TAG
private android.content.Context
mContext
private android.os.Handler
mHandler
private android.view.Window
mWindow
private android.view.WindowManager
mWindowManager
private android.view.View
mBgView
private BackgroundContinuityService
mService
private int
mThemeDrawableResourceId
private int
mHeightPx
private int
mWidthPx
private android.graphics.drawable.Drawable
mBackgroundDrawable
private int
mBackgroundColor
private boolean
mAttached
private android.graphics.drawable.LayerDrawable
mLayerDrawable
private DrawableWrapper
mLayerWrapper
private DrawableWrapper
mImageInWrapper
private DrawableWrapper
mImageOutWrapper
private DrawableWrapper
mColorWrapper
private DrawableWrapper
mDimWrapper
private android.graphics.drawable.Drawable
mThemeDrawable
private ChangeBackgroundRunnable
mChangeRunnable
private final Animator.AnimatorListener
mImageInListener
Constructors Summary
private BackgroundManager(android.app.Activity activity, boolean isSupportFragmentActivity)

        mContext = activity;
        mService = BackgroundContinuityService.getInstance();
        mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
        mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels;
        mHandler = new Handler();

        TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
                android.R.attr.windowBackground });
        mThemeDrawableResourceId = ta.getResourceId(0, -1);
        if (mThemeDrawableResourceId < 0) {
            if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!");
        }
        ta.recycle();

        if (isSupportFragmentActivity) {
            createSupportFragment((FragmentActivity) activity);
        } else {
            createFragment(activity);
        }
    
Methods Summary
private voidapplyBackgroundChanges()

        if (!mAttached || mLayerWrapper == null) {
            return;
        }

        if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable);

        int dimAlpha = 0;

        if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) {
            if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting");
            mImageOutWrapper.startAnimation();
            mImageOutWrapper = null;
            dimAlpha = DIM_ALPHA_ON_SOLID;
        }

        if (mImageInWrapper == null && mBackgroundDrawable != null) {
            if (DEBUG) Log.v(TAG, "creating new imagein drawable");
            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
            if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting");
            mImageInWrapper.setAlpha(0);
            mImageInWrapper.fadeIn(FADE_DURATION, 0);
            mImageInWrapper.startAnimation(mImageInListener);
            dimAlpha = FULL_ALPHA;
        }

        if (mDimWrapper != null && dimAlpha != 0) {
            if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha);
            mDimWrapper.fade(FADE_DURATION, 0, dimAlpha);
            mDimWrapper.startAnimation();
        }
    
public voidattach(android.view.Window window)
Make the background visible on the given Window.

        if (USE_SEPARATE_WINDOW) {
            attachBehindWindow(window);
        } else {
            attachToView(window.getDecorView());
        }
    
private voidattachBehindWindow(android.view.Window window)

        if (DEBUG) Log.v(TAG, "attachBehindWindow " + window);
        mWindow = window;
        mWindowManager = window.getWindowManager();

        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                // Media window sits behind the main application window
                WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
                // Avoid default to software format RGBA
                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                android.graphics.PixelFormat.TRANSLUCENT);
        params.setTitle(WINDOW_NAME);
        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        params.height = ViewGroup.LayoutParams.MATCH_PARENT;

        View backgroundView = LayoutInflater.from(mContext).inflate(
                R.layout.lb_background_window, null);
        mWindowManager.addView(backgroundView, params);

        attachToView(backgroundView);
    
private voidattachToView(android.view.View sceneRoot)

        mBgView = sceneRoot;
        mAttached = true;
        syncWithService();
    
private android.graphics.drawable.DrawablecreateEmptyDrawable()

        Bitmap bitmap = null;
        return new BitmapDrawable(mContext.getResources(), bitmap);
    
private voidcreateFragment(android.app.Activity activity)

        // Use a fragment to ensure the background manager gets detached properly.
        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
                .findFragmentByTag(FRAGMENT_TAG);
        if (fragment == null) {
            fragment = new BackgroundFragment();
            activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit();
        } else {
            if (fragment.getBackgroundManager() != null) {
                throw new IllegalStateException("Created duplicated BackgroundManager for same " +
                        "activity, please use getInstance() instead");
            }
        }
        fragment.setBackgroundManager(this);
    
private voidcreateSupportFragment(android.support.v4.app.FragmentActivity activity)

        // Use a fragment to ensure the background manager gets detached properly.
        BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
                .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
        if (fragment == null) {
            fragment = new BackgroundSupportFragment();
            activity.getSupportFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG)
                    .commit();
        } else {
            if (fragment.getBackgroundManager() != null) {
                throw new IllegalStateException("Created duplicated BackgroundManager for same " +
                    "activity, please use getInstance() instead");
            }
        }
        fragment.setBackgroundManager(this);
    
voiddetach()
Release references to Drawables and put the BackgroundManager into the detached state. Called when the associated Activity is destroyed.

hide

        if (DEBUG) Log.v(TAG, "detach " + this);
        release();

        if (mWindowManager != null && mBgView != null) {
            mWindowManager.removeViewImmediate(mBgView);
        }

        mWindowManager = null;
        mWindow = null;
        mBgView = null;
        mAttached = false;

        if (mService != null) {
            mService.unref();
            mService = null;
        }
    
public final intgetColor()
Returns the current background color.


              
        
        return mBackgroundColor;
    
public android.graphics.drawable.DrawablegetDrawable()
Returns the current background {@link Drawable}.

        return mBackgroundDrawable;
    
public static android.support.v17.leanback.app.BackgroundManagergetInstance(android.app.Activity activity)
Get the BackgroundManager associated with the Activity.

The BackgroundManager will be created on-demand for each individual Activity. Subsequent calls will return the same BackgroundManager created for this Activity.

        if (activity instanceof FragmentActivity) {
            return getSupportInstance((FragmentActivity) activity);
        }
        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
                .findFragmentByTag(FRAGMENT_TAG);
        if (fragment != null) {
            BackgroundManager manager = fragment.getBackgroundManager();
            if (manager != null) {
                return manager;
            }
            // manager is null: this is a fragment restored by FragmentManager,
            // fall through to create a BackgroundManager attach to it.
        }
        return new BackgroundManager(activity, false);
    
private static android.support.v17.leanback.app.BackgroundManagergetSupportInstance(android.support.v4.app.FragmentActivity activity)

        BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
                .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
        if (fragment != null) {
            BackgroundManager manager = fragment.getBackgroundManager();
            if (manager != null) {
                return manager;
            }
            // manager is null: this is a fragment restored by FragmentManager,
            // fall through to create a BackgroundManager attach to it.
        }
        return new BackgroundManager(activity, true);
    
private android.graphics.drawable.DrawablegetThemeDrawable()

        Drawable drawable = null;
        if (mThemeDrawableResourceId != -1) {
            drawable = mService.getThemeDrawable(mContext, mThemeDrawableResourceId);
        }
        if (drawable == null) {
            drawable = createEmptyDrawable();
        }
        return drawable;
    
private voidlazyInit()

        if (mLayerDrawable != null) {
            return;
        }

        mLayerDrawable = (LayerDrawable) ContextCompat.getDrawable(mContext,
                R.drawable.lb_background).mutate();
        mBgView.setBackground(mLayerDrawable);

        mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());

        mDimWrapper = new DrawableWrapper(
                mLayerDrawable.findDrawableByLayerId(R.id.background_dim));

        mLayerWrapper = new DrawableWrapper(mLayerDrawable);

        mColorWrapper = new DrawableWrapper(
                mLayerDrawable.findDrawableByLayerId(R.id.background_color));
    
voidonActivityResume()
Synchronizes state when the owning Activity is resumed.

        if (mService == null) {
            return;
        }
        if (mLayerDrawable == null) {
            if (DEBUG) Log.v(TAG, "onActivityResume " + this +
                    " released state, syncing with service");
            syncWithService();
        } else {
            if (DEBUG) Log.v(TAG, "onActivityResume " + this + " updating service color "
                    + mBackgroundColor + " drawable " + mBackgroundDrawable);
            mService.setColor(mBackgroundColor);
            mService.setDrawable(mBackgroundDrawable);
        }
    
public voidrelease()
Release references to Drawables. Typically called to reduce memory overhead when not visible.

When an Activity is resumed, if the BackgroundManager has not been released, the continuity service is updated from the BackgroundManager state. If the BackgroundManager was released, the BackgroundManager inherits the current state from the continuity service.

        if (DEBUG) Log.v(TAG, "release " + this);
        if (mLayerDrawable != null) {
            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
            mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
            mLayerDrawable = null;
        }
        mLayerWrapper = null;
        mImageInWrapper = null;
        mImageOutWrapper = null;
        mColorWrapper = null;
        mDimWrapper = null;
        mThemeDrawable = null;
        if (mChangeRunnable != null) {
            mChangeRunnable.cancel();
            mChangeRunnable = null;
        }
        releaseBackgroundBitmap();
    
private voidreleaseBackgroundBitmap()

        mBackgroundDrawable = null;
    
private booleansameDrawable(android.graphics.drawable.Drawable first, android.graphics.drawable.Drawable second)

        if (first == null || second == null) {
            return false;
        }
        if (first == second) {
            return true;
        }
        if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) {
            if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) {
                return true;
            }
        }
        return false;
    
public voidsetBitmap(android.graphics.Bitmap bitmap)
Set the given bitmap into the background. When using setBitmap to set the background, the provided bitmap will be scaled and cropped to correctly fit within the dimensions of the view. The timing for when this becomes visible in the app is undefined and may take place after a small delay.

        if (DEBUG) {
            Log.v(TAG, "setBitmap " + bitmap);
        }

        if (bitmap == null) {
            setDrawableInternal(null);
            return;
        }

        if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
            if (DEBUG) {
                Log.v(TAG, "invalid bitmap width or height");
            }
            return;
        }

        Matrix matrix = null;

        if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
            int dwidth = bitmap.getWidth();
            int dheight = bitmap.getHeight();
            float scale;

            // Scale proportionately to fit width and height.
            if (dwidth * mHeightPx > mWidthPx * dheight) {
                scale = (float) mHeightPx / (float) dheight;
            } else {
                scale = (float) mWidthPx / (float) dwidth;
            }

            int subX = Math.min((int) (mWidthPx / scale), dwidth);
            int dx = Math.max(0, (dwidth - subX) / 2);

            matrix = new Matrix();
            matrix.setScale(scale, scale);
            matrix.preTranslate(-dx, 0);

            if (DEBUG) Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight() +
                    " scale " + scale + " dx " + dx);
        }

        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix);

        setDrawableInternal(bitmapDrawable);
    
public voidsetColor(int color)
Set the background to the given color. The timing for when this becomes visible in the app is undefined and may take place after a small delay.

        if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color));

        mBackgroundColor = color;
        mService.setColor(mBackgroundColor);

        if (mColorWrapper != null) {
            mColorWrapper.setColor(mBackgroundColor);
        }
    
public voidsetDrawable(android.graphics.drawable.Drawable drawable)
Set the given drawable into the background. The provided Drawable will be used unmodified as the background, without any scaling or cropping applied to it. The timing for when this becomes visible in the app is undefined and may take place after a small delay.

        if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable);
        setDrawableInternal(drawable);
    
private voidsetDrawableInternal(android.graphics.drawable.Drawable drawable)

        if (!mAttached) {
            throw new IllegalStateException("Must attach before setting background drawable");
        }

        if (mChangeRunnable != null) {
            if (sameDrawable(drawable, mChangeRunnable.mDrawable)) {
                if (DEBUG) Log.v(TAG, "setting same drawable");
                return;
            }
            mChangeRunnable.cancel();
        }
        mChangeRunnable = new ChangeBackgroundRunnable(drawable);

        if (mImageInWrapper != null && mImageInWrapper.isAnimationStarted()) {
            if (DEBUG) Log.v(TAG, "animation in progress");
        } else {
            mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
        }
    
private voidshowWallpaper(boolean show)

        if (mWindow == null) {
            return;
        }

        WindowManager.LayoutParams layoutParams = mWindow.getAttributes();
        if (show) {
            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
                return;
            }
            if (DEBUG) Log.v(TAG, "showing wallpaper");
            layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
        } else {
            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) {
                return;
            }
            if (DEBUG) Log.v(TAG, "hiding wallpaper");
            layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
        }

        mWindow.setAttributes(layoutParams);
    
private voidsyncWithService()

        int color = mService.getColor();
        Drawable drawable = mService.getDrawable();

        if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color)
                + " drawable " + drawable);

        mBackgroundColor = color;
        mBackgroundDrawable = drawable == null ? null :
            drawable.getConstantState().newDrawable().mutate();

        updateImmediate();
    
private voidupdateImmediate()

        lazyInit();

        mColorWrapper.setColor(mBackgroundColor);
        if (mDimWrapper != null) {
            mDimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID);
        }
        showWallpaper(mBackgroundColor == Color.TRANSPARENT);

        mThemeDrawable = getThemeDrawable();
        mLayerDrawable.setDrawableByLayerId(R.id.background_theme, mThemeDrawable);

        if (mBackgroundDrawable == null) {
            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
        } else {
            if (DEBUG) Log.v(TAG, "Background drawable is available");
            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
            if (mDimWrapper != null) {
                mDimWrapper.setAlpha(FULL_ALPHA);
            }
        }