Dialer: Add back in-call vibration features

Fix call tracking.
getActiveCall() can return null when ending calls locally before they
are answered locally, so better use the presence of an active call as
indicator of whether to do the vibration instead of using the state.

Change-Id: I088cd743c9a9e6704e93f8c48fb566b3a20ca8be
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index feaf5ee..25826f7 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -385,6 +385,7 @@
     private int mState = State.INVALID;
     private DisconnectCause mDisconnectCause;
     private int mSessionModificationState;
+    private boolean mIsOutgoing = false;
     private final List<String> mChildCallIds = new ArrayList<>();
     private final VideoSettings mVideoSettings = new VideoSettings();
     private int mVideoState;
@@ -686,6 +687,9 @@
 
     public void setState(int state) {
         mState = state;
+        if (state == State.DIALING || state == State.CONNECTING) {
+            mIsOutgoing = true;
+        }
         if (mState == State.INCOMING) {
             mLogState.isIncoming = true;
         } else if (mState == State.DISCONNECTED) {
@@ -694,6 +698,10 @@
         }
     }
 
+    public boolean isOutgoing() {
+        return mIsOutgoing;
+    }
+
     public int getNumberPresentation() {
         return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandlePresentation();
     }
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 1e0a8f7..1bc3b4f 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -114,6 +114,7 @@
     private AudioModeProvider mAudioModeProvider;
     private StatusBarNotifier mStatusBarNotifier;
     private ExternalCallNotifier mExternalCallNotifier;
+    private InCallVibrationHandler mInCallVibrationHandler;
     private ContactInfoCache mContactInfoCache;
     private Context mContext;
     private CallList mCallList;
@@ -327,6 +328,9 @@
 
         mContactInfoCache = contactInfoCache;
 
+        mInCallVibrationHandler = new InCallVibrationHandler(context);
+        addListener(mInCallVibrationHandler);
+
         mStatusBarNotifier = statusBarNotifier;
         mExternalCallNotifier = externalCallNotifier;
         addListener(mStatusBarNotifier);
@@ -1705,6 +1709,11 @@
             mStatusBarNotifier = null;
 
             InCallCsRedialHandler.getInstance().tearDown();
+ 
+            if (mInCallVibrationHandler != null) {
+                removeListener(mInCallVibrationHandler);
+            }
+            mInCallVibrationHandler = null;
 
             if (mCallList != null) {
                 mCallList.removeListener(this);
diff --git a/InCallUI/src/com/android/incallui/InCallVibrationHandler.java b/InCallUI/src/com/android/incallui/InCallVibrationHandler.java
new file mode 100644
index 0000000..42845df
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallVibrationHandler.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.incallui;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.telecom.DisconnectCause;
+
+import com.android.incallui.InCallPresenter.InCallState;
+
+public class InCallVibrationHandler extends Handler implements
+        InCallPresenter.InCallStateListener {
+
+    private static final int MSG_VIBRATE_45_SEC = 1;
+
+    private static final String KEY_VIBRATE_OUTGOING = "incall_vibrate_outgoing";
+    private static final String KEY_VIBRATE_45SECS = "incall_vibrate_45secs";
+    private static final String KEY_VIBRATE_HANGUP = "incall_vibrate_hangup";
+
+    private SharedPreferences mPrefs;
+    private Vibrator mVibrator;
+    private Call mActiveCall;
+
+    public InCallVibrationHandler(Context context) {
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_VIBRATE_45_SEC:
+                vibrate(70, 0, 0);
+                sendEmptyMessageDelayed(MSG_VIBRATE_45_SEC, 60000);
+                break;
+        }
+    }
+
+    @Override
+    public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+        Call activeCall = callList.getActiveCall();
+
+        if (activeCall != null && mActiveCall == null) {
+            Log.d(this, "Transition to active call " + activeCall);
+            if (activeCall.isOutgoing()) {
+                handleOutgoingCallVibration(activeCall);
+            }
+            mActiveCall = activeCall;
+        } else if (activeCall == null && mActiveCall != null) {
+            Log.d(this, "Transition from active call " + mActiveCall);
+            handleCallEnd(mActiveCall);
+            mActiveCall = null;
+        }
+    }
+
+    private void handleOutgoingCallVibration(Call call) {
+        long durationMillis = System.currentTimeMillis() - call.getConnectTimeMillis();
+        Log.d(this, "Start outgoing call: duration = " + durationMillis);
+
+        if (mPrefs.getBoolean(KEY_VIBRATE_OUTGOING, false) && durationMillis < 200) {
+            vibrate(100, 200, 0);
+        }
+        if (mPrefs.getBoolean(KEY_VIBRATE_45SECS, false)) {
+            start45SecondVibration(durationMillis);
+        }
+    }
+
+    private void handleCallEnd(Call call) {
+        long durationMillis = System.currentTimeMillis() - call.getConnectTimeMillis();
+        DisconnectCause cause = call.getDisconnectCause();
+        boolean localDisconnect =
+                // Disconnection not yet processed
+                call.getState() == Call.State.DISCONNECTING ||
+                // Disconnection already processed
+                (cause != null && cause.getCode() == DisconnectCause.LOCAL);
+
+        Log.d(this, "Ending active call: duration = " + durationMillis
+                + ", locally disconnected = " + localDisconnect);
+
+        if (mPrefs.getBoolean(KEY_VIBRATE_HANGUP, false) &&
+                !localDisconnect && durationMillis > 500) {
+            vibrate(50, 100, 50);
+        }
+        // Stop 45-second vibration
+        removeMessages(MSG_VIBRATE_45_SEC);
+    }
+
+    private void start45SecondVibration(long callDurationMillis) {
+        callDurationMillis = callDurationMillis % 60000;
+        Log.d(this, "vibrate start @" + callDurationMillis);
+        removeMessages(MSG_VIBRATE_45_SEC);
+
+        long timer;
+        if (callDurationMillis > 45000) {
+            // Schedule the alarm at the next minute + 45 secs
+            timer = 45000 + 60000 - callDurationMillis;
+        } else {
+            // Schedule the alarm at the first 45 second mark
+            timer = 45000 - callDurationMillis;
+        }
+        sendEmptyMessageDelayed(MSG_VIBRATE_45_SEC, timer);
+    }
+
+    private void vibrate(int v1, int p1, int v2) {
+        long[] pattern = new long[] {
+            0, v1, p1, v2
+        };
+        mVibrator.vibrate(pattern, -1);
+    }
+}
diff --git a/res/values/custom_strings.xml b/res/values/custom_strings.xml
new file mode 100644
index 0000000..614f018
--- /dev/null
+++ b/res/values/custom_strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013-2016 The CyanogenMod 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.
+
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- In-call vibrate options -->
+    <string name="incall_vibration_category_title">In-call vibration</string>
+    <string name="incall_vibrate_outgoing_title">Vibrate on answer</string>
+    <string name="incall_vibrate_hangup_title">Vibrate on hang up</string>
+    <string name="incall_vibrate_45_title">Vibrate every minute</string>
+    <string name="incall_vibrate_45_summary">Vibrates at 45s of every minute during outgoing calls</string>
+
+</resources>
diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml
index 80fad62..a1170ad 100644
--- a/res/xml/sound_settings.xml
+++ b/res/xml/sound_settings.xml
@@ -25,13 +25,13 @@
         android:persistent="false"
         android:ringtoneType="ringtone" />
 
