HIC: Add report facility for overzealous anti-falsing

Bug: 27405075
Change-Id: I15e5d601af3c7cc067371bbe51c873d2ce15ec83
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
index 91f6520..b1f454e 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/DataCollector.java
@@ -21,6 +21,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Handler;
@@ -28,6 +29,7 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.widget.Toast;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -48,6 +50,8 @@
     private static final String TAG = "DataCollector";
     private static final String COLLECTOR_ENABLE = "data_collector_enable";
     private static final String COLLECT_BAD_TOUCHES = "data_collector_collect_bad_touches";
+    private static final String ALLOW_REJECTED_TOUCH_REPORTS =
+            "data_collector_allow_rejected_touch_reports";
 
     private static final long TIMEOUT_MILLIS = 11000; // 11 seconds.
     public static final boolean DEBUG = false;
@@ -64,6 +68,7 @@
     private boolean mCollectBadTouches = false;
     private boolean mCornerSwiping = false;
     private boolean mTrackingStarted = false;
+    private boolean mAllowReportRejectedTouch = false;
 
     private static DataCollector sInstance = null;
 
@@ -87,6 +92,11 @@
                 mSettingsObserver,
                 UserHandle.USER_ALL);
 
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(ALLOW_REJECTED_TOUCH_REPORTS), false,
+                mSettingsObserver,
+                UserHandle.USER_ALL);
+
         updateConfiguration();
     }
 
@@ -104,10 +114,13 @@
         mCollectBadTouches = mEnableCollector && 0 != Settings.Secure.getInt(
                 mContext.getContentResolver(),
                 COLLECT_BAD_TOUCHES, 0);
