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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 123 public static int insertAfter(ViewGroup container, View newView, View existingView) { 124 return insertView(container, newView, existingView, true); 125 } 126 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 */ 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 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