1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.ui;
6
7import android.content.Context;
8import android.graphics.Bitmap;
9import android.graphics.Canvas;
10import android.graphics.Rect;
11import android.util.Log;
12import android.view.SurfaceView;
13import android.view.View;
14import android.view.ViewGroup;
15import android.view.inputmethod.InputMethodManager;
16
17/**
18 * Utility functions for common Android UI tasks.
19 * This class is not supposed to be instantiated.
20 */
21public class UiUtils {
22    private static final String TAG = "UiUtils";
23
24    /**
25     * Guards this class from being instantiated.
26     */
27    private UiUtils() {
28    }
29
30    /** The minimum size of the bottom margin below the app to detect a keyboard. */
31    private static final float KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP = 100;
32
33    /**
34     * Shows the software keyboard if necessary.
35     * @param view The currently focused {@link View}, which would receive soft keyboard input.
36     */
37    public static void showKeyboard(View view) {
38        InputMethodManager imm =
39                (InputMethodManager) view.getContext().getSystemService(
40                        Context.INPUT_METHOD_SERVICE);
41        // Only shows soft keyboard if there isn't an open physical keyboard.
42        imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
43    }
44
45    /**
46     * Hides the keyboard.
47     * @param view The {@link View} that is currently accepting input.
48     * @return Whether the keyboard was visible before.
49     */
50    public static boolean hideKeyboard(View view) {
51        InputMethodManager imm =
52                (InputMethodManager) view.getContext().getSystemService(
53                        Context.INPUT_METHOD_SERVICE);
54        return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
55    }
56
57    public static boolean isKeyboardShowing(Context context, View view) {
58        View rootView = view.getRootView();
59        if (rootView == null) return false;
60        Rect appRect = new Rect();
61        rootView.getWindowVisibleDisplayFrame(appRect);
62        final float screenHeight = context.getResources().getDisplayMetrics().heightPixels;
63        final float bottomMargin = Math.abs(appRect.bottom - screenHeight);
64        final float density = context.getResources().getDisplayMetrics().density;
65        return bottomMargin > KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP * density;
66    }
67
68    /**
69     * Inserts a {@link View} into a {@link ViewGroup} after directly before a given {@View}.
70     * @param container The {@link View} to add newView to.
71     * @param newView The new {@link View} to add.
72     * @param existingView The {@link View} to insert the newView before.
73     * @return The index where newView was inserted, or -1 if it was not inserted.
74     */
75    public static int insertBefore(ViewGroup container, View newView, View existingView) {
76        return insertView(container, newView, existingView, false);
77    }
78
79    /**
80     * Inserts a {@link View} into a {@link ViewGroup} after directly after a given {@View}.
81     * @param container The {@link View} to add newView to.
82     * @param newView The new {@link View} to add.
83     * @param existingView The {@link View} to insert the newView after.
84     * @return The index where newView was inserted, or -1 if it was not inserted.
85     */
86    public static int insertAfter(ViewGroup container, View newView, View existingView) {
87        return insertView(container, newView, existingView, true);
88    }
89
90    private static int insertView(
91            ViewGroup container, View newView, View existingView, boolean after) {
92        // See if the view has already been added.
93        int index = container.indexOfChild(newView);
94        if (index >= 0) return index;
95
96        // Find the location of the existing view.
97        index = container.indexOfChild(existingView);
98        if (index < 0) return -1;
99
100        // Add the view.
101        if (after) index++;
102        container.addView(newView, index);
103        return index;
104    }
105
106    /**
107     * Generates a scaled screenshot of the given view.  The maximum size of the screenshot is
108     * determined by maximumDimension.
109     *
110     * @param currentView      The view to generate a screenshot of.
111     * @param maximumDimension The maximum width or height of the generated screenshot.  The bitmap
112     *                         will be scaled to ensure the maximum width or height is equal to or
113     *                         less than this.  Any value <= 0, will result in no scaling.
114     * @param bitmapConfig     Bitmap config for the generated screenshot (ARGB_8888 or RGB_565).
115     * @return The screen bitmap of the view or null if a problem was encountered.
116     */
117    public static Bitmap generateScaledScreenshot(
118            View currentView, int maximumDimension, Bitmap.Config bitmapConfig) {
119        Bitmap screenshot = null;
120        boolean drawingCacheEnabled = currentView.isDrawingCacheEnabled();
121        try {
122            prepareViewHierarchyForScreenshot(currentView, true);
123            if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(true);
124            // Android has a maximum drawing cache size and if the drawing cache is bigger
125            // than that, getDrawingCache() returns null.
126            Bitmap originalBitmap = currentView.getDrawingCache();
127            if (originalBitmap != null) {
128                double originalHeight = originalBitmap.getHeight();
129                double originalWidth = originalBitmap.getWidth();
130                int newWidth = (int) originalWidth;
131                int newHeight = (int) originalHeight;
132                if (maximumDimension > 0) {
133                    double scale = maximumDimension / Math.max(originalWidth, originalHeight);
134                    newWidth = (int) Math.round(originalWidth * scale);
135                    newHeight = (int) Math.round(originalHeight * scale);
136                }
137                Bitmap scaledScreenshot =
138                        Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true);
139                if (scaledScreenshot.getConfig() != bitmapConfig) {
140                    screenshot = scaledScreenshot.copy(bitmapConfig, false);
141                    scaledScreenshot.recycle();
142                    scaledScreenshot = null;
143                } else {
144                    screenshot = scaledScreenshot;
145                }
146            } else if (currentView.getMeasuredHeight() > 0 && currentView.getMeasuredWidth() > 0) {
147                double originalHeight = currentView.getMeasuredHeight();
148                double originalWidth = currentView.getMeasuredWidth();
149                int newWidth = (int) originalWidth;
150                int newHeight = (int) originalHeight;
151                if (maximumDimension > 0) {
152                    double scale = maximumDimension / Math.max(originalWidth, originalHeight);
153                    newWidth = (int) Math.round(originalWidth * scale);
154                    newHeight = (int) Math.round(originalHeight * scale);
155                }
156                Bitmap bitmap = Bitmap.createBitmap(newWidth, newHeight, bitmapConfig);
157                Canvas canvas = new Canvas(bitmap);
158                canvas.scale((float) (newWidth / originalWidth),
159                        (float) (newHeight / originalHeight));
160                currentView.draw(canvas);
161                screenshot = bitmap;
162            }
163        } catch (OutOfMemoryError e) {
164            Log.d(TAG, "Unable to capture screenshot and scale it down." + e.getMessage());
165        } finally {
166            if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(false);
167            prepareViewHierarchyForScreenshot(currentView, false);
168        }
169        return screenshot;
170    }
171
172    private static void prepareViewHierarchyForScreenshot(View view, boolean takingScreenshot) {
173        if (view instanceof ViewGroup) {
174            ViewGroup viewGroup = (ViewGroup) view;
175            for (int i = 0; i < viewGroup.getChildCount(); i++) {
176                prepareViewHierarchyForScreenshot(viewGroup.getChildAt(i), takingScreenshot);
177            }
178        } else if (view instanceof SurfaceView) {
179            view.setWillNotDraw(!takingScreenshot);
180        }
181    }
182}
183