Merge "Fix an OOB exception" into jb-mr1-dev
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 4d95209..dc84763 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -349,7 +349,7 @@
         return mKeyboardView;
     }
 
-    public View onCreateInputView() {
+    public View onCreateInputView(boolean isHardwareAcceleratedDrawingEnabled) {
         if (mKeyboardView != null) {
             mKeyboardView.closing();
         }
@@ -372,6 +372,10 @@
         }
 
         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
+        if (isHardwareAcceleratedDrawingEnabled) {
+            mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
+        }
         mKeyboardView.setKeyboardActionListener(mLatinIME);
         if (mForceNonDistinctMultitouch) {
             mKeyboardView.setDistinctMultitouch(false);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 6b1320d..69e4d98 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -123,6 +123,8 @@
     /** The working rectangle variable */
     private final Rect mWorkingRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
+    /** The clip region to draw keys */
+    private final Region mClipRegion = new Region();
     private Bitmap mOffscreenBuffer;
     /** The canvas for the above mutable keyboard bitmap */
     private Canvas mOffscreenCanvas;
@@ -457,10 +459,15 @@
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
+        if (canvas.isHardwareAccelerated()) {
+            onDrawKeyboard(canvas);
+            return;
+        }
         if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
             mBufferNeedsUpdate = false;
             if (maybeAllocateOffscreenBuffer()) {
                 mInvalidateAllKeys = true;
+                // TODO: Stop using the offscreen canvas even when in software rendering
                 if (mOffscreenCanvas != null) {
                     mOffscreenCanvas.setBitmap(mOffscreenBuffer);
                 } else {
@@ -502,35 +509,57 @@
         final Paint paint = mPaint;
         final KeyDrawParams params = mKeyDrawParams;
 
-        if (mInvalidateAllKeys || mInvalidatedKeys.isEmpty()) {
-            mWorkingRect.set(0, 0, width, height);
-            canvas.clipRect(mWorkingRect, Region.Op.REPLACE);
-            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+        // Calculate clip region and set.
+        final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
+        final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
+        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
+        if (drawAllKeys || isHardwareAccelerated) {
+            mClipRegion.set(0, 0, width, height);
+        } else {
+            mClipRegion.setEmpty();
+            for (final Key key : mInvalidatedKeys) {
+                if (mKeyboard.hasKey(key)) {
+                    final int x = key.mX + getPaddingLeft();
+                    final int y = key.mY + getPaddingTop();
+                    mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
+                    mClipRegion.union(mWorkingRect);
+                }
+            }
+        }
+        if (!isHardwareAccelerated) {
+            canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
+        }
+
+        // Draw keyboard background.
+        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+        final Drawable background = getBackground();
+        if (background != null) {
+            background.draw(canvas);
+        }
+
+        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
+        if (drawAllKeys || isHardwareAccelerated) {
             // Draw all keys.
             for (final Key key : mKeyboard.mKeys) {
                 onDrawKey(key, canvas, paint, params);
             }
-            if (mNeedsToDimEntireKeyboard) {
-                drawDimRectangle(canvas, mWorkingRect, mBackgroundDimAlpha, paint);
-            }
         } else {
             // Draw invalidated keys.
             for (final Key key : mInvalidatedKeys) {
-                if (!mKeyboard.hasKey(key)) {
-                    continue;
-                }
-                final int x = key.mX + getPaddingLeft();
-                final int y = key.mY + getPaddingTop();
-                mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
-                canvas.clipRect(mWorkingRect, Region.Op.REPLACE);
-                canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
-                onDrawKey(key, canvas, paint, params);
-                if (mNeedsToDimEntireKeyboard) {
-                    drawDimRectangle(canvas, mWorkingRect, mBackgroundDimAlpha, paint);
+                if (mKeyboard.hasKey(key)) {
+                    onDrawKey(key, canvas, paint, params);
                 }
             }
         }
 
+        // Overlay a dark rectangle to dim.
+        if (mNeedsToDimEntireKeyboard) {
+            paint.setColor(Color.BLACK);
+            paint.setAlpha(mBackgroundDimAlpha);
+            // Note: clipRegion() above is in effect if it was called.
+            canvas.drawRect(0, 0, width, height, paint);
+        }
+
         // ResearchLogging indicator.
         // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
         // and remove this call.
@@ -863,13 +892,6 @@
         canvas.translate(-x, -y);
     }
 
-    // Overlay a dark rectangle to dim.
-    private static void drawDimRectangle(Canvas canvas, Rect rect, int alpha, Paint paint) {
-        paint.setColor(Color.BLACK);
-        paint.setAlpha(alpha);
-        canvas.drawRect(rect, paint);
-    }
-
     public Paint newDefaultLabelPaint() {
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 8dc1081..a711ab9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -553,7 +553,7 @@
 
     @Override
     public View onCreateInputView() {
-        return mKeyboardSwitcher.onCreateInputView();
+        return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
     }
 
     @Override
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 93e19b3..74390cc 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "LatinIME: jni: ProximityInfo"
 
+#include <string>
+
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "jni.h"
 #include "jni_common.h"
@@ -42,10 +44,9 @@
     jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
     jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
     ProximityInfo *proximityInfo = new ProximityInfo(
-            localeStr, maxProximityCharsSize, displayWidth,
-            displayHeight, gridWidth, gridHeight, mostCommonkeyWidth,
-            (const int32_t*)proximityChars,
-            keyCount, (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
+            localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
+            mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
+            (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
             (const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
             (const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
             (const float*)sweetSpotRadii);
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 2d3d963..776f5f7 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -25,8 +25,9 @@
 #include "jni_common.h"
 
 #ifdef USE_MMAP_FOR_DICTIONARY
-#include <sys/mman.h>
+#include <cerrno>
 #include <fcntl.h>
+#include <sys/mman.h>
 #else // USE_MMAP_FOR_DICTIONARY
 #include <cstdlib>
 #endif // USE_MMAP_FOR_DICTIONARY
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index cb2351d..105a4dc 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -22,11 +22,8 @@
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
-#include "proximity_info.h"
 
 #include <cassert>
-#include <cerrno>
-#include <cstdio>
 
 using namespace latinime;
 
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
index 307f596..a76ec50 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
@@ -55,11 +55,10 @@
             return "compress <filename>: Compresses a file using gzip compression";
         }
 
-        public int getArity() {
-            return 1;
-        }
-
         public void run() throws IOException {
+            if (mArgs.length < 1) {
+                throw new RuntimeException("Not enough arguments for command " + COMMAND);
+            }
             final String inFilename = mArgs[0];
             final String outFilename = inFilename + SUFFIX;
             final FileInputStream input = new FileInputStream(new File(inFilename));
@@ -79,11 +78,10 @@
             return "uncompress <filename>: Uncompresses a file compressed with gzip compression";
         }
 
-        public int getArity() {
-            return 1;
-        }
-
         public void run() throws IOException {
+            if (mArgs.length < 1) {
+                throw new RuntimeException("Not enough arguments for command " + COMMAND);
+            }
             final String inFilename = mArgs[0];
             final String outFilename = inFilename + SUFFIX;
             final FileInputStream input = new FileInputStream(new File(inFilename));
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
index b78be79..8fc0423 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
@@ -27,7 +26,6 @@
         public void setArgs(String[] args) throws IllegalArgumentException {
             mArgs = args;
         }
-        abstract public int getArity();
         abstract public String getHelp();
         abstract public void run() throws Exception;
     }
@@ -62,59 +60,33 @@
         return sCommands.containsKey(commandName);
     }
 
-    private String mPreviousCommand = null; // local to the getNextCommand function
-    private Command getNextCommand(final ArrayList<String> arguments) {
-        final String firstArgument = arguments.get(0);
-        final String commandName;
-        if (isCommand(firstArgument)) {
-            commandName = firstArgument;
-            arguments.remove(0);
-        } else if (isCommand(mPreviousCommand)) {
-            commandName = mPreviousCommand;
-        } else {
-            throw new RuntimeException("Unknown command : " + firstArgument);
+    private Command getCommand(final String[] arguments) {
+        final String commandName = arguments[0];
+        if (!isCommand(commandName)) {
+            throw new RuntimeException("Unknown command : " + commandName);
         }
         final Command command = getCommandInstance(commandName);
-        final int arity = command.getArity();
-        if (arguments.size() < arity) {
-            throw new RuntimeException("Not enough arguments to command " + commandName);
-        }
-        final String[] argsArray = new String[arity];
-        arguments.subList(0, arity).toArray(argsArray);
-        for (int i = 0; i < arity; ++i) {
-            // For some reason, ArrayList#removeRange is protected
-            arguments.remove(0);
-        }
+        final String[] argsArray = Arrays.copyOfRange(arguments, 1, arguments.length);
         command.setArgs(argsArray);
-        mPreviousCommand = commandName;
         return command;
     }
 
-    private void execute(final ArrayList<String> arguments) {
-        ArrayList<Command> commandsToExecute = new ArrayList<Command>();
-        while (!arguments.isEmpty()) {
-            commandsToExecute.add(getNextCommand(arguments));
-        }
-        for (final Command command : commandsToExecute) {
-            try {
-                command.run();
-            } catch (Exception e) {
-                System.out.println("Exception while processing command "
-                        + command.getClass().getSimpleName() + " : " + e);
-                return;
-            }
+    private void execute(final String[] arguments) {
+        final Command command = getCommand(arguments);
+        try {
+            command.run();
+        } catch (Exception e) {
+            System.out.println("Exception while processing command "
+                    + command.getClass().getSimpleName() + " : " + e);
+            return;
         }
     }
 
-    public static void main(final String[] args) {
-        if (0 == args.length) {
+    public static void main(final String[] arguments) {
+        if (0 == arguments.length) {
             help();
             return;
         }
-        if (!isCommand(args[0])) throw new RuntimeException("Unknown command : " + args[0]);
-
-        final ArrayList<String> arguments = new ArrayList<String>(args.length);
-        arguments.addAll(Arrays.asList(args));
         new Dicttool().execute(arguments);
     }
 }
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
index cb032dd..e592617 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
@@ -17,6 +17,8 @@
 package com.android.inputmethod.latin.dicttool;
 
 public class Info extends Dicttool.Command {
+    public static final String COMMAND = "info";
+
     public Info() {
     }
 
@@ -24,12 +26,11 @@
         return "info <filename>: prints various information about a dictionary file";
     }
 
-    public int getArity() {
-        return 1;
-    }
-
     public void run() {
         // TODO: implement this
+        if (mArgs.length < 1) {
+            throw new RuntimeException("Not enough arguments for command " + COMMAND);
+        }
         System.out.println("Not implemented yet");
     }
 }