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.launcher3;
18
19import android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.Resources;
24import android.graphics.Bitmap;
25import android.graphics.BlurMaskFilter;
26import android.graphics.Canvas;
27import android.graphics.ColorMatrix;
28import android.graphics.ColorMatrixColorFilter;
29import android.graphics.Matrix;
30import android.graphics.Paint;
31import android.graphics.PaintFlagsDrawFilter;
32import android.graphics.Rect;
33import android.graphics.drawable.BitmapDrawable;
34import android.graphics.drawable.Drawable;
35import android.graphics.drawable.PaintDrawable;
36import android.util.DisplayMetrics;
37import android.util.Log;
38import android.view.View;
39import android.widget.Toast;
40
41import java.util.ArrayList;
42
43/**
44 * Various utilities shared amongst the Launcher's classes.
45 */
46final class Utilities {
47    private static final String TAG = "Launcher.Utilities";
48
49    private static int sIconWidth = -1;
50    private static int sIconHeight = -1;
51    public static int sIconTextureWidth = -1;
52    public static int sIconTextureHeight = -1;
53
54    private static final Paint sBlurPaint = new Paint();
55    private static final Paint sGlowColorPressedPaint = new Paint();
56    private static final Paint sGlowColorFocusedPaint = new Paint();
57    private static final Paint sDisabledPaint = new Paint();
58    private static final Rect sOldBounds = new Rect();
59    private static final Canvas sCanvas = new Canvas();
60
61    static {
62        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
63                Paint.FILTER_BITMAP_FLAG));
64    }
65    static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
66    static int sColorIndex = 0;
67
68    /**
69     * Returns a FastBitmapDrawable with the icon, accurately sized.
70     */
71    static Drawable createIconDrawable(Bitmap icon) {
72        FastBitmapDrawable d = new FastBitmapDrawable(icon);
73        d.setFilterBitmap(true);
74        resizeIconDrawable(d);
75        return d;
76    }
77
78    /**
79     * Resizes an icon drawable to the correct icon size.
80     */
81    static void resizeIconDrawable(Drawable icon) {
82        icon.setBounds(0, 0, sIconTextureWidth, sIconTextureHeight);
83    }
84
85    /**
86     * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
87     * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
88     * to the proper size (48dp)
89     */
90    static Bitmap createIconBitmap(Bitmap icon, Context context) {
91        int textureWidth = sIconTextureWidth;
92        int textureHeight = sIconTextureHeight;
93        int sourceWidth = icon.getWidth();
94        int sourceHeight = icon.getHeight();
95        if (sourceWidth > textureWidth && sourceHeight > textureHeight) {
96            // Icon is bigger than it should be; clip it (solves the GB->ICS migration case)
97            return Bitmap.createBitmap(icon,
98                    (sourceWidth - textureWidth) / 2,
99                    (sourceHeight - textureHeight) / 2,
100                    textureWidth, textureHeight);
101        } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) {
102            // Icon is the right size, no need to change it
103            return icon;
104        } else {
105            // Icon is too small, render to a larger bitmap
106            final Resources resources = context.getResources();
107            return createIconBitmap(new BitmapDrawable(resources, icon), context);
108        }
109    }
110
111    /**
112     * Returns a bitmap suitable for the all apps view.
113     */
114    static Bitmap createIconBitmap(Drawable icon, Context context) {
115        synchronized (sCanvas) { // we share the statics :-(
116            if (sIconWidth == -1) {
117                initStatics(context);
118            }
119
120            int width = sIconWidth;
121            int height = sIconHeight;
122
123            if (icon instanceof PaintDrawable) {
124                PaintDrawable painter = (PaintDrawable) icon;
125                painter.setIntrinsicWidth(width);
126                painter.setIntrinsicHeight(height);
127            } else if (icon instanceof BitmapDrawable) {
128                // Ensure the bitmap has a density.
129                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
130                Bitmap bitmap = bitmapDrawable.getBitmap();
131                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
132                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
133                }
134            }
135            int sourceWidth = icon.getIntrinsicWidth();
136            int sourceHeight = icon.getIntrinsicHeight();
137            if (sourceWidth > 0 && sourceHeight > 0) {
138                // Scale the icon proportionally to the icon dimensions
139                final float ratio = (float) sourceWidth / sourceHeight;
140                if (sourceWidth > sourceHeight) {
141                    height = (int) (width / ratio);
142                } else if (sourceHeight > sourceWidth) {
143                    width = (int) (height * ratio);
144                }
145            }
146
147            // no intrinsic size --> use default size
148            int textureWidth = sIconTextureWidth;
149            int textureHeight = sIconTextureHeight;
150
151            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
152                    Bitmap.Config.ARGB_8888);
153            final Canvas canvas = sCanvas;
154            canvas.setBitmap(bitmap);
155
156            final int left = (textureWidth-width) / 2;
157            final int top = (textureHeight-height) / 2;
158
159            @SuppressWarnings("all") // suppress dead code warning
160            final boolean debug = false;
161            if (debug) {
162                // draw a big box for the icon for debugging
163                canvas.drawColor(sColors[sColorIndex]);
164                if (++sColorIndex >= sColors.length) sColorIndex = 0;
165                Paint debugPaint = new Paint();
166                debugPaint.setColor(0xffcccc00);
167                canvas.drawRect(left, top, left+width, top+height, debugPaint);
168            }
169
170            sOldBounds.set(icon.getBounds());
171            icon.setBounds(left, top, left+width, top+height);
172            icon.draw(canvas);
173            icon.setBounds(sOldBounds);
174            canvas.setBitmap(null);
175
176            return bitmap;
177        }
178    }
179
180    /**
181     * Returns a Bitmap representing the thumbnail of the specified Bitmap.
182     *
183     * @param bitmap The bitmap to get a thumbnail of.
184     * @param context The application's context.
185     *
186     * @return A thumbnail for the specified bitmap or the bitmap itself if the
187     *         thumbnail could not be created.
188     */
189    static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) {
190        synchronized (sCanvas) { // we share the statics :-(
191            if (sIconWidth == -1) {
192                initStatics(context);
193            }
194
195            if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) {
196                return bitmap;
197            } else {
198                final Resources resources = context.getResources();
199                return createIconBitmap(new BitmapDrawable(resources, bitmap), context);
200            }
201        }
202    }
203
204    /**
205     * Given a coordinate relative to the descendant, find the coordinate in a parent view's
206     * coordinates.
207     *
208     * @param descendant The descendant to which the passed coordinate is relative.
209     * @param root The root view to make the coordinates relative to.
210     * @param coord The coordinate that we want mapped.
211     * @param includeRootScroll Whether or not to account for the scroll of the descendant:
212     *          sometimes this is relevant as in a child's coordinates within the descendant.
213     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
214     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
215     *         assumption fails, we will need to return a pair of scale factors.
216     */
217    public static float getDescendantCoordRelativeToParent(View descendant, View root,
218                                                           int[] coord, boolean includeRootScroll) {
219        ArrayList<View> ancestorChain = new ArrayList<View>();
220
221        float[] pt = {coord[0], coord[1]};
222
223        View v = descendant;
224        while(v != root && v != null) {
225            ancestorChain.add(v);
226            v = (View) v.getParent();
227        }
228        ancestorChain.add(root);
229
230        float scale = 1.0f;
231        int count = ancestorChain.size();
232        for (int i = 0; i < count; i++) {
233            View v0 = ancestorChain.get(i);
234            // For TextViews, scroll has a meaning which relates to the text position
235            // which is very strange... ignore the scroll.
236            if (v0 != descendant || includeRootScroll) {
237                pt[0] -= v0.getScrollX();
238                pt[1] -= v0.getScrollY();
239            }
240
241            v0.getMatrix().mapPoints(pt);
242            pt[0] += v0.getLeft();
243            pt[1] += v0.getTop();
244            scale *= v0.getScaleX();
245        }
246
247        coord[0] = (int) Math.round(pt[0]);
248        coord[1] = (int) Math.round(pt[1]);
249        return scale;
250    }
251
252    /**
253     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
254     */
255    public static float mapCoordInSelfToDescendent(View descendant, View root,
256                                                   int[] coord) {
257        ArrayList<View> ancestorChain = new ArrayList<View>();
258
259        float[] pt = {coord[0], coord[1]};
260
261        View v = descendant;
262        while(v != root) {
263            ancestorChain.add(v);
264            v = (View) v.getParent();
265        }
266        ancestorChain.add(root);
267
268        float scale = 1.0f;
269        Matrix inverse = new Matrix();
270        int count = ancestorChain.size();
271        for (int i = count - 1; i >= 0; i--) {
272            View ancestor = ancestorChain.get(i);
273            View next = i > 0 ? ancestorChain.get(i-1) : null;
274
275            pt[0] += ancestor.getScrollX();
276            pt[1] += ancestor.getScrollY();
277
278            if (next != null) {
279                pt[0] -= next.getLeft();
280                pt[1] -= next.getTop();
281                next.getMatrix().invert(inverse);
282                inverse.mapPoints(pt);
283                scale *= next.getScaleX();
284            }
285        }
286
287        coord[0] = (int) Math.round(pt[0]);
288        coord[1] = (int) Math.round(pt[1]);
289        return scale;
290    }
291
292    private static void initStatics(Context context) {
293        final Resources resources = context.getResources();
294        final DisplayMetrics metrics = resources.getDisplayMetrics();
295        final float density = metrics.density;
296
297        sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size);
298        sIconTextureWidth = sIconTextureHeight = sIconWidth;
299
300        sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL));
301        sGlowColorPressedPaint.setColor(0xffffc300);
302        sGlowColorFocusedPaint.setColor(0xffff8e00);
303
304        ColorMatrix cm = new ColorMatrix();
305        cm.setSaturation(0.2f);
306        sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm));
307        sDisabledPaint.setAlpha(0x88);
308    }
309
310    public static void setIconSize(int widthPx) {
311        sIconWidth = sIconHeight = widthPx;
312        sIconTextureWidth = sIconTextureHeight = widthPx;
313    }
314
315    public static void scaleRect(Rect r, float scale) {
316        if (scale != 1.0f) {
317            r.left = (int) (r.left * scale + 0.5f);
318            r.top = (int) (r.top * scale + 0.5f);
319            r.right = (int) (r.right * scale + 0.5f);
320            r.bottom = (int) (r.bottom * scale + 0.5f);
321        }
322    }
323
324    public static void scaleRectAboutCenter(Rect r, float scale) {
325        int cx = r.centerX();
326        int cy = r.centerY();
327        r.offset(-cx, -cy);
328        Utilities.scaleRect(r, scale);
329        r.offset(cx, cy);
330    }
331
332    public static void startActivityForResultSafely(
333            Activity activity, Intent intent, int requestCode) {
334        try {
335            activity.startActivityForResult(intent, requestCode);
336        } catch (ActivityNotFoundException e) {
337            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
338        } catch (SecurityException e) {
339            Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
340            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
341                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
342                    "or use the exported attribute for this activity.", e);
343        }
344    }
345}
346