/*
|
* Copyright (C) 2012 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.systemui.statusbar;
|
|
import android.os.Handler;
|
import android.os.Message;
|
import android.os.SystemClock;
|
import android.util.Log;
|
import android.view.MotionEvent;
|
|
import java.io.BufferedWriter;
|
import java.io.FileDescriptor;
|
import java.io.FileWriter;
|
import java.io.IOException;
|
import java.io.PrintWriter;
|
import java.util.HashSet;
|
import java.util.LinkedList;
|
|
/**
|
* Convenience class for capturing gestures for later analysis.
|
*/
|
public class GestureRecorder {
|
public static final boolean DEBUG = true; // for now
|
public static final String TAG = GestureRecorder.class.getSimpleName();
|
|
public class Gesture {
|
public abstract class Record {
|
long time;
|
public abstract String toJson();
|
}
|
public class MotionEventRecord extends Record {
|
public MotionEvent event;
|
public MotionEventRecord(long when, MotionEvent event) {
|
this.time = when;
|
this.event = MotionEvent.obtain(event);
|
}
|
String actionName(int action) {
|
switch (action) {
|
case MotionEvent.ACTION_DOWN:
|
return "down";
|
case MotionEvent.ACTION_UP:
|
return "up";
|
case MotionEvent.ACTION_MOVE:
|
return "move";
|
case MotionEvent.ACTION_CANCEL:
|
return "cancel";
|
default:
|
return String.valueOf(action);
|
}
|
}
|
public String toJson() {
|
return String.format(
|
("{\"type\":\"motion\", \"time\":%d, \"action\":\"%s\", "
|
+ "\"x\":%.2f, \"y\":%.2f, \"s\":%.2f, \"p\":%.2f}"),
|
this.time,
|
actionName(this.event.getAction()),
|
this.event.getRawX(),
|
this.event.getRawY(),
|
this.event.getSize(),
|
this.event.getPressure()
|
);
|
}
|
}
|
public class TagRecord extends Record {
|
public String tag, info;
|
public TagRecord(long when, String tag, String info) {
|
this.time = when;
|
this.tag = tag;
|
this.info = info;
|
}
|
public String toJson() {
|
return String.format("{\"type\":\"tag\", \"time\":%d, \"tag\":\"%s\", \"info\":\"%s\"}",
|
this.time,
|
this.tag,
|
this.info
|
);
|
}
|
}
|
private LinkedList<Record> mRecords = new LinkedList<Record>();
|
private HashSet<String> mTags = new HashSet<String>();
|
long mDownTime = -1;
|
boolean mComplete = false;
|
|
public void add(MotionEvent ev) {
|
mRecords.add(new MotionEventRecord(ev.getEventTime(), ev));
|
if (mDownTime < 0) {
|
mDownTime = ev.getDownTime();
|
} else {
|
if (mDownTime != ev.getDownTime()) {
|
Log.w(TAG, "Assertion failure in GestureRecorder: event downTime ("
|
+ev.getDownTime()+") does not match gesture downTime ("+mDownTime+")");
|
}
|
}
|
switch (ev.getActionMasked()) {
|
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_CANCEL:
|
mComplete = true;
|
}
|
}
|
public void tag(long when, String tag, String info) {
|
mRecords.add(new TagRecord(when, tag, info));
|
mTags.add(tag);
|
}
|
public boolean isComplete() {
|
return mComplete;
|
}
|
public String toJson() {
|
StringBuilder sb = new StringBuilder();
|
boolean first = true;
|
sb.append("[");
|
for (Record r : mRecords) {
|
if (!first) sb.append(", ");
|
first = false;
|
sb.append(r.toJson());
|
}
|
sb.append("]");
|
return sb.toString();
|
}
|
}
|
|
// -=-=-=-=-=-=-=-=-=-=-=-
|
|
static final long SAVE_DELAY = 5000; // ms
|
static final int SAVE_MESSAGE = 6351;
|
|
private LinkedList<Gesture> mGestures;
|
private Gesture mCurrentGesture;
|
private int mLastSaveLen = -1;
|
private String mLogfile;
|
|
private Handler mHandler = new Handler() {
|
@Override
|
public void handleMessage(Message msg) {
|
if (msg.what == SAVE_MESSAGE) {
|
save();
|
}
|
}
|
};
|
|
public GestureRecorder(String filename) {
|
mLogfile = filename;
|
mGestures = new LinkedList<Gesture>();
|
mCurrentGesture = null;
|
}
|
|
public void add(MotionEvent ev) {
|
synchronized (mGestures) {
|
if (mCurrentGesture == null || mCurrentGesture.isComplete()) {
|
mCurrentGesture = new Gesture();
|
mGestures.add(mCurrentGesture);
|
}
|
mCurrentGesture.add(ev);
|
}
|
saveLater();
|
}
|
|
public void tag(long when, String tag, String info) {
|
synchronized (mGestures) {
|
if (mCurrentGesture == null) {
|
mCurrentGesture = new Gesture();
|
mGestures.add(mCurrentGesture);
|
}
|
mCurrentGesture.tag(when, tag, info);
|
}
|
saveLater();
|
}
|
|
public void tag(long when, String tag) {
|
tag(when, tag, null);
|
}
|
|
public void tag(String tag) {
|
tag(SystemClock.uptimeMillis(), tag, null);
|
}
|
|
public void tag(String tag, String info) {
|
tag(SystemClock.uptimeMillis(), tag, info);
|
}
|
|
/**
|
* Generates a JSON string capturing all completed gestures.
|
* Not threadsafe; call with a lock.
|
*/
|
public String toJsonLocked() {
|
StringBuilder sb = new StringBuilder();
|
boolean first = true;
|
sb.append("[");
|
int count = 0;
|
for (Gesture g : mGestures) {
|
if (!g.isComplete()) continue;
|
if (!first) sb.append("," );
|
first = false;
|
sb.append(g.toJson());
|
count++;
|
}
|
mLastSaveLen = count;
|
sb.append("]");
|
return sb.toString();
|
}
|
|
public String toJson() {
|
String s;
|
synchronized (mGestures) {
|
s = toJsonLocked();
|
}
|
return s;
|
}
|
|
public void saveLater() {
|
mHandler.removeMessages(SAVE_MESSAGE);
|
mHandler.sendEmptyMessageDelayed(SAVE_MESSAGE, SAVE_DELAY);
|
}
|
|
public void save() {
|
synchronized (mGestures) {
|
try {
|
BufferedWriter w = new BufferedWriter(new FileWriter(mLogfile, /*append=*/ true));
|
w.append(toJsonLocked() + "\n");
|
w.close();
|
mGestures.clear();
|
// If we have a pending gesture, push it back
|
if (mCurrentGesture != null && !mCurrentGesture.isComplete()) {
|
mGestures.add(mCurrentGesture);
|
}
|
if (DEBUG) {
|
Log.v(TAG, String.format("Wrote %d complete gestures to %s", mLastSaveLen, mLogfile));
|
}
|
} catch (IOException e) {
|
Log.e(TAG, String.format("Couldn't write gestures to %s", mLogfile), e);
|
mLastSaveLen = -1;
|
}
|
}
|
}
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
save();
|
if (mLastSaveLen >= 0) {
|
pw.println(String.valueOf(mLastSaveLen) + " gestures written to " + mLogfile);
|
} else {
|
pw.println("error writing gestures");
|
}
|
}
|
}
|