-    <CheckBoxPreference
+    <SwitchPreference
         android:key="@string/vibrate_on_preference_key"
         android:title="@string/vibrate_on_ring_title"
         android:persistent="false"
         android:defaultValue="false" />
 
-    <CheckBoxPreference
+    <SwitchPreference
         android:key="@string/play_dtmf_preference_key"
         android:title="@string/dtmf_tone_enable_title"
         android:persistent="false"
@@ -43,4 +43,23 @@
         android:entries="@array/dtmf_tone_length_entries"
         android:entryValues="@array/dtmf_tone_length_entry_values" />
 
+    <PreferenceCategory
+        android:key="dialer_general_incall_vibration_category_key"
+        android:title="@string/incall_vibration_category_title">
+
+        <SwitchPreference
+            android:key="incall_vibrate_outgoing"
+            android:title="@string/incall_vibrate_outgoing_title" />
+
+        <SwitchPreference
+            android:key="incall_vibrate_hangup"
+            android:title="@string/incall_vibrate_hangup_title" />
+
+        <SwitchPreference
+            android:key="incall_vibrate_45secs"
+            android:title="@string/incall_vibrate_45_title"
+            android:summary="@string/incall_vibrate_45_summary" />
+
+    </PreferenceCategory>
+
 </PreferenceScreen>
diff --git a/src/com/android/dialer/settings/SoundSettingsFragment.java b/src/com/android/dialer/settings/SoundSettingsFragment.java
index 3736512..cf9f68f 100644
--- a/src/com/android/dialer/settings/SoundSettingsFragment.java
+++ b/src/com/android/dialer/settings/SoundSettingsFragment.java
@@ -23,9 +23,10 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Vibrator;
-import android.preference.CheckBoxPreference;
+import android.preference.SwitchPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
+import android.preference.PreferenceCategory;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
@@ -57,9 +58,12 @@
 
     private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
 
+    private static final String CATEGORY_INCALL_VIBRATION_KEY =
+            "dialer_general_incall_vibration_category_key";
+
     private Preference mRingtonePreference;
-    private CheckBoxPreference mVibrateWhenRinging;
-    private CheckBoxPreference mPlayDtmfTone;
+    private SwitchPreference mVibrateWhenRinging;
+    private SwitchPreference mPlayDtmfTone;
     private ListPreference mDtmfToneLength;
 
     private final Runnable mRingtoneLookupRunnable = new Runnable() {
@@ -94,9 +98,9 @@
         Context context = getActivity();
 
         mRingtonePreference = findPreference(context.getString(R.string.ringtone_preference_key));
-        mVibrateWhenRinging = (CheckBoxPreference) findPreference(
+        mVibrateWhenRinging = (SwitchPreference) findPreference(
                 context.getString(R.string.vibrate_on_preference_key));
-        mPlayDtmfTone = (CheckBoxPreference) findPreference(
+        mPlayDtmfTone = (SwitchPreference) findPreference(
                 context.getString(R.string.play_dtmf_preference_key));
         mDtmfToneLength = (ListPreference) findPreference(
                 context.getString(R.string.dtmf_tone_length_preference_key));
@@ -108,6 +112,10 @@
             mVibrateWhenRinging = null;
         }
 
+        if (!hasVibrator()) {
+            getPreferenceScreen().removePreference(findPreference(CATEGORY_INCALL_VIBRATION_KEY));
+        }
+
         mPlayDtmfTone.setOnPreferenceChangeListener(this);
         mPlayDtmfTone.setChecked(shouldPlayDtmfTone());