[3/3] Flip to Mute/Reject Call

Ported over from 4.2 implementation (thanks sasikumardr for that)

Original author of this patch was bugadani

We did just a cleanup

Provide easy mute and dismiss functionality for the phone application.

Available actions:

0: mute ringer
1: dismiss call
2: no action (default)

PS:
- fix NPE

Change-Id: Ia10df0045e9919c6b7efc97857abaeba246c365d
diff --git a/src/com/android/incallui/AccelerometerListener.java b/src/com/android/incallui/AccelerometerListener.java
index 1a70778..0384ad2 100644
--- a/src/com/android/incallui/AccelerometerListener.java
+++ b/src/com/android/incallui/AccelerometerListener.java
@@ -23,8 +23,14 @@
 import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.Message;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.telephony.ITelephony;
+
+import java.lang.reflect.Method;
+
 /**
  * This class is used to listen to the accelerometer to monitor the
  * orientation of the phone. The client of this class is notified when
@@ -59,16 +65,34 @@
     private static final int HORIZONTAL_DEBOUNCE = 500;
     private static final double VERTICAL_ANGLE = 50.0;
 
+    // Flip action IDs
+    private static final int MUTE_RINGER = 0;
+    private static final int DISMISS_CALL = 1;
+    private static final int RINGING_NO_ACTION = 2;
+
+    private Context mContext;
+    private ITelephony mTelephonyService;
+
     public interface OrientationListener {
         public void orientationChanged(int orientation);
     }
 
+    private interface ResettableSensorEventListener extends SensorEventListener {
+        public void reset();
+    }
+
     public AccelerometerListener(Context context, OrientationListener listener) {
         mListener = listener;
         mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
     }
 
+    public AccelerometerListener(Context context) {
+        mContext = context;
+        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        mTelephonyService = getTeleService();
+    }
+
     public void enable(boolean enable) {
         if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
         synchronized (this) {
@@ -84,6 +108,25 @@
         }
     }
 
+    public void enableSensor(boolean enable) {
+        if (DEBUG) Log.d(TAG, "enableSensor(" + enable + ")");
+        int action = getFlipAction();
+        synchronized (this) {
+            if (enable) {
+                if (action != RINGING_NO_ACTION) {
+                    mFlipListener.reset();
+                    mSensorManager.registerListener(mFlipListener,
+                            mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+                            SensorManager.SENSOR_DELAY_NORMAL);
+                }
+            } else {
+                if (action != RINGING_NO_ACTION) {
+                    mSensorManager.unregisterListener(mFlipListener);
+                }
+            }
+        }
+    }
+
     private void setOrientation(int orientation) {
         synchronized (this) {
             if (mPendingOrientation == orientation) {
@@ -140,6 +183,123 @@
         }
     };
 
+    private final ResettableSensorEventListener
+                mFlipListener = new ResettableSensorEventListener() {
+        // Our accelerometers are not quite accurate.
+        private static final int FACE_UP_GRAVITY_THRESHOLD = 7;
+        private static final int FACE_DOWN_GRAVITY_THRESHOLD = -7;
+        private static final int TILT_THRESHOLD = 3;
+        private static final int SENSOR_SAMPLES = 3;
+        private static final int MIN_ACCEPT_COUNT = SENSOR_SAMPLES - 1;
+
+        private boolean mStopped;
+        private boolean mWasFaceUp;
+        private boolean[] mSamples = new boolean[SENSOR_SAMPLES];
+        private int mSampleIndex;
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int acc) {
+        }
+
+        @Override
+        public void reset() {
+            mWasFaceUp = false;
+            mStopped = false;
+            for (int i = 0; i < SENSOR_SAMPLES; i++) {
+                mSamples[i] = false;
+            }
+        }
+
+        private boolean filterSamples() {
+            int trues = 0;
+            for (boolean sample : mSamples) {
+                if(sample) {
+                    ++trues;
+                }
+            }
+            return trues >= MIN_ACCEPT_COUNT;
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mStopped) {
+                return;
+            }
+            // Add a sample overwriting the oldest one. Several samples
+            // are used to avoid the erroneous values the sensor sometimes
+            // returns.
+            float z = event.values[2];
+
+            if (!mWasFaceUp) {
+                // Check if its face up enough.
+                mSamples[mSampleIndex] = z > FACE_UP_GRAVITY_THRESHOLD;
+
+                // face up
+                if (filterSamples()) {
+                    mWasFaceUp = true;
+                    for (int i = 0; i < SENSOR_SAMPLES; i++) {
+                        mSamples[i] = false;
+                    }
+                }
+            } else {
+                // Check if its face down enough.
+                mSamples[mSampleIndex] = z < FACE_DOWN_GRAVITY_THRESHOLD;
+
+                // face down
+                if (filterSamples()) {
+                    mStopped = true;
+                    handleAction();
+                }
+            }
+
+            mSampleIndex = ((mSampleIndex + 1) % SENSOR_SAMPLES);
+        }
+    };
+
+    public void handleAction() {
+        switch(getFlipAction()) {
+            case MUTE_RINGER:
+                if (mTelephonyService!= null) {
+                    try{
+                        mTelephonyService.silenceRinger();
+                    }catch(android.os.RemoteException e){
+                        Log.d(TAG, e.toString());
+                    }
+                }
+                break;
+            case DISMISS_CALL:
+                CallCommandClient.getInstance().rejectCall(
+                        CallList.getInstance().getIncomingCall(), false, null);
+                break;
+            case RINGING_NO_ACTION:
+            default:
+                //no action
+                break;
+        }
+    }
+
+    private int getFlipAction(){
+        return Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.CALL_FLIP_ACTION_KEY, RINGING_NO_ACTION);
+    }
+
+    private ITelephony getTeleService() {
+        TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        try {
+            // Java reflection to gain access to TelephonyManager's
+            // ITelephony getter
+            Class c = Class.forName(tm.getClass().getName());
+            Method m = c.getDeclaredMethod("getITelephony");
+            m.setAccessible(true);
+            return (ITelephony) m.invoke(tm);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, "- Could not connect to telephony subsystem");
+            return null;
+        }
+    }
+
     Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             switch (msg.what) {
diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java
index a3ea80d..cedda0b 100644
--- a/src/com/android/incallui/InCallPresenter.java
+++ b/src/com/android/incallui/InCallPresenter.java
@@ -58,6 +58,7 @@
     private CallList mCallList;
     private InCallActivity mInCallActivity;
     private InCallState mInCallState = InCallState.NO_CALLS;
+    private AccelerometerListener mAccelerometerListener;
     private ProximitySensor mProximitySensor;
     private boolean mServiceConnected = false;
     private boolean mCallUiInBackground = false;
@@ -108,6 +109,8 @@
         mProximitySensor = new ProximitySensor(context, mAudioModeProvider);
         addListener(mProximitySensor);
 
+        mAccelerometerListener = new AccelerometerListener(context);
+
         mCallList = callList;
 
         // This only gets called by the service so this is okay.
@@ -235,6 +238,9 @@
         // Renable notification shade and soft navigation buttons, if we are no longer in the
         // incoming call screen
         if (!newState.isIncoming()) {
+            if (mAccelerometerListener != null) {
+                mAccelerometerListener.enableSensor(false);
+            }
             CallCommandClient.getInstance().setSystemBarNavigationEnabled(true);
         }
 
@@ -271,6 +277,9 @@
         // on new incoming call as long it is no background call
         if (newState.isIncoming() && !mCallUiInBackground) {
             CallCommandClient.getInstance().setSystemBarNavigationEnabled(false);
+            if (mAccelerometerListener != null) {
+                mAccelerometerListener.enableSensor(true);
+            }
         }
 
         for (IncomingCallListener listener : mIncomingCallListeners) {
@@ -456,6 +465,9 @@
         // (1) Attempt to answer a call
         if (incomingCall != null) {
             CallCommandClient.getInstance().answerCall(incomingCall.getCallId());
+            if (mAccelerometerListener != null) {
+                mAccelerometerListener.enableSensor(false);
+            }
             return true;
         }
 
@@ -694,6 +706,8 @@
             }
             mProximitySensor = null;
 
+            mAccelerometerListener = null;
+
             mAudioModeProvider = null;
 
             if (mStatusBarNotifier != null) {