Home | History | Annotate | Download | only in statusbar
      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 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.os.Handler;
     20 import android.os.Message;
     21 import android.os.SystemClock;
     22 import android.util.Log;
     23 import android.view.MotionEvent;
     24 
     25 import java.io.BufferedWriter;
     26 import java.io.FileDescriptor;
     27 import java.io.FileWriter;
     28 import java.io.IOException;
     29 import java.io.PrintWriter;
     30 import java.util.HashSet;
     31 import java.util.LinkedList;
     32 
     33 /**
     34  * Convenience class for capturing gestures for later analysis.
     35  */
     36 public 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 = MotionEvent.obtain(event);
     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                     Log.w(TAG, "Assertion failure in GestureRecorder: event downTime ("
    105                             +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
    236                 if (mCurrentGesture != null && !mCurrentGesture.isComplete()) {
    237                     mGestures.add(mCurrentGesture);
    238                 }
    239                 if (DEBUG) {
    240                     Log.v(TAG, String.format("Wrote %d complete gestures to %s", mLastSaveLen, mLogfile));
    241                 }
    242             } catch (IOException e) {
    243                 Log.e(TAG, String.format("Couldn't write gestures to %s", mLogfile), e);
    244                 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 }
    258