blob: f2adaf042b2f6cb0ae7cd7b6cc6c2c3d931b185a [file] [log] [blame]
Daniel Sandler33805342012-07-23 15:45:12 -04001/*
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
John Spurlockde84f0e2013-06-12 12:41:00 -040019import android.os.Handler;
20import android.os.Message;
21import android.os.SystemClock;
22import android.util.Log;
23import android.view.MotionEvent;
24
Daniel Sandler33805342012-07-23 15:45:12 -040025import java.io.BufferedWriter;
26import java.io.FileDescriptor;
27import java.io.FileWriter;
28import java.io.IOException;
29import java.io.PrintWriter;
30import java.util.HashSet;
31import java.util.LinkedList;
32
Daniel Sandler33805342012-07-23 15:45:12 -040033/**
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;
John Spurlockb8baccc2013-06-05 12:59:01 -040049 this.event = MotionEvent.obtain(event);
Daniel Sandler33805342012-07-23 15:45:12 -040050 }
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() {
Daniel Sandler780d6682012-07-24 12:37:58 -040066 return String.format(
67 ("{\"type\":\"motion\", \"time\":%d, \"action\":\"%s\", "
68 + "\"x\":%.2f, \"y\":%.2f, \"s\":%.2f, \"p\":%.2f}"),
Daniel Sandler33805342012-07-23 15:45:12 -040069 this.time,
70 actionName(this.event.getAction()),
71 this.event.getRawX(),
Daniel Sandler780d6682012-07-24 12:37:58 -040072 this.event.getRawY(),
73 this.event.getSize(),
74 this.event.getPressure()
Daniel Sandler33805342012-07-23 15:45:12 -040075 );
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()) {
John Spurlockcd686b52013-06-05 10:13:46 -0400104 Log.w(TAG, "Assertion failure in GestureRecorder: event downTime ("
Daniel Sandler33805342012-07-23 15:45:12 -0400105 +ev.getDownTime()+") does not match gesture downTime ("+mDownTime+")");
106 }
107 }
108 switch (ev.getActionMasked()) {
109 case MotionEvent.ACTION_UP:
110 case MotionEvent.ACTION_CANCEL:
111 mComplete = true;
112 }
113 }
114 public void tag(long when, String tag, String info) {
115 mRecords.add(new TagRecord(when, tag, info));
116 mTags.add(tag);
117 }
118 public boolean isComplete() {
119 return mComplete;
120 }
121 public String toJson() {
122 StringBuilder sb = new StringBuilder();
123 boolean first = true;
124 sb.append("[");
125 for (Record r : mRecords) {
126 if (!first) sb.append(", ");
127 first = false;
128 sb.append(r.toJson());
129 }
130 sb.append("]");
131 return sb.toString();
132 }
133 }
134
135 // -=-=-=-=-=-=-=-=-=-=-=-
136
137 static final long SAVE_DELAY = 5000; // ms
138 static final int SAVE_MESSAGE = 6351;
139
140 private LinkedList<Gesture> mGestures;
141 private Gesture mCurrentGesture;
142 private int mLastSaveLen = -1;
143 private String mLogfile;
144
145 private Handler mHandler = new Handler() {
146 @Override
147 public void handleMessage(Message msg) {
148 if (msg.what == SAVE_MESSAGE) {
149 save();
150 }
151 }
152 };
153
154 public GestureRecorder(String filename) {
155 mLogfile = filename;
156 mGestures = new LinkedList<Gesture>();
157 mCurrentGesture = null;
158 }
159
160 public void add(MotionEvent ev) {
161 synchronized (mGestures) {
162 if (mCurrentGesture == null || mCurrentGesture.isComplete()) {
163 mCurrentGesture = new Gesture();
164 mGestures.add(mCurrentGesture);
165 }
166 mCurrentGesture.add(ev);
167 }
168 saveLater();
169 }
170
171 public void tag(long when, String tag, String info) {
172 synchronized (mGestures) {
173 if (mCurrentGesture == null) {
174 mCurrentGesture = new Gesture();
175 mGestures.add(mCurrentGesture);
176 }
177 mCurrentGesture.tag(when, tag, info);
178 }
179 saveLater();
180 }
181
182 public void tag(long when, String tag) {
183 tag(when, tag, null);
184 }
185
186 public void tag(String tag) {
187 tag(SystemClock.uptimeMillis(), tag, null);
188 }
189
190 public void tag(String tag, String info) {
191 tag(SystemClock.uptimeMillis(), tag, info);
192 }
193
194 /**
195 * Generates a JSON string capturing all completed gestures.
196 * Not threadsafe; call with a lock.
197 */
198 public String toJsonLocked() {
199 StringBuilder sb = new StringBuilder();
200 boolean first = true;
201 sb.append("[");
202 int count = 0;
203 for (Gesture g : mGestures) {
204 if (!g.isComplete()) continue;
205 if (!first) sb.append("," );
206 first = false;
207 sb.append(g.toJson());
208 count++;
209 }
210 mLastSaveLen = count;
211 sb.append("]");
212 return sb.toString();
213 }
214
215 public String toJson() {
216 String s;
217 synchronized (mGestures) {
218 s = toJsonLocked();
219 }
220 return s;
221 }
222
223 public void saveLater() {
224 mHandler.removeMessages(SAVE_MESSAGE);
225 mHandler.sendEmptyMessageDelayed(SAVE_MESSAGE, SAVE_DELAY);
226 }
227
228 public void save() {
229 synchronized (mGestures) {
230 try {
231 BufferedWriter w = new BufferedWriter(new FileWriter(mLogfile, /*append=*/ true));
232 w.append(toJsonLocked() + "\n");
233 w.close();
234 mGestures.clear();
235 // If we have a pending gesture, push it back
Daniel Sandler75fcac42012-07-25 13:51:44 -0400236 if (mCurrentGesture != null && !mCurrentGesture.isComplete()) {
Daniel Sandler33805342012-07-23 15:45:12 -0400237 mGestures.add(mCurrentGesture);
238 }
239 if (DEBUG) {
John Spurlockcd686b52013-06-05 10:13:46 -0400240 Log.v(TAG, String.format("Wrote %d complete gestures to %s", mLastSaveLen, mLogfile));
Daniel Sandler33805342012-07-23 15:45:12 -0400241 }
242 } catch (IOException e) {
John Spurlockcd686b52013-06-05 10:13:46 -0400243 Log.e(TAG, String.format("Couldn't write gestures to %s", mLogfile), e);
Daniel Sandler33805342012-07-23 15:45:12 -0400244 mLastSaveLen = -1;
245 }
246 }
247 }
248
249 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
250 save();
251 if (mLastSaveLen >= 0) {
252 pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile);
253 } else {
254 pw.println("error writing gestures");
255 }
256 }
257}