BackgroundManager.java revision 0eb4595e6a4d6e9ebc9f5b8065e0c787d45a0f70
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.app;
15
16import android.support.v17.leanback.R;
17import android.animation.ObjectAnimator;
18import android.app.Activity;
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.ColorFilter;
26import android.graphics.Matrix;
27import android.graphics.Paint;
28import android.graphics.drawable.ColorDrawable;
29import android.graphics.drawable.Drawable;
30import android.graphics.drawable.LayerDrawable;
31import android.os.Handler;
32import android.util.Log;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.view.ViewGroup;
36import android.view.Window;
37import android.view.WindowManager;
38import android.view.animation.LinearInterpolator;
39
40/**
41 * Supports background image continuity between multiple Activities.
42 *
43 * <p>An Activity should instantiate a BackgroundManager and {@link #attach}
44 * to the Activity's window.  When the Activity is started, the background is
45 * initialized to the current background values stored in a continuity service.
46 * The background continuity service is updated as the background is updated.
47 *
48 * <p>At some point, for example when it is stopped, the Activity may release
49 * its background state.
50 *
51 * <p>When an Activity is resumed, if the BackgroundManager has not been
52 * released, the continuity service is updated from the BackgroundManager state.
53 * If the BackgroundManager was released, the BackgroundManager inherits the
54 * current state from the continuity service.
55 *
56 * <p>When the last Activity is destroyed, the background state is reset.
57 *
58 * <p>Backgrounds consist of several layers, from back to front:
59 * <ul>
60 *   <li>the background Drawable of the theme</li>
61 *   <li>a solid color (set via {@link #setColor})</li>
62 *   <li>two Drawables, previous and current (set via {@link #setBitmap} or
63 *   {@link #setDrawable}), which may be in transition</li>
64 * </ul>
65 *
66 * <p>BackgroundManager holds references to potentially large bitmap Drawables.
67 * Call {@link #release} to release these references when the Activity is not
68 * visible.
69 */
70// TODO: support for multiple app processes requires a proper android service
71// instead of the shared memory "service" implemented here. Such a service could
72// support continuity between fragments of different applications if desired.
73public final class BackgroundManager {
74    private static final String TAG = "BackgroundManager";
75    private static final boolean DEBUG = false;
76
77    private static final int FULL_ALPHA = 255;
78    private static final int DIM_ALPHA_ON_SOLID = (int) (0.8f * FULL_ALPHA);
79    private static final int CHANGE_BG_DELAY_MS = 500;
80    private static final int FADE_DURATION_QUICK = 200;
81    private static final int FADE_DURATION_SLOW = 1000;
82
83    /**
84     * Using a separate window for backgrounds can improve graphics performance by
85     * leveraging hardware display layers.
86     * TODO: support a leanback configuration option.
87     */
88    private static final boolean USE_SEPARATE_WINDOW = false;
89
90    private static final String WINDOW_NAME = "BackgroundManager";
91    private static final String FRAGMENT_TAG = BackgroundManager.class.getCanonicalName();
92
93    private Context mContext;
94    private Handler mHandler;
95    private Window mWindow;
96    private WindowManager mWindowManager;
97    private View mBgView;
98    private BackgroundContinuityService mService;
99    private int mThemeDrawableResourceId;
100
101    private int mHeightPx;
102    private int mWidthPx;
103    private Drawable mBackgroundDrawable;
104    private int mBackgroundColor;
105    private boolean mAttached;
106
107    private static class BitmapDrawable extends Drawable {
108
109        Bitmap mBitmap;
110        Matrix mMatrix;
111        Paint mPaint;
112
113        BitmapDrawable(Resources resources, Bitmap bitmap) {
114            this(resources, bitmap, null);
115        }
116
117        BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) {
118            mBitmap = bitmap;
119            mMatrix = matrix != null ? matrix : new Matrix();
120            mPaint = new Paint();
121            mPaint.setFilterBitmap(true);
122        }
123
124        Bitmap getBitmap() {
125            return mBitmap;
126        }
127
128        @Override
129        public void draw(Canvas canvas) {
130            if (mBitmap == null) {
131                return;
132            }
133            canvas.drawBitmap(mBitmap, mMatrix, mPaint);
134        }
135
136        @Override
137        public int getOpacity() {
138            return android.graphics.PixelFormat.OPAQUE;
139        }
140
141        @Override
142        public void setAlpha(int alpha) {
143            if (mPaint.getAlpha() != alpha) {
144                mPaint.setAlpha(alpha);
145                invalidateSelf();
146            }
147        }
148
149        @Override
150        public void setColorFilter(ColorFilter cf) {
151            // Abstract in Drawable, not implemented
152        }
153    }
154
155    private static class DrawableWrapper {
156        protected int mAlpha;
157        protected Drawable mDrawable;
158        protected ObjectAnimator mAnimator;
159        protected boolean mAnimationPending;
160
161        public DrawableWrapper(Drawable drawable) {
162            mDrawable = drawable;
163            setAlpha(FULL_ALPHA);
164        }
165
166        public Drawable getDrawable() {
167            return mDrawable;
168        }
169        public void setAlpha(int alpha) {
170            mAlpha = alpha;
171            mDrawable.setAlpha(alpha);
172        }
173        public int getAlpha() {
174            return mAlpha;
175        }
176        public void setColor(int color) {
177            ((ColorDrawable) mDrawable).setColor(color);
178        }
179        public void fadeIn(int durationMs, int delayMs) {
180            fade(durationMs, delayMs, FULL_ALPHA);
181        }
182        public void fadeOut(int durationMs) {
183            fade(durationMs, 0, 0);
184        }
185        public void fade(int durationMs, int delayMs, int alpha) {
186            if (mAnimator != null && mAnimator.isStarted()) {
187                mAnimator.cancel();
188            }
189            mAnimator = ObjectAnimator.ofInt(this, "alpha", alpha);
190            mAnimator.setInterpolator(new LinearInterpolator());
191            mAnimator.setDuration(durationMs);
192            mAnimator.setStartDelay(delayMs);
193            mAnimationPending = true;
194        }
195        public boolean isAnimationPending() {
196            return mAnimationPending;
197        }
198        public boolean isAnimationStarted() {
199            return mAnimator != null && mAnimator.isStarted();
200        }
201        public void startAnimation() {
202            mAnimator.start();
203            mAnimationPending = false;
204        }
205    }
206
207    private LayerDrawable mLayerDrawable;
208    private DrawableWrapper mLayerWrapper;
209    private DrawableWrapper mImageInWrapper;
210    private DrawableWrapper mImageOutWrapper;
211    private DrawableWrapper mColorWrapper;
212    private DrawableWrapper mDimWrapper;
213
214    private Drawable mThemeDrawable;
215    private ChangeBackgroundRunnable mChangeRunnable;
216
217    /**
218     * Shared memory continuity service.
219     */
220    private static class BackgroundContinuityService {
221        private static final String TAG = "BackgroundContinuityService";
222        private static boolean DEBUG = BackgroundManager.DEBUG;
223
224        private static BackgroundContinuityService sService = new BackgroundContinuityService();
225
226        private int mColor;
227        private Drawable mDrawable;
228        private int mCount;
229
230        private BackgroundContinuityService() {
231            reset();
232        }
233
234        private void reset() {
235            mColor = Color.TRANSPARENT;
236            mDrawable = null;
237        }
238
239        public static BackgroundContinuityService getInstance() {
240            final int count = sService.mCount++;
241            if (DEBUG) Log.v(TAG, "Returning instance with new count " + count);
242            return sService;
243        }
244
245        public void unref() {
246            if (mCount <= 0) throw new IllegalStateException("Can't unref, count " + mCount);
247            if (--mCount == 0) {
248                if (DEBUG) Log.v(TAG, "mCount is zero, resetting");
249                reset();
250            }
251        }
252        public int getColor() {
253            return mColor;
254        }
255        public Drawable getDrawable() {
256            return mDrawable;
257        }
258        public void setColor(int color) {
259            mColor = color;
260        }
261        public void setDrawable(Drawable drawable) {
262            mDrawable = drawable;
263        }
264    }
265
266    private Drawable getThemeDrawable() {
267        Drawable drawable = null;
268        if (mThemeDrawableResourceId != -1) {
269            drawable = mContext.getResources().getDrawable(mThemeDrawableResourceId);
270        }
271        if (drawable == null) {
272            drawable = createEmptyDrawable();
273        }
274        return drawable;
275    }
276
277    /**
278     * Get the BackgroundManager associated with the Activity.
279     * <p>
280     * The BackgroundManager will be created on-demand for each individual
281     * Activity. Subsequent calls will return the same BackgroundManager created
282     * for this Activity.
283     */
284    public static BackgroundManager getInstance(Activity activity) {
285        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
286                .findFragmentByTag(FRAGMENT_TAG);
287        if (fragment != null) {
288            BackgroundManager manager = fragment.getBackgroundManager();
289            if (manager != null) {
290                return manager;
291            }
292            // manager is null: this is a fragment restored by FragmentManager,
293            // fall through to create a BackgroundManager attach to it.
294        }
295        return new BackgroundManager(activity);
296    }
297
298    /**
299     * Construct a BackgroundManager instance. The Initial background is set
300     * from the continuity service.
301     * @deprecated Use getInstance(Activity).
302     */
303    @Deprecated
304    public BackgroundManager(Activity activity) {
305        mContext = activity;
306        mService = BackgroundContinuityService.getInstance();
307        mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
308        mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels;
309        mHandler = new Handler();
310
311        TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
312                android.R.attr.windowBackground });
313        mThemeDrawableResourceId = ta.getResourceId(0, -1);
314        if (mThemeDrawableResourceId < 0) {
315            if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!");
316        }
317        ta.recycle();
318
319        createFragment(activity);
320    }
321
322    private void createFragment(Activity activity) {
323        // Use a fragment to ensure the background manager gets detached properly.
324        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
325                .findFragmentByTag(FRAGMENT_TAG);
326        if (fragment == null) {
327            fragment = new BackgroundFragment();
328            activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit();
329        } else {
330            if (fragment.getBackgroundManager() != null) {
331                throw new IllegalStateException("Created duplicated BackgroundManager for same " +
332                        "activity, please use getInstance() instead");
333            }
334        }
335        fragment.setBackgroundManager(this);
336    }
337
338    /**
339     * Synchronizes state when the owning Activity is resumed.
340     */
341    void onActivityResume() {
342        if (mService == null) {
343            return;
344        }
345        if (mLayerDrawable == null) {
346            if (DEBUG) Log.v(TAG, "onActivityResume: released state, syncing with service");
347            syncWithService();
348        } else {
349            if (DEBUG) Log.v(TAG, "onActivityResume: updating service color "
350                    + mBackgroundColor + " drawable " + mBackgroundDrawable);
351            mService.setColor(mBackgroundColor);
352            mService.setDrawable(mBackgroundDrawable);
353        }
354    }
355
356    private void syncWithService() {
357        int color = mService.getColor();
358        Drawable drawable = mService.getDrawable();
359
360        if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color)
361                + " drawable " + drawable);
362
363        mBackgroundColor = color;
364        mBackgroundDrawable = drawable;
365
366        updateImmediate();
367    }
368
369    private void lazyInit() {
370        if (mLayerDrawable != null) {
371            return;
372        }
373
374        mLayerDrawable = (LayerDrawable) mContext.getResources().getDrawable(
375                R.drawable.lb_background).mutate();
376        mBgView.setBackground(mLayerDrawable);
377
378        mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
379
380        mDimWrapper = new DrawableWrapper(
381                mLayerDrawable.findDrawableByLayerId(R.id.background_dim));
382
383        mLayerWrapper = new DrawableWrapper(mLayerDrawable);
384
385        mColorWrapper = new DrawableWrapper(
386                mLayerDrawable.findDrawableByLayerId(R.id.background_color));
387    }
388
389    /**
390     * Make the background visible on the given Window.
391     */
392    public void attach(Window window) {
393        if (USE_SEPARATE_WINDOW) {
394            attachBehindWindow(window);
395        } else {
396            attachToView(window.getDecorView());
397        }
398    }
399
400    private void attachBehindWindow(Window window) {
401        if (DEBUG) Log.v(TAG, "attachBehindWindow " + window);
402        mWindow = window;
403        mWindowManager = window.getWindowManager();
404
405        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
406                // Media window sits behind the main application window
407                WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
408                // Avoid default to software format RGBA
409                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
410                android.graphics.PixelFormat.TRANSLUCENT);
411        params.setTitle(WINDOW_NAME);
412        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
413        params.height = ViewGroup.LayoutParams.MATCH_PARENT;
414
415        View backgroundView = LayoutInflater.from(mContext).inflate(
416                R.layout.lb_background_window, null);
417        mWindowManager.addView(backgroundView, params);
418
419        attachToView(backgroundView);
420    }
421
422    private void attachToView(View sceneRoot) {
423        mBgView = sceneRoot;
424        mAttached = true;
425        syncWithService();
426    }
427
428    /**
429     * Release references to Drawables and put the BackgroundManager into the
430     * detached state. Called when the associated Activity is destroyed.
431     * @hide
432     */
433    void detach() {
434        if (DEBUG) Log.v(TAG, "detach");
435        release();
436
437        if (mWindowManager != null && mBgView != null) {
438            mWindowManager.removeViewImmediate(mBgView);
439        }
440
441        mWindowManager = null;
442        mWindow = null;
443        mBgView = null;
444        mAttached = false;
445
446        if (mService != null) {
447            mService.unref();
448            mService = null;
449        }
450    }
451
452    /**
453     * Release references to Drawables. Typically called to reduce memory
454     * overhead when not visible.
455     * <p>
456     * When an Activity is resumed, if the BackgroundManager has not been
457     * released, the continuity service is updated from the BackgroundManager
458     * state. If the BackgroundManager was released, the BackgroundManager
459     * inherits the current state from the continuity service.
460     */
461    public void release() {
462        if (DEBUG) Log.v(TAG, "release");
463        if (mLayerDrawable != null) {
464            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
465            mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
466            mLayerDrawable = null;
467        }
468        mLayerWrapper = null;
469        mImageInWrapper = null;
470        mImageOutWrapper = null;
471        mColorWrapper = null;
472        mDimWrapper = null;
473        mThemeDrawable = null;
474        if (mChangeRunnable != null) {
475            mChangeRunnable.cancel();
476            mChangeRunnable = null;
477        }
478        releaseBackgroundBitmap();
479    }
480
481    private void releaseBackgroundBitmap() {
482        mBackgroundDrawable = null;
483    }
484
485    private void updateImmediate() {
486        lazyInit();
487
488        mColorWrapper.setColor(mBackgroundColor);
489        if (mDimWrapper != null) {
490            mDimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID);
491        }
492        showWallpaper(mBackgroundColor == Color.TRANSPARENT);
493
494        mThemeDrawable = getThemeDrawable();
495        mLayerDrawable.setDrawableByLayerId(R.id.background_theme, mThemeDrawable);
496
497        if (mBackgroundDrawable == null) {
498            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
499        } else {
500            if (DEBUG) Log.v(TAG, "Background drawable is available");
501            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
502            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
503            if (mDimWrapper != null) {
504                mDimWrapper.setAlpha(FULL_ALPHA);
505            }
506        }
507    }
508
509    /**
510     * Set the background to the given color. The timing for when this becomes
511     * visible in the app is undefined and may take place after a small delay.
512     */
513    public void setColor(int color) {
514        if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color));
515
516        mBackgroundColor = color;
517        mService.setColor(mBackgroundColor);
518
519        if (mColorWrapper != null) {
520            mColorWrapper.setColor(mBackgroundColor);
521        }
522    }
523
524    /**
525     * Set the given drawable into the background. The provided Drawable will be
526     * used unmodified as the background, without any scaling or cropping
527     * applied to it. The timing for when this becomes visible in the app is
528     * undefined and may take place after a small delay.
529     */
530    public void setDrawable(Drawable drawable) {
531        if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable);
532        setDrawableInternal(drawable);
533    }
534
535    private void setDrawableInternal(Drawable drawable) {
536        if (!mAttached) {
537            throw new IllegalStateException("Must attach before setting background drawable");
538        }
539
540        if (mChangeRunnable != null) {
541            mChangeRunnable.cancel();
542        }
543        mChangeRunnable = new ChangeBackgroundRunnable(drawable);
544
545        mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
546    }
547
548    /**
549     * Set the given bitmap into the background. When using setBitmap to set the
550     * background, the provided bitmap will be scaled and cropped to correctly
551     * fit within the dimensions of the view. The timing for when this becomes
552     * visible in the app is undefined and may take place after a small delay.
553     */
554    public void setBitmap(Bitmap bitmap) {
555        if (DEBUG) {
556            Log.v(TAG, "setBitmap " + bitmap);
557        }
558
559        if (bitmap == null) {
560            setDrawableInternal(null);
561            return;
562        }
563
564        if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
565            if (DEBUG) {
566                Log.v(TAG, "invalid bitmap width or height");
567            }
568            return;
569        }
570
571        Matrix matrix = null;
572
573        if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
574            int dwidth = bitmap.getWidth();
575            int dheight = bitmap.getHeight();
576            float scale;
577
578            // Scale proportionately to fit width and height.
579            if (dwidth * mHeightPx > mWidthPx * dheight) {
580                scale = (float) mHeightPx / (float) dheight;
581            } else {
582                scale = (float) mWidthPx / (float) dwidth;
583            }
584
585            int subX = Math.min((int) (mWidthPx / scale), dwidth);
586            int dx = Math.max(0, (dwidth - subX) / 2);
587
588            matrix = new Matrix();
589            matrix.setScale(scale, scale);
590            matrix.preTranslate(-dx, 0);
591
592            if (DEBUG) Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight() +
593                    " scale " + scale + " dx " + dx);
594        }
595
596        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix);
597
598        setDrawableInternal(bitmapDrawable);
599    }
600
601    private void applyBackgroundChanges() {
602        if (!mAttached || mLayerWrapper == null) {
603            return;
604        }
605
606        if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable);
607
608        int dimAlpha = 0;
609
610        if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) {
611            if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting");
612            mImageOutWrapper.startAnimation();
613            mImageOutWrapper = null;
614            dimAlpha = DIM_ALPHA_ON_SOLID;
615        }
616
617        if (mImageInWrapper == null && mBackgroundDrawable != null) {
618            if (DEBUG) Log.v(TAG, "creating new imagein drawable");
619            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
620            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
621            if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting");
622            mImageInWrapper.setAlpha(0);
623            mImageInWrapper.fadeIn(FADE_DURATION_SLOW, 0);
624            mImageInWrapper.startAnimation();
625            dimAlpha = FULL_ALPHA;
626        }
627
628        if (mDimWrapper != null && dimAlpha != 0) {
629            if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha);
630            mDimWrapper.fade(FADE_DURATION_SLOW, 0, dimAlpha);
631            mDimWrapper.startAnimation();
632        }
633    }
634
635    /**
636     * Returns the current background color.
637     */
638    public final int getColor() {
639        return mBackgroundColor;
640    }
641
642    /**
643     * Returns the current background {@link Drawable}.
644     */
645    public Drawable getDrawable() {
646        return mBackgroundDrawable;
647    }
648
649    private boolean sameDrawable(Drawable first, Drawable second) {
650        if (first == null || second == null) {
651            return false;
652        }
653        if (first == second) {
654            return true;
655        }
656        if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) {
657            if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) {
658                return true;
659            }
660        }
661        return false;
662    }
663
664    /**
665     * Task which changes the background.
666     */
667    class ChangeBackgroundRunnable implements Runnable {
668        private Drawable mDrawable;
669        private boolean mCancel;
670
671        ChangeBackgroundRunnable(Drawable drawable) {
672            mDrawable = drawable;
673        }
674
675        public void cancel() {
676            mCancel = true;
677        }
678
679        @Override
680        public void run() {
681            if (!mCancel) {
682                runTask();
683            }
684        }
685
686        private void runTask() {
687            lazyInit();
688
689            if (sameDrawable(mDrawable, mBackgroundDrawable)) {
690                if (DEBUG) Log.v(TAG, "same bitmap detected");
691                return;
692            }
693
694            releaseBackgroundBitmap();
695
696            if (mImageInWrapper != null) {
697                mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable());
698                mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha());
699                mImageOutWrapper.fadeOut(FADE_DURATION_QUICK);
700
701                // Order is important! Setting a drawable "removes" the
702                // previous one from the view
703                mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
704                mLayerDrawable.setDrawableByLayerId(R.id.background_imageout,
705                        mImageOutWrapper.getDrawable());
706                mImageInWrapper.setAlpha(0);
707                mImageInWrapper = null;
708            }
709
710            mBackgroundDrawable = mDrawable;
711            mService.setDrawable(mBackgroundDrawable);
712
713            applyBackgroundChanges();
714        }
715    }
716
717    private Drawable createEmptyDrawable() {
718        Bitmap bitmap = null;
719        return new BitmapDrawable(mContext.getResources(), bitmap);
720    }
721
722    private void showWallpaper(boolean show) {
723        if (mWindow == null) {
724            return;
725        }
726
727        WindowManager.LayoutParams layoutParams = mWindow.getAttributes();
728        if (show) {
729            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
730                return;
731            }
732            if (DEBUG) Log.v(TAG, "showing wallpaper");
733            layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
734        } else {
735            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) {
736                return;
737            }
738            if (DEBUG) Log.v(TAG, "hiding wallpaper");
739            layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
740        }
741
742        mWindow.setAttributes(layoutParams);
743    }
744}
745