GestureRecorder.java revision 75fcac4eebdf7ff68a534e9af1664c571f40ef30
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.systemui.statusbar; 18 19import java.io.BufferedWriter; 20import java.io.FileDescriptor; 21import java.io.FileWriter; 22import java.io.IOException; 23import java.io.PrintWriter; 24import java.util.HashSet; 25import java.util.LinkedList; 26 27import android.os.Handler; 28import android.os.Message; 29import android.os.SystemClock; 30import android.util.Slog; 31import android.view.MotionEvent; 32 33/** 34 * Convenience class for capturing gestures for later analysis. 35 */ 36public class GestureRecorder { 37 public static final boolean DEBUG = true; // for now 38 public static final String TAG = GestureRecorder.class.getSimpleName(); 39 40 public class Gesture { 41 public abstract class Record { 42 long time; 43 public abstract String toJson(); 44 } 45 public class MotionEventRecord extends Record { 46 public MotionEvent event; 47 public MotionEventRecord(long when, MotionEvent event) { 48 this.time = when; 49 this.event = event.copy(); 50 } 51 String actionName(int action) { 52 switch (action) { 53 case MotionEvent.ACTION_DOWN: 54 return "down"; 55 case MotionEvent.ACTION_UP: 56 return "up"; 57 case MotionEvent.ACTION_MOVE: 58 return "move"; 59 case MotionEvent.ACTION_CANCEL: 60 return "cancel"; 61 default: 62 return String.valueOf(action); 63 } 64 } 65 public String toJson() { 66 return String.format( 67 ("{\"type\":\"motion\", \"time\":%d, \"action\":\"%s\", " 68 + "\"x\":%.2f, \"y\":%.2f, \"s\":%.2f, \"p\":%.2f}"), 69 this.time, 70 actionName(this.event.getAction()), 71 this.event.getRawX(), 72 this.event.getRawY(), 73 this.event.getSize(), 74 this.event.getPressure() 75 ); 76 } 77 } 78 public class TagRecord extends Record { 79 public String tag, info; 80 public TagRecord(long when, String tag, String info) { 81 this.time = when; 82 this.tag = tag; 83 this.info = info; 84 } 85 public String toJson() { 86 return String.format("{\"type\":\"tag\", \"time\":%d, \"tag\":\"%s\", \"info\":\"%s\"}", 87 this.time, 88 this.tag, 89 this.info 90 ); 91 } 92 } 93 private LinkedList<Record> mRecords = new LinkedList<Record>(); 94 private HashSet<String> mTags = new HashSet<String>(); 95 long mDownTime = -1; 96 boolean mComplete = false; 97 98 public void add(MotionEvent ev) { 99 mRecords.add(new MotionEventRecord(ev.getEventTime(), ev)); 100 if (mDownTime < 0) { 101 mDownTime = ev.getDownTime(); 102 } else { 103 if (mDownTime != ev.getDownTime()) { 104 // TODO: remove 105 throw new RuntimeException("Assertion failure in GestureRecorder: event downTime (" 106 +ev.getDownTime()+") does not match gesture downTime ("+mDownTime+")"); 107 } 108 } 109 switch (ev.getActionMasked()) { 110 case MotionEvent.ACTION_UP: 111 case MotionEvent.ACTION_CANCEL: 112 mComplete = true; 113 } 114 } 115 public void tag(long when, String tag, String info) { 116 mRecords.add(new TagRecord(when, tag, info)); 117 mTags.add(tag); 118 } 119 public boolean isComplete() { 120 return mComplete; 121 } 122 public String toJson() { 123 StringBuilder sb = new StringBuilder(); 124 boolean first = true; 125 sb.append("["); 126 for (Record r : mRecords) { 127 if (!first) sb.append(", "); 128 first = false; 129 sb.append(r.toJson()); 130 } 131 sb.append("]"); 132 return sb.toString(); 133 } 134 } 135 136 // -=-=-=-=-=-=-=-=-=-=-=- 137 138 static final long SAVE_DELAY = 5000; // ms 139 static final int SAVE_MESSAGE = 6351; 140 141 private LinkedList<Gesture> mGestures; 142 private Gesture mCurrentGesture; 143 private int mLastSaveLen = -1; 144 private String mLogfile; 145 146 private Handler mHandler = new Handler() { 147 @Override 148 public void handleMessage(Message msg) { 149 if (msg.what == SAVE_MESSAGE) { 150 save(); 151 } 152 } 153 }; 154 155 public GestureRecorder(String filename) { 156 mLogfile = filename; 157 mGestures = new LinkedList<Gesture>(); 158 mCurrentGesture = null; 159 } 160 161 public void add(MotionEvent ev) { 162 synchronized (mGestures) { 163 if (mCurrentGesture == null || mCurrentGesture.isComplete()) { 164 mCurrentGesture = new Gesture(); 165 mGestures.add(mCurrentGesture); 166 } 167 mCurrentGesture.add(ev); 168 } 169 saveLater(); 170 } 171 172 public void tag(long when, String tag, String info) { 173 synchronized (mGestures) { 174 if (mCurrentGesture == null) { 175 mCurrentGesture = new Gesture(); 176 mGestures.add(mCurrentGesture); 177 } 178 mCurrentGesture.tag(when, tag, info); 179 } 180 saveLater(); 181 } 182 183 public void tag(long when, String tag) { 184 tag(when, tag, null); 185 } 186 187 public void tag(String tag) { 188 tag(SystemClock.uptimeMillis(), tag, null); 189 } 190 191 public void tag(String tag, String info) { 192 tag(SystemClock.uptimeMillis(), tag, info); 193 } 194 195 /** 196 * Generates a JSON string capturing all completed gestures. 197 * Not threadsafe; call with a lock. 198 */ 199 public String toJsonLocked() { 200 StringBuilder sb = new StringBuilder(); 201 boolean first = true; 202 sb.append("["); 203 int count = 0; 204 for (Gesture g : mGestures) { 205 if (!g.isComplete()) continue; 206 if (!first) sb.append("," ); 207 first = false; 208 sb.append(g.toJson()); 209 count++; 210 } 211 mLastSaveLen = count; 212 sb.append("]"); 213 return sb.toString(); 214 } 215 216 public String toJson() { 217 String s; 218 synchronized (mGestures) { 219 s = toJsonLocked(); 220 } 221 return s; 222 } 223 224 public void saveLater() { 225 mHandler.removeMessages(SAVE_MESSAGE); 226 mHandler.sendEmptyMessageDelayed(SAVE_MESSAGE, SAVE_DELAY); 227 } 228 229 public void save() { 230 synchronized (mGestures) { 231 try { 232 BufferedWriter w = new BufferedWriter(new FileWriter(mLogfile, /*append=*/ true)); 233 w.append(toJsonLocked() + "\n"); 234 w.close(); 235 mGestures.clear(); 236 // If we have a pending gesture, push it back 237 if (mCurrentGesture != null && !mCurrentGesture.isComplete()) { 238 mGestures.add(mCurrentGesture); 239 } 240 if (DEBUG) { 241 Slog.v(TAG, String.format("Wrote %d complete gestures to %s", mLastSaveLen, mLogfile)); 242 } 243 } catch (IOException e) { 244 Slog.e(TAG, String.format("Couldn't write gestures to %s", mLogfile), e); 245 mLastSaveLen = -1; 246 } 247 } 248 } 249 250 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 251 save(); 252 if (mLastSaveLen >= 0) { 253 pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile); 254 } else { 255 pw.println("error writing gestures"); 256 } 257 } 258} 259