Loading java/src/com/android/inputmethod/research/MotionEventReader.java +236 −17 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.inputmethod.research; import android.util.JsonReader; import android.util.Log; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.inputmethod.latin.define.ProductionFlag; Loading @@ -33,6 +35,14 @@ import java.util.ArrayList; public class MotionEventReader { private static final String TAG = MotionEventReader.class.getSimpleName(); private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; // Assumes that MotionEvent.ACTION_MASK does not have all bits set.` private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK; // No legitimate int is negative private static final int UNINITIALIZED_INT = -1; // No legitimate long is negative private static final long UNINITIALIZED_LONG = -1L; // No legitimate float is negative private static final float UNINITIALIZED_FLOAT = -1.0f; public ReplayData readMotionEventData(final File file) { final ReplayData replayData = new ReplayData(); Loading @@ -55,19 +65,82 @@ public class MotionEventReader { static class ReplayData { final ArrayList<Integer> mActions = new ArrayList<Integer>(); final ArrayList<Integer> mXCoords = new ArrayList<Integer>(); final ArrayList<Integer> mYCoords = new ArrayList<Integer>(); final ArrayList<PointerProperties[]> mPointerPropertiesArrays = new ArrayList<PointerProperties[]>(); final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<PointerCoords[]>(); final ArrayList<Long> mTimes = new ArrayList<Long>(); } private void readLogStatement(final JsonReader jsonReader, final ReplayData replayData) throws IOException { /** * Read motion data from a logStatement and store it in {@code replayData}. * * Two kinds of logStatements can be read. In the first variant, the MotionEvent data is * represented as attributes at the top level like so: * * <pre> * { * "_ct": 1359590400000, * "_ut": 4381933, * "_ty": "MotionEvent", * "action": "UP", * "isLoggingRelated": false, * "x": 100, * "y": 200 * } * </pre> * * In the second variant, there is a separate attribute for the MotionEvent that includes * historical data if present: * * <pre> * { * "_ct": 135959040000, * "_ut": 4382702, * "_ty": "MotionEvent", * "action": "MOVE", * "isLoggingRelated": false, * "motionEvent": { * "pointerIds": [ * 0 * ], * "xyt": [ * { * "t": 4382551, * "d": [ * { * "x": 141.25, * "y": 151.8485107421875, * "toma": 101.82337188720703, * "tomi": 101.82337188720703, * "o": 0.0 * } * ] * }, * { * "t": 4382559, * "d": [ * { * "x": 140.7266082763672, * "y": 151.8485107421875, * "toma": 101.82337188720703, * "tomi": 101.82337188720703, * "o": 0.0 * } * ] * } * ] * } * }, * </pre> */ /* package for test */ void readLogStatement(final JsonReader jsonReader, final ReplayData replayData) throws IOException { String logStatementType = null; Integer actionType = null; Integer x = null; Integer y = null; Long time = null; boolean loggingRelated = false; int actionType = UNINITIALIZED_ACTION; int x = UNINITIALIZED_INT; int y = UNINITIALIZED_INT; long time = UNINITIALIZED_LONG; boolean isLoggingRelated = false; jsonReader.beginObject(); while (jsonReader.hasNext()) { Loading @@ -90,7 +163,18 @@ public class MotionEventReader { actionType = MotionEvent.ACTION_MOVE; } } else if (key.equals("loggingRelated")) { loggingRelated = jsonReader.nextBoolean(); isLoggingRelated = jsonReader.nextBoolean(); } else if (logStatementType != null && logStatementType.equals("MotionEvent") && key.equals("motionEvent")) { if (actionType == UNINITIALIZED_ACTION) { Log.e(TAG, "no actionType assigned in MotionEvent json"); } // Second variant of LogStatement. if (isLoggingRelated) { jsonReader.skipValue(); } else { readEmbeddedMotionEvent(jsonReader, replayData, actionType); } } else { if (DEBUG) { Log.w(TAG, "Unknown JSON key in LogStatement: " + key); Loading @@ -100,14 +184,149 @@ public class MotionEventReader { } jsonReader.endObject(); if (logStatementType != null && time != null && x != null && y != null && actionType != null && logStatementType.equals("MotionEvent") && !loggingRelated) { replayData.mActions.add(actionType); replayData.mXCoords.add(x); replayData.mYCoords.add(y); replayData.mTimes.add(time); if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT && y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION && logStatementType.equals("MotionEvent") && !isLoggingRelated) { // First variant of LogStatement. final PointerProperties pointerProperties = new PointerProperties(); pointerProperties.id = 0; pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; final PointerProperties[] pointerPropertiesArray = { pointerProperties }; final PointerCoords pointerCoords = new PointerCoords(); pointerCoords.x = x; pointerCoords.y = y; pointerCoords.pressure = 1.0f; pointerCoords.size = 1.0f; final PointerCoords[] pointerCoordsArray = { pointerCoords }; addMotionEventData(replayData, actionType, time, pointerPropertiesArray, pointerCoordsArray); } } private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData, final int actionType) throws IOException { jsonReader.beginObject(); PointerProperties[] pointerPropertiesArray = null; while (jsonReader.hasNext()) { // pointerIds/xyt final String name = jsonReader.nextName(); if (name.equals("pointerIds")) { pointerPropertiesArray = readPointerProperties(jsonReader); } else if (name.equals("xyt")) { readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray); } } jsonReader.endObject(); } private PointerProperties[] readPointerProperties(final JsonReader jsonReader) throws IOException { final ArrayList<PointerProperties> pointerPropertiesArrayList = new ArrayList<PointerProperties>(); jsonReader.beginArray(); while (jsonReader.hasNext()) { final PointerProperties pointerProperties = new PointerProperties(); pointerProperties.id = jsonReader.nextInt(); pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; pointerPropertiesArrayList.add(pointerProperties); } jsonReader.endArray(); return pointerPropertiesArrayList.toArray( new PointerProperties[pointerPropertiesArrayList.size()]); } private void readPointerData(final JsonReader jsonReader, final ReplayData replayData, final int actionType, final PointerProperties[] pointerPropertiesArray) throws IOException { if (pointerPropertiesArray == null) { Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent"); jsonReader.skipValue(); return; } long time = UNINITIALIZED_LONG; jsonReader.beginArray(); while (jsonReader.hasNext()) { // Array of historical data jsonReader.beginObject(); final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<PointerCoords>(); while (jsonReader.hasNext()) { // Time/data object final String name = jsonReader.nextName(); if (name.equals("t")) { time = jsonReader.nextLong(); } else if (name.equals("d")) { jsonReader.beginArray(); while (jsonReader.hasNext()) { // array of data per pointer final PointerCoords pointerCoords = readPointerCoords(jsonReader); if (pointerCoords != null) { pointerCoordsArrayList.add(pointerCoords); } } jsonReader.endArray(); } else { jsonReader.skipValue(); } } jsonReader.endObject(); // Data was recorded as historical events, but must be split apart into // separate MotionEvents for replaying if (time != UNINITIALIZED_LONG) { addMotionEventData(replayData, actionType, time, pointerPropertiesArray, pointerCoordsArrayList.toArray( new PointerCoords[pointerCoordsArrayList.size()])); } else { Log.e(TAG, "Time not assigned in json for MotionEvent"); } } jsonReader.endArray(); } private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException { jsonReader.beginObject(); float x = UNINITIALIZED_FLOAT; float y = UNINITIALIZED_FLOAT; while (jsonReader.hasNext()) { // x,y final String name = jsonReader.nextName(); if (name.equals("x")) { x = (float) jsonReader.nextDouble(); } else if (name.equals("y")) { y = (float) jsonReader.nextDouble(); } else { jsonReader.skipValue(); } } jsonReader.endObject(); if (Float.compare(x, UNINITIALIZED_FLOAT) == 0 || Float.compare(y, UNINITIALIZED_FLOAT) == 0) { Log.w(TAG, "missing x or y value in MotionEvent json"); return null; } final PointerCoords pointerCoords = new PointerCoords(); pointerCoords.x = x; pointerCoords.y = y; pointerCoords.pressure = 1.0f; pointerCoords.size = 1.0f; return pointerCoords; } /** * Tests that {@code x} is uninitialized. * * Assumes that {@code x} will never be given a valid value less than 0, and that * UNINITIALIZED_FLOAT is less than 0.0f. */ private boolean isUninitializedFloat(final float x) { return x < 0.0f; } private void addMotionEventData(final ReplayData replayData, final int actionType, final long time, final PointerProperties[] pointerProperties, final PointerCoords[] pointerCoords) { replayData.mActions.add(actionType); replayData.mTimes.add(time); replayData.mPointerPropertiesArrays.add(pointerProperties); replayData.mPointerCoordsArrays.add(pointerCoords); } } java/src/com/android/inputmethod/research/Replayer.java +16 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.os.Message; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; Loading Loading @@ -62,7 +64,6 @@ public class Replayer { if (mIsReplaying) { return; } mIsReplaying = true; final int numActions = replayData.mActions.size(); if (DEBUG) { Loading Loading @@ -95,25 +96,36 @@ public class Replayer { case MSG_MOTION_EVENT: final int index = msg.arg1; final int action = replayData.mActions.get(index); final int x = replayData.mXCoords.get(index); final int y = replayData.mYCoords.get(index); final PointerProperties[] pointerPropertiesArray = replayData.mPointerPropertiesArrays.get(index); final PointerCoords[] pointerCoordsArray = replayData.mPointerCoordsArrays.get(index); final long origTime = replayData.mTimes.get(index); if (action == MotionEvent.ACTION_DOWN) { mOrigDownTime = origTime; } final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment, origTime + timeAdjustment, action, x, y, 0); origTime + timeAdjustment, action, pointerPropertiesArray.length, pointerPropertiesArray, pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); mainKeyboardView.processMotionEvent(me); me.recycle(); break; case MSG_DONE: mIsReplaying = false; ResearchLogger.getInstance().requestIndicatorRedraw(); break; } } }; handler.post(new Runnable() { @Override public void run() { ResearchLogger.getInstance().requestIndicatorRedraw(); } }); for (int i = 0; i < numActions; i++) { final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0); final long msgTime = replayData.mTimes.get(i) + timeAdjustment; Loading tests/src/com/android/inputmethod/research/MotionEventReaderTests.java 0 → 100644 +169 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.inputmethod.research; import android.test.AndroidTestCase; import android.util.JsonReader; import com.android.inputmethod.research.MotionEventReader.ReplayData; import java.io.IOException; import java.io.StringReader; public class MotionEventReaderTests extends AndroidTestCase { private MotionEventReader mMotionEventReader = new MotionEventReader(); private ReplayData mReplayData; @Override protected void setUp() throws Exception { super.setUp(); mReplayData = new ReplayData(); } private JsonReader jsonReaderForString(final String s) { return new JsonReader(new StringReader(s)); } public void testTopLevelDataVariant() { final JsonReader jsonReader = jsonReaderForString( "{" + "\"_ct\": 1359590400000," + "\"_ut\": 4381933," + "\"_ty\": \"MotionEvent\"," + "\"action\": \"UP\"," + "\"isLoggingRelated\": false," + "\"x\": 100.0," + "\"y\": 200.0" + "}" ); try { mMotionEventReader.readLogStatement(jsonReader, mReplayData); } catch (IOException e) { e.printStackTrace(); fail("IOException thrown"); } assertEquals("x set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); assertEquals("y set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1); assertEquals("only one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1); } public void testNestedDataVariant() { final JsonReader jsonReader = jsonReaderForString( "{" + " \"_ct\": 135959040000," + " \"_ut\": 4382702," + " \"_ty\": \"MotionEvent\"," + " \"action\": \"MOVE\"," + " \"isLoggingRelated\": false," + " \"motionEvent\": {" + " \"pointerIds\": [" + " 0" + " ]," + " \"xyt\": [" + " {" + " \"t\": 4382551," + " \"d\": [" + " {" + " \"x\": 100.0," + " \"y\": 200.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }" + " ]" + " }," + " {" + " \"t\": 4382559," + " \"d\": [" + " {" + " \"x\": 300.0," + " \"y\": 400.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }" + " ]" + " }" + " ]" + " }" + "}" ); try { mMotionEventReader.readLogStatement(jsonReader, mReplayData); } catch (IOException e) { e.printStackTrace(); fail("IOException thrown"); } assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].x, 300); assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].y, 400); assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1); assertEquals("two MotionEvents", mReplayData.mPointerCoordsArrays.size(), 2); } public void testNestedDataVariantMultiPointer() { final JsonReader jsonReader = jsonReaderForString( "{" + " \"_ct\": 135959040000," + " \"_ut\": 4382702," + " \"_ty\": \"MotionEvent\"," + " \"action\": \"MOVE\"," + " \"isLoggingRelated\": false," + " \"motionEvent\": {" + " \"pointerIds\": [" + " 1" + " ]," + " \"xyt\": [" + " {" + " \"t\": 4382551," + " \"d\": [" + " {" + " \"x\": 100.0," + " \"y\": 200.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }," + " {" + " \"x\": 300.0," + " \"y\": 400.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }" + " ]" + " }" + " ]" + " }" + "}" ); try { mMotionEventReader.readLogStatement(jsonReader, mReplayData); } catch (IOException e) { e.printStackTrace(); fail("IOException thrown"); } assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].x, 300); assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].y, 400); assertEquals("two pointers", mReplayData.mPointerCoordsArrays.get(0).length, 2); assertEquals("one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1); } } Loading
java/src/com/android/inputmethod/research/MotionEventReader.java +236 −17 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.inputmethod.research; import android.util.JsonReader; import android.util.Log; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.inputmethod.latin.define.ProductionFlag; Loading @@ -33,6 +35,14 @@ import java.util.ArrayList; public class MotionEventReader { private static final String TAG = MotionEventReader.class.getSimpleName(); private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG; // Assumes that MotionEvent.ACTION_MASK does not have all bits set.` private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK; // No legitimate int is negative private static final int UNINITIALIZED_INT = -1; // No legitimate long is negative private static final long UNINITIALIZED_LONG = -1L; // No legitimate float is negative private static final float UNINITIALIZED_FLOAT = -1.0f; public ReplayData readMotionEventData(final File file) { final ReplayData replayData = new ReplayData(); Loading @@ -55,19 +65,82 @@ public class MotionEventReader { static class ReplayData { final ArrayList<Integer> mActions = new ArrayList<Integer>(); final ArrayList<Integer> mXCoords = new ArrayList<Integer>(); final ArrayList<Integer> mYCoords = new ArrayList<Integer>(); final ArrayList<PointerProperties[]> mPointerPropertiesArrays = new ArrayList<PointerProperties[]>(); final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<PointerCoords[]>(); final ArrayList<Long> mTimes = new ArrayList<Long>(); } private void readLogStatement(final JsonReader jsonReader, final ReplayData replayData) throws IOException { /** * Read motion data from a logStatement and store it in {@code replayData}. * * Two kinds of logStatements can be read. In the first variant, the MotionEvent data is * represented as attributes at the top level like so: * * <pre> * { * "_ct": 1359590400000, * "_ut": 4381933, * "_ty": "MotionEvent", * "action": "UP", * "isLoggingRelated": false, * "x": 100, * "y": 200 * } * </pre> * * In the second variant, there is a separate attribute for the MotionEvent that includes * historical data if present: * * <pre> * { * "_ct": 135959040000, * "_ut": 4382702, * "_ty": "MotionEvent", * "action": "MOVE", * "isLoggingRelated": false, * "motionEvent": { * "pointerIds": [ * 0 * ], * "xyt": [ * { * "t": 4382551, * "d": [ * { * "x": 141.25, * "y": 151.8485107421875, * "toma": 101.82337188720703, * "tomi": 101.82337188720703, * "o": 0.0 * } * ] * }, * { * "t": 4382559, * "d": [ * { * "x": 140.7266082763672, * "y": 151.8485107421875, * "toma": 101.82337188720703, * "tomi": 101.82337188720703, * "o": 0.0 * } * ] * } * ] * } * }, * </pre> */ /* package for test */ void readLogStatement(final JsonReader jsonReader, final ReplayData replayData) throws IOException { String logStatementType = null; Integer actionType = null; Integer x = null; Integer y = null; Long time = null; boolean loggingRelated = false; int actionType = UNINITIALIZED_ACTION; int x = UNINITIALIZED_INT; int y = UNINITIALIZED_INT; long time = UNINITIALIZED_LONG; boolean isLoggingRelated = false; jsonReader.beginObject(); while (jsonReader.hasNext()) { Loading @@ -90,7 +163,18 @@ public class MotionEventReader { actionType = MotionEvent.ACTION_MOVE; } } else if (key.equals("loggingRelated")) { loggingRelated = jsonReader.nextBoolean(); isLoggingRelated = jsonReader.nextBoolean(); } else if (logStatementType != null && logStatementType.equals("MotionEvent") && key.equals("motionEvent")) { if (actionType == UNINITIALIZED_ACTION) { Log.e(TAG, "no actionType assigned in MotionEvent json"); } // Second variant of LogStatement. if (isLoggingRelated) { jsonReader.skipValue(); } else { readEmbeddedMotionEvent(jsonReader, replayData, actionType); } } else { if (DEBUG) { Log.w(TAG, "Unknown JSON key in LogStatement: " + key); Loading @@ -100,14 +184,149 @@ public class MotionEventReader { } jsonReader.endObject(); if (logStatementType != null && time != null && x != null && y != null && actionType != null && logStatementType.equals("MotionEvent") && !loggingRelated) { replayData.mActions.add(actionType); replayData.mXCoords.add(x); replayData.mYCoords.add(y); replayData.mTimes.add(time); if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT && y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION && logStatementType.equals("MotionEvent") && !isLoggingRelated) { // First variant of LogStatement. final PointerProperties pointerProperties = new PointerProperties(); pointerProperties.id = 0; pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; final PointerProperties[] pointerPropertiesArray = { pointerProperties }; final PointerCoords pointerCoords = new PointerCoords(); pointerCoords.x = x; pointerCoords.y = y; pointerCoords.pressure = 1.0f; pointerCoords.size = 1.0f; final PointerCoords[] pointerCoordsArray = { pointerCoords }; addMotionEventData(replayData, actionType, time, pointerPropertiesArray, pointerCoordsArray); } } private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData, final int actionType) throws IOException { jsonReader.beginObject(); PointerProperties[] pointerPropertiesArray = null; while (jsonReader.hasNext()) { // pointerIds/xyt final String name = jsonReader.nextName(); if (name.equals("pointerIds")) { pointerPropertiesArray = readPointerProperties(jsonReader); } else if (name.equals("xyt")) { readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray); } } jsonReader.endObject(); } private PointerProperties[] readPointerProperties(final JsonReader jsonReader) throws IOException { final ArrayList<PointerProperties> pointerPropertiesArrayList = new ArrayList<PointerProperties>(); jsonReader.beginArray(); while (jsonReader.hasNext()) { final PointerProperties pointerProperties = new PointerProperties(); pointerProperties.id = jsonReader.nextInt(); pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; pointerPropertiesArrayList.add(pointerProperties); } jsonReader.endArray(); return pointerPropertiesArrayList.toArray( new PointerProperties[pointerPropertiesArrayList.size()]); } private void readPointerData(final JsonReader jsonReader, final ReplayData replayData, final int actionType, final PointerProperties[] pointerPropertiesArray) throws IOException { if (pointerPropertiesArray == null) { Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent"); jsonReader.skipValue(); return; } long time = UNINITIALIZED_LONG; jsonReader.beginArray(); while (jsonReader.hasNext()) { // Array of historical data jsonReader.beginObject(); final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<PointerCoords>(); while (jsonReader.hasNext()) { // Time/data object final String name = jsonReader.nextName(); if (name.equals("t")) { time = jsonReader.nextLong(); } else if (name.equals("d")) { jsonReader.beginArray(); while (jsonReader.hasNext()) { // array of data per pointer final PointerCoords pointerCoords = readPointerCoords(jsonReader); if (pointerCoords != null) { pointerCoordsArrayList.add(pointerCoords); } } jsonReader.endArray(); } else { jsonReader.skipValue(); } } jsonReader.endObject(); // Data was recorded as historical events, but must be split apart into // separate MotionEvents for replaying if (time != UNINITIALIZED_LONG) { addMotionEventData(replayData, actionType, time, pointerPropertiesArray, pointerCoordsArrayList.toArray( new PointerCoords[pointerCoordsArrayList.size()])); } else { Log.e(TAG, "Time not assigned in json for MotionEvent"); } } jsonReader.endArray(); } private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException { jsonReader.beginObject(); float x = UNINITIALIZED_FLOAT; float y = UNINITIALIZED_FLOAT; while (jsonReader.hasNext()) { // x,y final String name = jsonReader.nextName(); if (name.equals("x")) { x = (float) jsonReader.nextDouble(); } else if (name.equals("y")) { y = (float) jsonReader.nextDouble(); } else { jsonReader.skipValue(); } } jsonReader.endObject(); if (Float.compare(x, UNINITIALIZED_FLOAT) == 0 || Float.compare(y, UNINITIALIZED_FLOAT) == 0) { Log.w(TAG, "missing x or y value in MotionEvent json"); return null; } final PointerCoords pointerCoords = new PointerCoords(); pointerCoords.x = x; pointerCoords.y = y; pointerCoords.pressure = 1.0f; pointerCoords.size = 1.0f; return pointerCoords; } /** * Tests that {@code x} is uninitialized. * * Assumes that {@code x} will never be given a valid value less than 0, and that * UNINITIALIZED_FLOAT is less than 0.0f. */ private boolean isUninitializedFloat(final float x) { return x < 0.0f; } private void addMotionEventData(final ReplayData replayData, final int actionType, final long time, final PointerProperties[] pointerProperties, final PointerCoords[] pointerCoords) { replayData.mActions.add(actionType); replayData.mTimes.add(time); replayData.mPointerPropertiesArrays.add(pointerProperties); replayData.mPointerCoordsArrays.add(pointerCoords); } }
java/src/com/android/inputmethod/research/Replayer.java +16 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.os.Message; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; Loading Loading @@ -62,7 +64,6 @@ public class Replayer { if (mIsReplaying) { return; } mIsReplaying = true; final int numActions = replayData.mActions.size(); if (DEBUG) { Loading Loading @@ -95,25 +96,36 @@ public class Replayer { case MSG_MOTION_EVENT: final int index = msg.arg1; final int action = replayData.mActions.get(index); final int x = replayData.mXCoords.get(index); final int y = replayData.mYCoords.get(index); final PointerProperties[] pointerPropertiesArray = replayData.mPointerPropertiesArrays.get(index); final PointerCoords[] pointerCoordsArray = replayData.mPointerCoordsArrays.get(index); final long origTime = replayData.mTimes.get(index); if (action == MotionEvent.ACTION_DOWN) { mOrigDownTime = origTime; } final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment, origTime + timeAdjustment, action, x, y, 0); origTime + timeAdjustment, action, pointerPropertiesArray.length, pointerPropertiesArray, pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); mainKeyboardView.processMotionEvent(me); me.recycle(); break; case MSG_DONE: mIsReplaying = false; ResearchLogger.getInstance().requestIndicatorRedraw(); break; } } }; handler.post(new Runnable() { @Override public void run() { ResearchLogger.getInstance().requestIndicatorRedraw(); } }); for (int i = 0; i < numActions; i++) { final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0); final long msgTime = replayData.mTimes.get(i) + timeAdjustment; Loading
tests/src/com/android/inputmethod/research/MotionEventReaderTests.java 0 → 100644 +169 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.inputmethod.research; import android.test.AndroidTestCase; import android.util.JsonReader; import com.android.inputmethod.research.MotionEventReader.ReplayData; import java.io.IOException; import java.io.StringReader; public class MotionEventReaderTests extends AndroidTestCase { private MotionEventReader mMotionEventReader = new MotionEventReader(); private ReplayData mReplayData; @Override protected void setUp() throws Exception { super.setUp(); mReplayData = new ReplayData(); } private JsonReader jsonReaderForString(final String s) { return new JsonReader(new StringReader(s)); } public void testTopLevelDataVariant() { final JsonReader jsonReader = jsonReaderForString( "{" + "\"_ct\": 1359590400000," + "\"_ut\": 4381933," + "\"_ty\": \"MotionEvent\"," + "\"action\": \"UP\"," + "\"isLoggingRelated\": false," + "\"x\": 100.0," + "\"y\": 200.0" + "}" ); try { mMotionEventReader.readLogStatement(jsonReader, mReplayData); } catch (IOException e) { e.printStackTrace(); fail("IOException thrown"); } assertEquals("x set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); assertEquals("y set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1); assertEquals("only one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1); } public void testNestedDataVariant() { final JsonReader jsonReader = jsonReaderForString( "{" + " \"_ct\": 135959040000," + " \"_ut\": 4382702," + " \"_ty\": \"MotionEvent\"," + " \"action\": \"MOVE\"," + " \"isLoggingRelated\": false," + " \"motionEvent\": {" + " \"pointerIds\": [" + " 0" + " ]," + " \"xyt\": [" + " {" + " \"t\": 4382551," + " \"d\": [" + " {" + " \"x\": 100.0," + " \"y\": 200.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }" + " ]" + " }," + " {" + " \"t\": 4382559," + " \"d\": [" + " {" + " \"x\": 300.0," + " \"y\": 400.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }" + " ]" + " }" + " ]" + " }" + "}" ); try { mMotionEventReader.readLogStatement(jsonReader, mReplayData); } catch (IOException e) { e.printStackTrace(); fail("IOException thrown"); } assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].x, 300); assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(1)[0].y, 400); assertEquals("only one pointer", mReplayData.mPointerCoordsArrays.get(0).length, 1); assertEquals("two MotionEvents", mReplayData.mPointerCoordsArrays.size(), 2); } public void testNestedDataVariantMultiPointer() { final JsonReader jsonReader = jsonReaderForString( "{" + " \"_ct\": 135959040000," + " \"_ut\": 4382702," + " \"_ty\": \"MotionEvent\"," + " \"action\": \"MOVE\"," + " \"isLoggingRelated\": false," + " \"motionEvent\": {" + " \"pointerIds\": [" + " 1" + " ]," + " \"xyt\": [" + " {" + " \"t\": 4382551," + " \"d\": [" + " {" + " \"x\": 100.0," + " \"y\": 200.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }," + " {" + " \"x\": 300.0," + " \"y\": 400.0," + " \"toma\": 999.0," + " \"tomi\": 999.0," + " \"o\": 0.0" + " }" + " ]" + " }" + " ]" + " }" + "}" ); try { mMotionEventReader.readLogStatement(jsonReader, mReplayData); } catch (IOException e) { e.printStackTrace(); fail("IOException thrown"); } assertEquals("x1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].x, 100); assertEquals("y1 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[0].y, 200); assertEquals("x2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].x, 300); assertEquals("y2 set correctly", (int) mReplayData.mPointerCoordsArrays.get(0)[1].y, 400); assertEquals("two pointers", mReplayData.mPointerCoordsArrays.get(0).length, 2); assertEquals("one MotionEvent", mReplayData.mPointerCoordsArrays.size(), 1); } }