+        mAllowReportRejectedTouch = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
+                mContext.getContentResolver(),
+                ALLOW_REJECTED_TOUCH_REPORTS, 0);
     }
 
     private boolean sessionEntrypoint() {
-        if (mEnableCollector && mCurrentSession == null) {
+        if (isEnabled() && mCurrentSession == null) {
             onSessionStart();
             return true;
         }
@@ -115,7 +128,7 @@
     }
 
     private void sessionExitpoint(int result) {
-        if (mEnableCollector && mCurrentSession != null) {
+        if (mCurrentSession != null) {
             onSessionEnd(result);
         }
     }
@@ -130,8 +143,36 @@
         SensorLoggerSession session = mCurrentSession;
         mCurrentSession = null;
 
-        session.end(System.currentTimeMillis(), result);
-        queueSession(session);
+        if (mEnableCollector) {
+            session.end(System.currentTimeMillis(), result);
+            queueSession(session);
+        }
+    }
+
+    public Uri reportRejectedTouch() {
+        if (mCurrentSession == null) {
+            Toast.makeText(mContext, "Generating rejected touch report failed: session timed out.",
+                    Toast.LENGTH_LONG).show();
+            return null;
+        }
+        SensorLoggerSession currentSession = mCurrentSession;
+
+        currentSession.setType(Session.REJECTED_TOUCH_REPORT);
+        currentSession.end(System.currentTimeMillis(), Session.SUCCESS);
+        Session proto = currentSession.toProto();
+
+        byte[] b = Session.toByteArray(proto);
+        File dir = new File(mContext.getExternalCacheDir(), "rejected_touch_reports");
+        dir.mkdir();
+        File touch = new File(dir, "rejected_touch_report_" + System.currentTimeMillis());
+
+        try {
+            new FileOutputStream(touch).write(b);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        return Uri.fromFile(touch);
     }
 
     private void queueSession(final SensorLoggerSession currentSession) {
@@ -164,7 +205,7 @@
 
     @Override
     public synchronized void onSensorChanged(SensorEvent event) {
-        if (mEnableCollector && mCurrentSession != null) {
+        if (isEnabled() && mCurrentSession != null) {
             mCurrentSession.addSensorEvent(event, System.nanoTime());
             enforceTimeout();
         }
@@ -186,7 +227,19 @@
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }
 
+    /**
+     * @return true if data is being collected - either for data gathering or creating a
+     *         rejected touch report.
+     */
     public boolean isEnabled() {
+        return mEnableCollector || mAllowReportRejectedTouch;
+    }
+
+    /**
+     * @return true if the full data set for data gathering should be collected - including
+     *         extensive sensor data, which is is not normally included with rejected touch reports.
+     */
+    public boolean isEnabledFull() {
         return mEnableCollector;
     }
 
@@ -401,8 +454,12 @@
     }
 
     private void addEvent(int eventType) {
-        if (mEnableCollector && mCurrentSession != null) {
+        if (isEnabled() && mCurrentSession != null) {
             mCurrentSession.addPhoneEvent(eventType, System.nanoTime());
         }
     }
+
+    public boolean isReportingEnabled() {
+        return mAllowReportRejectedTouch;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
index faaad2b..b39803a 100644
--- a/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
+++ b/packages/SystemUI/src/com/android/systemui/analytics/SensorLoggerSession.java
@@ -53,6 +53,10 @@
         mType = Session.REAL;
     }
 
+    public void setType(int type) {
+        mType = type;
+    }
+
     public void end(long endTimestampMillis, int result) {
         mResult = result;
         mEndTimestampMillis = endTimestampMillis;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index 1ac5992..664e886 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.UserHandle;
@@ -142,7 +143,7 @@
         if (mHumanInteractionClassifier.isEnabled()) {
             registerSensors(CLASSIFIER_SENSORS);
         }
-        if (mDataCollector.isEnabled()) {
+        if (mDataCollector.isEnabledFull()) {
             registerSensors(COLLECTOR_SENSORS);
         }
     }
@@ -400,4 +401,15 @@
         pw.print("mScreenOn="); pw.println(mScreenOn ? 1 : 0);
         pw.println();
     }
+
+    public Uri reportRejectedTouch() {
+        if (mDataCollector.isEnabled()) {
+            return mDataCollector.reportRejectedTouch();
+        }
+        return null;
+    }
+
+    public boolean isReportingEnabled() {
+        return mDataCollector.isReportingEnabled();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index b623f19..1b0a05c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -73,6 +73,7 @@
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
@@ -85,6 +86,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
@@ -195,6 +197,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -365,6 +368,8 @@
     private View mPendingRemoteInputView;
     private View mPendingWorkRemoteInputView;
 
+    private View mReportRejectedTouch;
+
     int mMaxAllowedKeyguardNotifications;
 
     boolean mExpandedVisible;
@@ -929,6 +934,36 @@
                 mBatteryController);
         mKeyguardStatusBar.setBatteryController(mBatteryController);
 
+        mReportRejectedTouch = mStatusBarWindow.findViewById(R.id.report_rejected_touch);
+        if (mReportRejectedTouch != null) {
+            updateReportRejectedTouchVisibility();
+            mReportRejectedTouch.setOnClickListener(v -> {
+                Uri session = mFalsingManager.reportRejectedTouch();
+                if (session == null) { return; }
+
+                StringWriter message = new StringWriter();
+                message.write("Build info: ");
+                message.write(SystemProperties.get("ro.build.description"));
+                message.write("\nSerial number: ");
+                message.write(SystemProperties.get("ro.serialno"));
+                message.write("\n");
+
+                PrintWriter falsingPw = new PrintWriter(message);
+                FalsingLog.dump(falsingPw);
+                falsingPw.flush();
+
+                startActivityDismissingKeyguard(Intent.createChooser(new Intent(Intent.ACTION_SEND)
+                                .setType("*/*")
+                                .putExtra(Intent.EXTRA_SUBJECT, "Rejected touch report")
+                                .putExtra(Intent.EXTRA_STREAM, session)
+                                .putExtra(Intent.EXTRA_TEXT, message.toString()),
+                        "Share rejected touch report")
+                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                        true /* onlyProvisioned */, true /* dismissShade */);
+            });
+        }
+
+
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mBroadcastReceiver.onReceive(mContext,
                 new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF));
@@ -2260,6 +2295,14 @@
         Trace.endSection();
     }
 
+    private void updateReportRejectedTouchVisibility() {
+        if (mReportRejectedTouch == null) {
+            return;
+        }
+        mReportRejectedTouch.setVisibility(mState == StatusBarState.KEYGUARD
+                && mFalsingManager.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE);
+    }
+
     protected int adjustDisableFlags(int state) {
         if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway
                 && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
@@ -4388,6 +4431,7 @@
         mGroupManager.setStatusBarState(state);
         mFalsingManager.setStatusBarState(state);
         mStatusBarWindowManager.setStatusBarState(state);
+        updateReportRejectedTouchVisibility();
         updateDozing();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/touch_analytics.proto b/packages/SystemUI/src/com/android/systemui/statusbar/phone/touch_analytics.proto
index afc8f77..50fd52a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/touch_analytics.proto
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/touch_analytics.proto
@@ -120,6 +120,7 @@
         RESERVED_2 = 1;
         RANDOM_WAKEUP = 2;
         REAL = 3;
+        REJECTED_TOUCH_REPORT = 4;
     }
 
     optional uint64 startTimestampMillis = 1;