Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3380534a authored by Daniel Sandler's avatar Daniel Sandler
Browse files

Collecting some data on notification panel gestures.

Look for it in /sdcard/statusbar_gestures.dat, in "JSON
lines" format: one list of gestures per line; each gesture
is itself a list of objects representing motion events and
tags (annotations).

Exploded example:

  [ // list of gestures
    [ // this starts a gesture
      {"type":"motion",
       "time":1347697,  // in SystemClock.uptimeMillis() base,
                        // like MotionEvents
       "action":"down", // down, up, move, cancel, else numeric
       "x":277.61,
       "y":1.00
      },
      {"type":"tag",
       "time":1347701,
       "tag":"tracking", // "tracking" or "fling"
       "info":"collapsed" // extra stuff
      },
      ... // more events
    ],
    ... // more gestures
  ]
  // newline
  [ // another list of gestures
    ...
  ]
  ...

Change-Id: Ifacbf03749c879cd82fb899289fb79a4bdd4fc3b
parent 0e848d4e
Loading
Loading
Loading
Loading
+254 −0
Original line number Diff line number Diff line
/*
 * 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 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;

import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;
import android.view.MotionEvent;

/**
 * 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 = event.copy();
            }
            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}",
                        this.time,
                        actionName(this.event.getAction()),
                        this.event.getRawX(),
                        this.event.getRawY()
                        );
            }
        }
        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()) {
                    // TODO: remove
                    throw new RuntimeException("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.isComplete()) {
                    mGestures.add(mCurrentGesture);
                }
                if (DEBUG) {
                    Slog.v(TAG, String.format("Wrote %d complete gestures to %s", mLastSaveLen, mLogfile));
                }
            } catch (IOException e) {
                Slog.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");
        }
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ import com.android.systemui.UniverseBackground;
import com.android.systemui.recent.RecentTasksLoader;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.RotationToggle;
@@ -242,6 +243,9 @@ public class PhoneStatusBar extends BaseStatusBar {

    DisplayMetrics mDisplayMetrics = new DisplayMetrics();

    // XXX: gesture research
    private GestureRecorder mGestureRec = new GestureRecorder("/sdcard/statusbar_gestures.dat");

    private int mNavigationIconHints = 0;
    private final Animator.AnimatorListener mMakeIconsInvisible = new AnimatorListenerAdapter() {
        @Override
@@ -1570,6 +1574,8 @@ public class PhoneStatusBar extends BaseStatusBar {
            }
        }

        mGestureRec.add(event);

        if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
            return false;
        }
@@ -1604,6 +1610,7 @@ public class PhoneStatusBar extends BaseStatusBar {
                if (x >= edgeBorder && x < mDisplayMetrics.widthPixels - edgeBorder) {
                    prepareTracking(y, !mExpanded);// opening if we're not already fully visible
                    trackMovement(event);
                    mGestureRec.tag("tracking", mExpanded ? "expanded" : "collapsed");
                }
            }
        } else if (mTracking) {
@@ -1654,6 +1661,8 @@ public class PhoneStatusBar extends BaseStatusBar {
                    mFlingY = y;
                }
                mFlingVelocity = vel;
                mGestureRec.tag("fling " + ((mFlingVelocity > 0) ? "open" : "closed"),
                                "v=" + mFlingVelocity);
                mHandler.post(mPerformFling);
            }

@@ -1938,6 +1947,9 @@ public class PhoneStatusBar extends BaseStatusBar {
            }
        }

        pw.print("  status bar gestures: ");
        mGestureRec.dump(fd, pw, args);

        mNetworkController.dump(fd, pw, args);
    }