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

Commit a444ee69 authored by Daniel Sandler's avatar Daniel Sandler Committed by Android (Google) Code Review
Browse files

Merge "Collecting some data on notification panel gestures."

parents cdb50046 3380534a
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);
    }