| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.ui; |
| |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Rect; |
| import android.util.Log; |
| import android.view.SurfaceView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.inputmethod.InputMethodManager; |
| |
| /** |
| * Utility functions for common Android UI tasks. |
| * This class is not supposed to be instantiated. |
| */ |
| public class UiUtils { |
| private static final String TAG = "UiUtils"; |
| |
| /** |
| * Guards this class from being instantiated. |
| */ |
| private UiUtils() { |
| } |
| |
| /** The minimum size of the bottom margin below the app to detect a keyboard. */ |
| private static final float KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP = 100; |
| |
| /** A delegate that allows disabling keyboard visibility detection. */ |
| private static KeyboardShowingDelegate sKeyboardShowingDelegate; |
| |
| /** |
| * A delegate that can be implemented to override whether or not keyboard detection will be |
| * used. |
| */ |
| public interface KeyboardShowingDelegate { |
| /** |
| * Will be called to determine whether or not to detect if the keyboard is visible. |
| * @param context A {@link Context} instance. |
| * @param view A {@link View}. |
| * @return Whether or not the keyboard check should be disabled. |
| */ |
| boolean disableKeyboardCheck(Context context, View view); |
| } |
| |
| /** |
| * Allows setting a delegate to override the default software keyboard visibility detection. |
| * @param delegate A {@link KeyboardShowingDelegate} instance. |
| */ |
| public static void setKeyboardShowingDelegate(KeyboardShowingDelegate delegate) { |
| sKeyboardShowingDelegate = delegate; |
| } |
| |
| /** |
| * Shows the software keyboard if necessary. |
| * @param view The currently focused {@link View}, which would receive soft keyboard input. |
| */ |
| public static void showKeyboard(View view) { |
| InputMethodManager imm = |
| (InputMethodManager) view.getContext().getSystemService( |
| Context.INPUT_METHOD_SERVICE); |
| // Only shows soft keyboard if there isn't an open physical keyboard. |
| imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); |
| } |
| |
| /** |
| * Hides the keyboard. |
| * @param view The {@link View} that is currently accepting input. |
| * @return Whether the keyboard was visible before. |
| */ |
| public static boolean hideKeyboard(View view) { |
| InputMethodManager imm = |
| (InputMethodManager) view.getContext().getSystemService( |
| Context.INPUT_METHOD_SERVICE); |
| return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); |
| } |
| |
| /** |
| * Detects whether or not the keyboard is showing. This is a best guess as there is no |
| * standardized/foolproof way to do this. |
| * @param context A {@link Context} instance. |
| * @param view A {@link View}. |
| * @return Whether or not the software keyboard is visible and taking up screen space. |
| */ |
| public static boolean isKeyboardShowing(Context context, View view) { |
| if (sKeyboardShowingDelegate != null |
| && sKeyboardShowingDelegate.disableKeyboardCheck(context, view)) { |
| return false; |
| } |
| |
| View rootView = view.getRootView(); |
| if (rootView == null) return false; |
| Rect appRect = new Rect(); |
| rootView.getWindowVisibleDisplayFrame(appRect); |
| |
| final float density = context.getResources().getDisplayMetrics().density; |
| final float bottomMarginDp = Math.abs(rootView.getHeight() - appRect.height()) / density; |
| return bottomMarginDp > KEYBOARD_DETECT_BOTTOM_THRESHOLD_DP; |
| } |
| |
| /** |
| * Inserts a {@link View} into a {@link ViewGroup} after directly before a given {@View}. |
| * @param container The {@link View} to add newView to. |
| * @param newView The new {@link View} to add. |
| * @param existingView The {@link View} to insert the newView before. |
| * @return The index where newView was inserted, or -1 if it was not inserted. |
| */ |
| public static int insertBefore(ViewGroup container, View newView, View existingView) { |
| return insertView(container, newView, existingView, false); |
| } |
| |
| /** |
| * Inserts a {@link View} into a {@link ViewGroup} after directly after a given {@View}. |
| * @param container The {@link View} to add newView to. |
| * @param newView The new {@link View} to add. |
| * @param existingView The {@link View} to insert the newView after. |
| * @return The index where newView was inserted, or -1 if it was not inserted. |
| */ |
| public static int insertAfter(ViewGroup container, View newView, View existingView) { |
| return insertView(container, newView, existingView, true); |
| } |
| |
| private static int insertView( |
| ViewGroup container, View newView, View existingView, boolean after) { |
| // See if the view has already been added. |
| int index = container.indexOfChild(newView); |
| if (index >= 0) return index; |
| |
| // Find the location of the existing view. |
| index = container.indexOfChild(existingView); |
| if (index < 0) return -1; |
| |
| // Add the view. |
| if (after) index++; |
| container.addView(newView, index); |
| return index; |
| } |
| |
| /** |
| * Generates a scaled screenshot of the given view. The maximum size of the screenshot is |
| * determined by maximumDimension. |
| * |
| * @param currentView The view to generate a screenshot of. |
| * @param maximumDimension The maximum width or height of the generated screenshot. The bitmap |
| * will be scaled to ensure the maximum width or height is equal to or |
| * less than this. Any value <= 0, will result in no scaling. |
| * @param bitmapConfig Bitmap config for the generated screenshot (ARGB_8888 or RGB_565). |
| * @return The screen bitmap of the view or null if a problem was encountered. |
| */ |
| public static Bitmap generateScaledScreenshot( |
| View currentView, int maximumDimension, Bitmap.Config bitmapConfig) { |
| Bitmap screenshot = null; |
| boolean drawingCacheEnabled = currentView.isDrawingCacheEnabled(); |
| try { |
| prepareViewHierarchyForScreenshot(currentView, true); |
| if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(true); |
| // Android has a maximum drawing cache size and if the drawing cache is bigger |
| // than that, getDrawingCache() returns null. |
| Bitmap originalBitmap = currentView.getDrawingCache(); |
| if (originalBitmap != null) { |
| double originalHeight = originalBitmap.getHeight(); |
| double originalWidth = originalBitmap.getWidth(); |
| int newWidth = (int) originalWidth; |
| int newHeight = (int) originalHeight; |
| if (maximumDimension > 0) { |
| double scale = maximumDimension / Math.max(originalWidth, originalHeight); |
| newWidth = (int) Math.round(originalWidth * scale); |
| newHeight = (int) Math.round(originalHeight * scale); |
| } |
| Bitmap scaledScreenshot = |
| Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true); |
| if (scaledScreenshot.getConfig() != bitmapConfig) { |
| screenshot = scaledScreenshot.copy(bitmapConfig, false); |
| scaledScreenshot.recycle(); |
| scaledScreenshot = null; |
| } else { |
| screenshot = scaledScreenshot; |
| } |
| } else if (currentView.getMeasuredHeight() > 0 && currentView.getMeasuredWidth() > 0) { |
| double originalHeight = currentView.getMeasuredHeight(); |
| double originalWidth = currentView.getMeasuredWidth(); |
| int newWidth = (int) originalWidth; |
| int newHeight = (int) originalHeight; |
| if (maximumDimension > 0) { |
| double scale = maximumDimension / Math.max(originalWidth, originalHeight); |
| newWidth = (int) Math.round(originalWidth * scale); |
| newHeight = (int) Math.round(originalHeight * scale); |
| } |
| Bitmap bitmap = Bitmap.createBitmap(newWidth, newHeight, bitmapConfig); |
| Canvas canvas = new Canvas(bitmap); |
| canvas.scale((float) (newWidth / originalWidth), |
| (float) (newHeight / originalHeight)); |
| currentView.draw(canvas); |
| screenshot = bitmap; |
| } |
| } catch (OutOfMemoryError e) { |
| Log.d(TAG, "Unable to capture screenshot and scale it down." + e.getMessage()); |
| } finally { |
| if (!drawingCacheEnabled) currentView.setDrawingCacheEnabled(false); |
| prepareViewHierarchyForScreenshot(currentView, false); |
| } |
| return screenshot; |
| } |
| |
| private static void prepareViewHierarchyForScreenshot(View view, boolean takingScreenshot) { |
| if (view instanceof ViewGroup) { |
| ViewGroup viewGroup = (ViewGroup) view; |
| for (int i = 0; i < viewGroup.getChildCount(); i++) { |
| prepareViewHierarchyForScreenshot(viewGroup.getChildAt(i), takingScreenshot); |
| } |
| } else if (view instanceof SurfaceView) { |
| view.setWillNotDraw(!takingScreenshot); |
| } |
| } |
| } |