Merge changes from topic "emergency-dialer-metrics" am: fa47390a8f am: 6f4617b6d2
am: f542347b36

Change-Id: Ie2e77308094805bdf0ca3512c2ab91e9361972fa
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 5d18f8e..84c2d65 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -23,6 +23,7 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.app.KeyguardManager;
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -31,12 +32,18 @@
 import android.database.DataSetObserver;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.media.AudioManager;
 import android.media.ToneGenerator;
+import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.telecom.ParcelableCallAnalytics;
 import android.telecom.PhoneAccount;
@@ -71,6 +78,8 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.phone.common.dialpad.DialpadKeyButton;
 import com.android.phone.common.util.ViewUtil;
 import com.android.phone.common.widget.ResizingTextEditText;
@@ -108,13 +117,81 @@
 public class EmergencyDialer extends Activity implements View.OnClickListener,
         View.OnLongClickListener, View.OnKeyListener, TextWatcher,
         DialpadKeyButton.OnPressedListener, ColorExtractor.OnColorsChangedListener,
-        EmergencyShortcutButton.OnConfirmClickListener {
+        EmergencyShortcutButton.OnConfirmClickListener, SensorEventListener {
+
+    private class MetricsWriter {
+        // Metrics constants indicating the entry type that user opened emergency dialer.
+        // This info is sent from system UI with EXTRA_ENTRY_TYPE. Please make them being
+        // in sync with those in com.android.systemui.util.EmergencyDialerConstants.
+        public static final int ENTRY_TYPE_UNKNOWN = 0;
+        public static final int ENTRY_TYPE_LOCKSCREEN_BUTTON = 1;
+        public static final int ENTRY_TYPE_POWER_MENU = 2;
+
+        // Metrics constants indicating the UI that user made phone call.
+        public static final int CALL_SOURCE_DIALPAD = 0;
+        public static final int CALL_SOURCE_SHORTCUT = 1;
+
+        // Metrics constants indicating the phone number type of a call user made.
+        public static final int PHONE_NUMBER_TYPE_GENERAL = 0;
+        public static final int PHONE_NUMBER_TYPE_EMERGENCY = 1;
+
+        // Metrics constants indicating the actions performed by user.
+        public static final int USER_ACTION_NONE = 0x0;
+        public static final int USER_ACTION_OPEN_DIALPAD = 0x1;
+        public static final int USER_ACTION_OPEN_EMERGENCY_INFO = 0x2;
+        public static final int USER_ACTION_MAKE_CALL_VIA_DIALPAD = 0x4;
+        public static final int USER_ACTION_MAKE_CALL_VIA_SHORTCUT = 0x8;
+
+        private MetricsLogger mMetricsLogger = new MetricsLogger();
+
+        public void writeMetricsForEnter() {
+            int entryType = getIntent().getIntExtra(EXTRA_ENTRY_TYPE, ENTRY_TYPE_UNKNOWN);
+            KeyguardManager keyguard = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+            mMetricsLogger.write(new LogMaker(MetricsEvent.EMERGENCY_DIALER)
+                    .setType(MetricsEvent.TYPE_OPEN)
+                    .setSubtype(entryType)
+                    .addTaggedData(MetricsEvent.FIELD_EMERGENCY_DIALER_IS_SCREEN_LOCKED,
+                            keyguard.isKeyguardLocked() ? 1 : 0));
+        }
+
+        public void writeMetricsForExit() {
+            int entryType = getIntent().getIntExtra(EXTRA_ENTRY_TYPE, ENTRY_TYPE_UNKNOWN);
+            long userStayDuration = SystemClock.elapsedRealtime() - mUserEnterTimeMillis;
+            mMetricsLogger.write(new LogMaker(MetricsEvent.EMERGENCY_DIALER)
+                    .setType(MetricsEvent.TYPE_CLOSE)
+                    .setSubtype(entryType)
+                    .addTaggedData(MetricsEvent.FIELD_EMERGENCY_DIALER_USER_ACTIONS, mUserActions)
+                    .addTaggedData(
+                            MetricsEvent.FIELD_EMERGENCY_DIALER_DURATION_MS, userStayDuration));
+        }
+
+        public void writeMetricsForMakingCall(int callSource, int phoneNumberType,
+                boolean hasShortcut) {
+            mMetricsLogger.write(new LogMaker(MetricsEvent.EMERGENCY_DIALER_MAKE_CALL)
+                    .setType(MetricsEvent.TYPE_ACTION)
+                    .setSubtype(callSource)
+                    .addTaggedData(MetricsEvent.FIELD_EMERGENCY_DIALER_PHONE_NUMBER_TYPE,
+                            phoneNumberType)
+                    .addTaggedData(MetricsEvent.FIELD_EMERGENCY_DIALER_PHONE_NUMBER_HAS_SHORTCUT,
+                            hasShortcut ? 1 : 0)
+                    .addTaggedData(MetricsEvent.FIELD_EMERGENCY_DIALER_IN_POCKET,
+                            mIsProximityNear ? 1 : 0));
+        }
+    }
+
     // Keys used with onSaveInstanceState().
     private static final String LAST_NUMBER = "lastNumber";
 
     // Intent action for this activity.
     public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
 
+    /**
+     * Extra included in {@link #ACTION_DIAL} to indicate the entry type that user starts
+     * the emergency dialer.
+     */
+    public static final String EXTRA_ENTRY_TYPE =
+            "com.android.phone.EmergencyDialer.extra.ENTRY_TYPE";
+
     // List of dialer button IDs.
     private static final int[] DIALER_KEYS = new int[] {
             R.id.one, R.id.two, R.id.three,
@@ -212,6 +289,22 @@
 
     private boolean mAreEmergencyDialerShortcutsEnabled;
 
+    private MetricsWriter mMetricsWriter;
+    private SensorManager mSensorManager;
+    private Sensor mProximitySensor;
+    private boolean mIsProximityNear = false;
+
+    /**
+     * The time, in millis, since boot when user opened emergency dialer.
+     * This is used when calculating the user stay duration for metrics data.
+     */
+    private long mUserEnterTimeMillis = 0;
+
+    /**
+     * Bit flag indicating the actions performed by user. This is used for metrics data.
+     */
+    private int mUserActions = MetricsWriter.USER_ACTION_NONE;
+
     @Override
     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
         // Do nothing
@@ -246,6 +339,12 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        mMetricsWriter = new MetricsWriter();
+        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
+        if (mSensorManager != null) {
+            mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        }
+
         // Allow this activity to be displayed in front of the keyguard / lockscreen.
         setShowWhenLocked(true);
         // Allow turning screen on
@@ -484,6 +583,13 @@
     public void onConfirmClick(EmergencyShortcutButton button) {
         if (button == null) return;
 
+        mUserActions |= MetricsWriter.USER_ACTION_MAKE_CALL_VIA_SHORTCUT;
+
+        // We interest on the context when user has intention to make phone call,
+        // so write metrics here for shortcut number even the call may not be created.
+        mMetricsWriter.writeMetricsForMakingCall(MetricsWriter.CALL_SOURCE_SHORTCUT,
+                MetricsWriter.PHONE_NUMBER_TYPE_EMERGENCY, true);
+
         String phoneNumber = button.getPhoneNumber();
 
         if (!TextUtils.isEmpty(phoneNumber)) {
@@ -517,11 +623,13 @@
                 return;
             }
             case R.id.floating_action_button_dialpad: {
+                mUserActions |= MetricsWriter.USER_ACTION_OPEN_DIALPAD;
                 mDigits.getText().clear();
                 switchView(mDialpadView, mEmergencyShortcutView, true);
                 return;
             }
             case R.id.emergency_info_button: {
+                mUserActions |= MetricsWriter.USER_ACTION_OPEN_EMERGENCY_INFO;
                 Intent intent = (Intent) view.getTag(R.id.tag_intent);
                 if (intent != null) {
                     startActivity(intent);
@@ -623,6 +731,11 @@
     @Override
     protected void onStart() {
         super.onStart();
+
+        mUserEnterTimeMillis = SystemClock.elapsedRealtime();
+        mUserActions = MetricsWriter.USER_ACTION_NONE;
+        mMetricsWriter.writeMetricsForEnter();
+
         // It does not support dark text theme, when emergency dialer shortcuts are enabled.
         // And set background color to black.
         if (mAreEmergencyDialerShortcutsEnabled) {
@@ -668,6 +781,11 @@
     protected void onResume() {
         super.onResume();
 
+        if (mProximitySensor != null) {
+            mSensorManager.registerListener(
+                    this, mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
+        }
+
         // retrieve the DTMF tone play back setting.
         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
@@ -692,11 +810,15 @@
     @Override
     public void onPause() {
         super.onPause();
+        if (mProximitySensor != null) {
+            mSensorManager.unregisterListener(this, mProximitySensor);
+        }
     }
 
     @Override
     protected void onStop() {
         super.onStop();
+        mMetricsWriter.writeMetricsForExit();
         mColorExtractor.removeOnColorsChangedListener(this);
     }
 
@@ -734,6 +856,7 @@
      * place the call, but check to make sure it is a viable number.
      */
     private void placeCall() {
+        mUserActions |= MetricsWriter.USER_ACTION_MAKE_CALL_VIA_DIALPAD;
         mLastNumber = mDigits.getText().toString();
 
         // Convert into emergency number according to emergency conversion map.
@@ -750,6 +873,10 @@
                 playTone(ToneGenerator.TONE_PROP_NACK);
                 return;
             }
+
+            mMetricsWriter.writeMetricsForMakingCall(MetricsWriter.CALL_SOURCE_DIALPAD,
+                    MetricsWriter.PHONE_NUMBER_TYPE_EMERGENCY, isShortcutNumber(mLastNumber));
+
             Bundle extras = new Bundle();
             extras.putInt(TelecomManager.EXTRA_CALL_SOURCE,
                     ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD);
@@ -758,6 +885,11 @@
         } else {
             if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
 
+            // We interest on the context when user has intention to make phone call,
+            // so write metrics here for non-emergency numbers even these numbers are rejected.
+            mMetricsWriter.writeMetricsForMakingCall(MetricsWriter.CALL_SOURCE_DIALPAD,
+                    MetricsWriter.PHONE_NUMBER_TYPE_GENERAL, false);
+
             showDialog(BAD_EMERGENCY_NUMBER_DIALOG);
         }
         mDigits.getText().delete(0, mDigits.getText().length());
@@ -1145,4 +1277,30 @@
                     }
                 });
     }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        float distance = event.values[0];
+        mIsProximityNear = (distance < mProximitySensor.getMaximumRange());
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // Not used.
+    }
+
+    private boolean isShortcutNumber(String number) {
+        if (TextUtils.isEmpty(number) || mEmergencyShortcutButtonList == null) {
+            return false;
+        }
+
+        boolean isShortcut = false;
+        for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) {
+            if (button != null && number.equals(button.getPhoneNumber())) {
+                isShortcut = true;
+                break;
+            }
+        }
+        return isShortcut;
+    }
 }
diff --git a/src/com/android/phone/EmergencyShortcutButton.java b/src/com/android/phone/EmergencyShortcutButton.java
index 275dac0..59b3794 100644
--- a/src/com/android/phone/EmergencyShortcutButton.java
+++ b/src/com/android/phone/EmergencyShortcutButton.java
@@ -19,6 +19,8 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
+import android.metrics.LogMaker;
+import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -30,6 +32,9 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 /**
  * Emergency shortcut button displays a local emergency phone number information(including phone
  * number, and phone type). To decrease false clicking, it need to click twice to confirm to place
@@ -61,6 +66,12 @@
 
     private boolean mConfirmViewHiding;
 
+    /**
+     * The time, in millis, since boot when user taps on shortcut button to reveal confirm view.
+     * This is used for metrics when calculating the interval between reveal tap and confirm tap.
+     */
+    private long mTimeOfRevealTapInMillis = 0;
+
     public EmergencyShortcutButton(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -185,6 +196,8 @@
         switch (view.getId()) {
             case R.id.emergency_call_number_info_view:
                 if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
+                    // TalkBack itself includes a prompt to confirm click action implicitly,
+                    // so we don't need an additional confirmation with second tap on button.
                     if (mOnConfirmClickListener != null) {
                         mOnConfirmClickListener.onConfirmClick(this);
                     }
@@ -193,6 +206,15 @@
                 }
                 break;
             case R.id.emergency_call_confirm_view:
+                if (mTimeOfRevealTapInMillis != 0) {
+                    long timeBetweenTwoTaps =
+                            SystemClock.elapsedRealtime() - mTimeOfRevealTapInMillis;
+                    // Reset reveal time to zero for next reveal-confirm taps pair.
+                    mTimeOfRevealTapInMillis = 0;
+
+                    writeMetricsForConfirmTap(timeBetweenTwoTaps);
+                }
+
                 if (mOnConfirmClickListener != null) {
                     mOnConfirmClickListener.onConfirmClick(this);
                 }
@@ -204,6 +226,7 @@
         mConfirmViewHiding = false;
 
         mConfirmView.setVisibility(View.VISIBLE);
+        mTimeOfRevealTapInMillis = SystemClock.elapsedRealtime();
         int centerX = mCallNumberInfoView.getLeft() + mCallNumberInfoView.getWidth() / 2;
         int centerY = mCallNumberInfoView.getTop() + mCallNumberInfoView.getHeight() / 2;
         Animator reveal = ViewAnimationUtils.createCircularReveal(
@@ -240,6 +263,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mConfirmView.setVisibility(INVISIBLE);
+                // Reset reveal time to zero for next reveal-confirm taps pair.
+                mTimeOfRevealTapInMillis = 0;
             }
         });
         reveal.start();
@@ -254,4 +279,12 @@
             hideSelectedButton();
         }
     };
+
+    private void writeMetricsForConfirmTap(long timeBetweenTwoTaps) {
+        LogMaker logContent = new LogMaker(MetricsEvent.EMERGENCY_DIALER_SHORTCUT_CONFIRM_TAP)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .addTaggedData(MetricsEvent.FIELD_EMERGENCY_DIALER_SHORTCUT_TAPS_INTERVAL,
+                        timeBetweenTwoTaps);
+        MetricsLogger.action(logContent);
+    }
 }