• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 package org.chromium.ui;
6 
7 import android.content.Context;
8 import android.graphics.Bitmap;
9 import android.graphics.Canvas;
10 import android.graphics.Rect;
11 import android.util.Log;
12 import android.view.SurfaceView;
13 import android.view.View;
14 import android.view.ViewGroup;
15 import android.view.inputmethod.InputMethodManager;
16 
17 /**
18  * Utility functions for common Android UI tasks.
19  * This class is not supposed to be instantiated.
20  */
21 public class UiUtils {
22     private static final String TAG = "UiUtils";
23 
24     /**
25      * Guards this class from being instantiated.
26      */
UiUtils()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     /** A delegate that allows disabling keyboard visibility detection. */
34     private static KeyboardShowingDelegate sKeyboardShowingDelegate;
35 
36     /**
37      * A delegate that can be implemented to override whether or not keyboard detection will be
38      * used.
39      */
40     public interface KeyboardShowingDelegate {
41         /**
42          * Will be called to determine whether or not to detect if the keyboard is visible.
43          * @param context A {@link Context} instance.
44          * @param view    A {@link View}.
45          * @return        Whether or not the keyboard check should be disabled.
46          */
disableKeyboardCheck(Context context, View view)47         boolean disableKeyboardCheck(Context context, View view);
48     }
49 
50     /**
51      * Allows setting a delegate to override the default software keyboard visibility detection.
52      * @param delegate A {@link KeyboardShowingDelegate} instance.
53      */
setKeyboardShowingDelegate(KeyboardShowingDelegate delegate)54     public static void setKeyboardShowingDelegate(KeyboardShowingDelegate delegate) {
55         sKeyboardShowingDelegate = delegate;
56     }
57 
58     /**
59      * Shows the software keyboard if necessary.
60      * @param view The currently focused {@link View}, which would receive soft keyboard input.
61      */
showKeyboard(View view)62     public static void showKeyboard(View view) {
63         InputMethodManager imm =
64                 (InputMethodManager) view.getContext().getSystemService(
65                         Context.INPUT_METHOD_SERVICE);
66         // Only shows soft keyboard if there isn't an open physical keyboard.
67         imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
68     }
69 
70     /**
71      * Hides the keyboard.
72      * @param view The {@link View} that is currently accepting input.
73      * @return Whether the keyboard was visible before.
74      */
hideKeyboard(View view)75     public static boolean hideKeyboard(View view) {
76         InputMethodManager imm =
77                 (InputMethodManager) view.getContext().getSystemService(
78                         Context.INPUT_METHOD_SERVICE);
79         return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
80     }
81 
82     /**
83      * Detects whether or not the keyboard is showing.  This is a best guess as there is no
84      * standardized/foolproof way to do this.
85      * @param context A {@link Context} instance.
86      * @param view    A {@link View}.
87      * @return        Whether or not the software keyboard is visible and taking up screen space.
88      */
isKeyboardShowing(Context context, View view)89     public static boolean isKeyboardShowing(Context context, View view) {
90         if (sKeyboardShowingDelegate != null
91                 && sKeyboardShowingDelegate.disableKeyboardCheck(context, view)) {
92             return false;
93         }
94 
95         View rootView = view.getRootView();
96         if (rootView == null) return false;
97         Rect appRect = new Rect();
98         rootView.getWindowVisibleDisplayFrame(appRect);
99 
100         final float density = context.getResources().getDisplayMetrics().density;
101         final float bottomMarginDp = Math.abs(rootView.getHeight() - appRect.height()) / density;
102         return bottomMarginDp > KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP;
103     }
104 
105     /**
106      * Inserts a {@link View} into a {@link ViewGroup} after directly before a given {@View}.
107      * @param container The {@link View} to add newView to.
108      * @param newView The new {@link View} to add.
109      * @param existingView The {@link View} to insert the newView before.
110      * @return The index where newView was inserted, or -1 if it was not inserted.
111      */
insertBefore(ViewGroup container, View newView, View existingView)112     public static int insertBefore(ViewGroup container, View newView, View existingView) {
113         return insertView(container, newView, existingView, false);
114     }
115 
116     /**
117      * Inserts a {@link View} into a {@link ViewGroup} after directly after a given {@View}.
118      * @param container The {@link View} to add newView to.
119      * @param newView The new {@link View} to add.
120      * @param existingView The {@link View} to insert the newView after.
121      * @return The index where newView was inserted, or -1 if it was not inserted.
122      */
insertAfter(ViewGroup container, View newView, View existingView)123     public static int insertAfter(ViewGroup container, View newView, View existingView) {
124         return insertView(container, newView, existingView, true);
125     }
126 
insertView( ViewGroup container, View newView, View existingView, boolean after)127     private static int insertView(
128             ViewGroup container, View newView, View existingView, boolean after) {
129         // See if the view has already been added.
130         int index = container.indexOfChild(newView);
131         if (index >= 0) return index;
132 
133         // Find the location of the existing view.
134         index = container.indexOfChild(existingView);
135         if (index < 0) return -1;
136 
137         // Add the view.
138         if (after) index++;
139         container.addView(newView, index);
140         return index;
141     }
142 
143     /**
144      * Generates a scaled screenshot of the given view.  The maximum size of the screenshot is
145      * determined by maximumDimension.
146      *
147      * @param currentView      The view to generate a screenshot of.
148      * @param maximumDimension The maximum width or height of the generated screenshot.  The bitmap
149      *                         will be scaled to ensure the maximum width or height is equal to or
150      *                         less than this.  Any value <= 0, will result in no scaling.
151      * @param bitmapConfig     Bitmap config for the generated screenshot (ARGB_8888 or RGB_565).
152      * @return The screen bitmap of the view or null if a problem was encountered.
153      */
generateScaledScreenshot( View currentView, int maximumDimension, Bitmap.Config bitmapConfig)154     public static Bitmap generateScaledScreenshot(
155             View currentView, int maximumDimension, Bitmap.Config bitmapConfig) {
156         Bitmap screenshot = null;
157         boolean drawingCacheEnabled = currentView.isDrawingCacheEnabled();
158         try {
159             prepareViewHierarchyForScreenshot(currentView, true);
160             if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(true);
161             // Android has a maximum drawing cache size and if the drawing cache is bigger
162             // than that, getDrawingCache() returns null.
163             Bitmap originalBitmap = currentView.getDrawingCache();
164             if (originalBitmap != null) {
165                 double originalHeight = originalBitmap.getHeight();
166                 double originalWidth = originalBitmap.getWidth();
167                 int newWidth = (int) originalWidth;
168                 int newHeight = (int) originalHeight;
169                 if (maximumDimension > 0) {
170                     double scale = maximumDimension / Math.max(originalWidth, originalHeight);
171                     newWidth = (int) Math.round(originalWidth * scale);
172                     newHeight = (int) Math.round(originalHeight * scale);
173                 }
174                 Bitmap scaledScreenshot =
175                         Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true);
176                 if (scaledScreenshot.getConfig() != bitmapConfig) {
177                     screenshot = scaledScreenshot.copy(bitmapConfig, false);
178                     scaledScreenshot.recycle();
179                     scaledScreenshot = null;
180                 } else {
181                     screenshot = scaledScreenshot;
182                 }
183             } else if (currentView.getMeasuredHeight() > 0 && currentView.getMeasuredWidth() > 0) {
184                 double originalHeight = currentView.getMeasuredHeight();
185                 double originalWidth = currentView.getMeasuredWidth();
186                 int newWidth = (int) originalWidth;
187                 int newHeight = (int) originalHeight;
188                 if (maximumDimension > 0) {
189                     double scale = maximumDimension / Math.max(originalWidth, originalHeight);
190                     newWidth = (int) Math.round(originalWidth * scale);
191                     newHeight = (int) Math.round(originalHeight * scale);
192                 }
193                 Bitmap bitmap = Bitmap.createBitmap(newWidth, newHeight, bitmapConfig);
194                 Canvas canvas = new Canvas(bitmap);
195                 canvas.scale((float) (newWidth / originalWidth),
196                         (float) (newHeight / originalHeight));
197                 currentView.draw(canvas);
198                 screenshot = bitmap;
199             }
200         } catch (OutOfMemoryError e) {
201             Log.d(TAG, "Unable to capture screenshot and scale it down." + e.getMessage());
202         } finally {
203             if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(false);
204             prepareViewHierarchyForScreenshot(currentView, false);
205         }
206         return screenshot;
207     }
208 
prepareViewHierarchyForScreenshot(View view, boolean takingScreenshot)209     private static void prepareViewHierarchyForScreenshot(View view, boolean takingScreenshot) {
210         if (view instanceof ViewGroup) {
211             ViewGroup viewGroup = (ViewGroup) view;
212             for (int i = 0; i < viewGroup.getChildCount(); i++) {
213                 prepareViewHierarchyForScreenshot(viewGroup.getChildAt(i), takingScreenshot);
214             }
215         } else if (view instanceof SurfaceView) {
216             view.setWillNotDraw(!takingScreenshot);
217         }
218     }
219 }
220