Home | History | Annotate | Download | only in ui
      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