1/*
2 * Copyright (C) 2008 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 com.android.launcher2;
18
19import java.util.Random;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.graphics.Bitmap;
24import android.graphics.BlurMaskFilter;
25import android.graphics.Canvas;
26import android.graphics.ColorMatrix;
27import android.graphics.ColorMatrixColorFilter;
28import android.graphics.Paint;
29import android.graphics.PaintFlagsDrawFilter;
30import android.graphics.PorterDuff;
31import android.graphics.Rect;
32import android.graphics.drawable.BitmapDrawable;
33import android.graphics.drawable.Drawable;
34import android.graphics.drawable.PaintDrawable;
35import android.util.DisplayMetrics;
36
37import com.android.launcher.R;
38
39/**
40 * Various utilities shared amongst the Launcher's classes.
41 */
42final class Utilities {
43    @SuppressWarnings("unused")
44    private static final String TAG = "Launcher.Utilities";
45
46    private static int sIconWidth = -1;
47    private static int sIconHeight = -1;
48    private static int sIconTextureWidth = -1;
49    private static int sIconTextureHeight = -1;
50
51    private static final Paint sBlurPaint = new Paint();
52    private static final Paint sGlowColorPressedPaint = new Paint();
53    private static final Paint sGlowColorFocusedPaint = new Paint();
54    private static final Paint sDisabledPaint = new Paint();
55    private static final Rect sOldBounds = new Rect();
56    private static final Canvas sCanvas = new Canvas();
57
58    static {
59        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
60                Paint.FILTER_BITMAP_FLAG));
61    }
62    static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
63    static int sColorIndex = 0;
64
65    /**
66     * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
67     * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
68     * to the proper size (48dp)
69     */
70    static Bitmap createIconBitmap(Bitmap icon, Context context) {
71        int textureWidth = sIconTextureWidth;
72        int textureHeight = sIconTextureHeight;
73        int sourceWidth = icon.getWidth();
74        int sourceHeight = icon.getHeight();
75        if (sourceWidth > textureWidth && sourceHeight > textureHeight) {
76            // Icon is bigger than it should be; clip it (solves the GB->ICS migration case)
77            return Bitmap.createBitmap(icon,
78                    (sourceWidth - textureWidth) / 2,
79                    (sourceHeight - textureHeight) / 2,
80                    textureWidth, textureHeight);
81        } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) {
82            // Icon is the right size, no need to change it
83            return icon;
84        } else {
85            // Icon is too small, render to a larger bitmap
86            final Resources resources = context.getResources();
87            return createIconBitmap(new BitmapDrawable(resources, icon), context);
88        }
89    }
90
91    /**
92     * Returns a bitmap suitable for the all apps view.
93     */
94    static Bitmap createIconBitmap(Drawable icon, Context context) {
95        synchronized (sCanvas) { // we share the statics :-(
96            if (sIconWidth == -1) {
97                initStatics(context);
98            }
99
100            int width = sIconWidth;
101            int height = sIconHeight;
102
103            if (icon instanceof PaintDrawable) {
104                PaintDrawable painter = (PaintDrawable) icon;
105                painter.setIntrinsicWidth(width);
106                painter.setIntrinsicHeight(height);
107            } else if (icon instanceof BitmapDrawable) {
108                // Ensure the bitmap has a density.
109                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
110                Bitmap bitmap = bitmapDrawable.getBitmap();
111                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
112                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
113                }
114            }
115            int sourceWidth = icon.getIntrinsicWidth();
116            int sourceHeight = icon.getIntrinsicHeight();
117            if (sourceWidth > 0 && sourceHeight > 0) {
118                // There are intrinsic sizes.
119                if (width < sourceWidth || height < sourceHeight) {
120                    // It's too big, scale it down.
121                    final float ratio = (float) sourceWidth / sourceHeight;
122                    if (sourceWidth > sourceHeight) {
123                        height = (int) (width / ratio);
124                    } else if (sourceHeight > sourceWidth) {
125                        width = (int) (height * ratio);
126                    }
127                } else if (sourceWidth < width && sourceHeight < height) {
128                    // Don't scale up the icon
129                    width = sourceWidth;
130                    height = sourceHeight;
131                }
132            }
133
134            // no intrinsic size --> use default size
135            int textureWidth = sIconTextureWidth;
136            int textureHeight = sIconTextureHeight;
137
138            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
139                    Bitmap.Config.ARGB_8888);
140            final Canvas canvas = sCanvas;
141            canvas.setBitmap(bitmap);
142
143            final int left = (textureWidth-width) / 2;
144            final int top = (textureHeight-height) / 2;
145
146            @SuppressWarnings("all") // suppress dead code warning
147            final boolean debug = false;
148            if (debug) {
149                // draw a big box for the icon for debugging
150                canvas.drawColor(sColors[sColorIndex]);
151                if (++sColorIndex >= sColors.length) sColorIndex = 0;
152                Paint debugPaint = new Paint();
153                debugPaint.setColor(0xffcccc00);
154                canvas.drawRect(left, top, left+width, top+height, debugPaint);
155            }
156
157            sOldBounds.set(icon.getBounds());
158            icon.setBounds(left, top, left+width, top+height);
159            icon.draw(canvas);
160            icon.setBounds(sOldBounds);
161            canvas.setBitmap(null);
162
163            return bitmap;
164        }
165    }
166
167    static void drawSelectedAllAppsBitmap(Canvas dest, int destWidth, int destHeight,
168            boolean pressed, Bitmap src) {
169        synchronized (sCanvas) { // we share the statics :-(
170            if (sIconWidth == -1) {
171                // We can't have gotten to here without src being initialized, which
172                // comes from this file already.  So just assert.
173                //initStatics(context);
174                throw new RuntimeException("Assertion failed: Utilities not initialized");
175            }
176
177            dest.drawColor(0, PorterDuff.Mode.CLEAR);
178
179            int[] xy = new int[2];
180            Bitmap mask = src.extractAlpha(sBlurPaint, xy);
181
182            float px = (destWidth - src.getWidth()) / 2;
183            float py = (destHeight - src.getHeight()) / 2;
184            dest.drawBitmap(mask, px + xy[0], py + xy[1],
185                    pressed ? sGlowColorPressedPaint : sGlowColorFocusedPaint);
186
187            mask.recycle();
188        }
189    }
190
191    /**
192     * Returns a Bitmap representing the thumbnail of the specified Bitmap.
193     * The size of the thumbnail is defined by the dimension
194     * android.R.dimen.launcher_application_icon_size.
195     *
196     * @param bitmap The bitmap to get a thumbnail of.
197     * @param context The application's context.
198     *
199     * @return A thumbnail for the specified bitmap or the bitmap itself if the
200     *         thumbnail could not be created.
201     */
202    static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) {
203        synchronized (sCanvas) { // we share the statics :-(
204            if (sIconWidth == -1) {
205                initStatics(context);
206            }
207
208            if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) {
209                return bitmap;
210            } else {
211                final Resources resources = context.getResources();
212                return createIconBitmap(new BitmapDrawable(resources, bitmap), context);
213            }
214        }
215    }
216
217    static Bitmap drawDisabledBitmap(Bitmap bitmap, Context context) {
218        synchronized (sCanvas) { // we share the statics :-(
219            if (sIconWidth == -1) {
220                initStatics(context);
221            }
222            final Bitmap disabled = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
223                    Bitmap.Config.ARGB_8888);
224            final Canvas canvas = sCanvas;
225            canvas.setBitmap(disabled);
226
227            canvas.drawBitmap(bitmap, 0.0f, 0.0f, sDisabledPaint);
228
229            canvas.setBitmap(null);
230
231            return disabled;
232        }
233    }
234
235    private static void initStatics(Context context) {
236        final Resources resources = context.getResources();
237        final DisplayMetrics metrics = resources.getDisplayMetrics();
238        final float density = metrics.density;
239
240        sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size);
241        sIconTextureWidth = sIconTextureHeight = sIconWidth;
242
243        sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL));
244        sGlowColorPressedPaint.setColor(0xffffc300);
245        sGlowColorFocusedPaint.setColor(0xffff8e00);
246
247        ColorMatrix cm = new ColorMatrix();
248        cm.setSaturation(0.2f);
249        sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm));
250        sDisabledPaint.setAlpha(0x88);
251    }
252
253    /** Only works for positive numbers. */
254    static int roundToPow2(int n) {
255        int orig = n;
256        n >>= 1;
257        int mask = 0x8000000;
258        while (mask != 0 && (n & mask) == 0) {
259            mask >>= 1;
260        }
261        while (mask != 0) {
262            n |= mask;
263            mask >>= 1;
264        }
265        n += 1;
266        if (n != orig) {
267            n <<= 1;
268        }
269        return n;
270    }
271
272    static int generateRandomId() {
273        return new Random(System.currentTimeMillis()).nextInt(1 << 24);
274    }
275}
276