1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.research; 18 19 import android.os.Handler; 20 import android.os.Looper; 21 import android.os.Message; 22 import android.os.SystemClock; 23 import android.util.Log; 24 import android.view.MotionEvent; 25 import android.view.MotionEvent.PointerCoords; 26 import android.view.MotionEvent.PointerProperties; 27 28 import com.android.inputmethod.keyboard.KeyboardSwitcher; 29 import com.android.inputmethod.keyboard.MainKeyboardView; 30 import com.android.inputmethod.latin.define.ProductionFlag; 31 import com.android.inputmethod.research.MotionEventReader.ReplayData; 32 33 /** 34 * Replays a sequence of motion events in realtime on the screen. 35 * 36 * Useful for user inspection of logged data. 37 */ 38 public class Replayer { 39 private static final String TAG = Replayer.class.getSimpleName(); 40 private static final boolean DEBUG = false 41 && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; 42 private static final long START_TIME_DELAY_MS = 500; 43 44 private boolean mIsReplaying = false; 45 private KeyboardSwitcher mKeyboardSwitcher; 46 47 private Replayer() { 48 } 49 50 private static final Replayer sInstance = new Replayer(); 51 public static Replayer getInstance() { 52 return sInstance; 53 } 54 55 public void setKeyboardSwitcher(final KeyboardSwitcher keyboardSwitcher) { 56 mKeyboardSwitcher = keyboardSwitcher; 57 } 58 59 private static final int MSG_MOTION_EVENT = 0; 60 private static final int MSG_DONE = 1; 61 private static final int COMPLETION_TIME_MS = 500; 62 63 // TODO: Support historical events and multi-touch. 64 public void replay(final ReplayData replayData, final Runnable callback) { 65 if (mIsReplaying) { 66 return; 67 } 68 mIsReplaying = true; 69 final int numActions = replayData.mActions.size(); 70 if (DEBUG) { 71 Log.d(TAG, "replaying " + numActions + " actions"); 72 } 73 if (numActions == 0) { 74 mIsReplaying = false; 75 return; 76 } 77 final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); 78 79 // The reference time relative to the times stored in events. 80 final long origStartTime = replayData.mTimes.get(0); 81 // The reference time relative to which events are replayed in the present. 82 final long currentStartTime = SystemClock.uptimeMillis() + START_TIME_DELAY_MS; 83 // The adjustment needed to translate times from the original recorded time to the current 84 // time. 85 final long timeAdjustment = currentStartTime - origStartTime; 86 final Handler handler = new Handler(Looper.getMainLooper()) { 87 // Track the time of the most recent DOWN event, to be passed as a parameter when 88 // constructing a MotionEvent. It's initialized here to the origStartTime, but this is 89 // only a precaution. The value should be overwritten by the first ACTION_DOWN event 90 // before the first use of the variable. Note that this may cause the first few events 91 // to have incorrect {@code downTime}s. 92 private long mOrigDownTime = origStartTime; 93 94 @Override 95 public void handleMessage(final Message msg) { 96 switch (msg.what) { 97 case MSG_MOTION_EVENT: 98 final int index = msg.arg1; 99 final int action = replayData.mActions.get(index); 100 final PointerProperties[] pointerPropertiesArray = 101 replayData.mPointerPropertiesArrays.get(index); 102 final PointerCoords[] pointerCoordsArray = 103 replayData.mPointerCoordsArrays.get(index); 104 final long origTime = replayData.mTimes.get(index); 105 if (action == MotionEvent.ACTION_DOWN) { 106 mOrigDownTime = origTime; 107 } 108 109 final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment, 110 origTime + timeAdjustment, action, 111 pointerPropertiesArray.length, pointerPropertiesArray, 112 pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); 113 mainKeyboardView.processMotionEvent(me); 114 me.recycle(); 115 break; 116 case MSG_DONE: 117 mIsReplaying = false; 118 ResearchLogger.getInstance().requestIndicatorRedraw(); 119 break; 120 } 121 } 122 }; 123 124 handler.post(new Runnable() { 125 @Override 126 public void run() { 127 ResearchLogger.getInstance().requestIndicatorRedraw(); 128 } 129 }); 130 for (int i = 0; i < numActions; i++) { 131 final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0); 132 final long msgTime = replayData.mTimes.get(i) + timeAdjustment; 133 handler.sendMessageAtTime(msg, msgTime); 134 if (DEBUG) { 135 Log.d(TAG, "queuing event at " + msgTime); 136 } 137 } 138 139 final long presentDoneTime = replayData.mTimes.get(numActions - 1) + timeAdjustment 140 + COMPLETION_TIME_MS; 141 handler.sendMessageAtTime(Message.obtain(handler, MSG_DONE), presentDoneTime); 142 if (callback != null) { 143 handler.postAtTime(callback, presentDoneTime + 1); 144 } 145 } 146 147 public boolean isReplaying() { 148 return mIsReplaying; 149 } 150 } 151