Handle MSIM behavior for VM notifications and CFI.

Implement correct behavior for voicemail notifications, also known
as message waiting indicator (MWI) and call forwarding (CFI).

- Don't instantiate CallNotifier with a Phone object.
+ Change updateMwi and updateCfi to take the subscription id as a
parameter so the notification can be shown on a per-SIM basis.
+ Change phone state listener to be subscription-specific.
+ Register/unregister phone state listeners on subscription changes.
+ Update Cfi according to subscription-specific changes.
+ Use subscription ids as a tag on the voicemail/call forwarding
notifications so they can be shown/canceled with greater precision.

Tested:
+ Voicemail ringtone on Sprout for different SIMs.
+ Voicemail vibration on Sprout for different SIMs.
+ Call forwarding notifications on Sprout for different SIMs.
+ Remove/place SIM on Shamu to see notifications disappear/reappear.

TBD:
+ Voicemail notifications just dial the voicemail schema right now.
Need to ascertain whether we can dial a specific number, and what
happens if the number is the same for different SIMs.

Bug: 18232725
Change-Id: Ie15c3d640e8da217fa8778b2d9d904d76bf0c586
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 01a2449..6f9c9dd 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -49,10 +49,18 @@
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Log;
 
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Phone app module that listens for phone state changes and various other
  * events from the telephony layer, and triggers any resulting UI behavior
@@ -85,6 +93,8 @@
 
     // object used to synchronize access to mCallerInfoQueryState
     private Object mCallerInfoQueryStateGuard = new Object();
+    private Map<Integer, CallNotifierPhoneStateListener> mPhoneStateListeners =
+            new ArrayMap<Integer, CallNotifierPhoneStateListener>();
 
     private PhoneGlobals mApplication;
     private CallManager mCM;
@@ -103,20 +113,22 @@
 
     // Cached AudioManager
     private AudioManager mAudioManager;
-
     private final BluetoothManager mBluetoothManager;
+    private SubscriptionManager mSubscriptionManager;
+    private TelephonyManager mTelephonyManager;
 
     /**
      * Initialize the singleton CallNotifier instance.
      * This is only done once, at startup, from PhoneApp.onCreate().
      */
-    /* package */ static CallNotifier init(PhoneGlobals app, Phone phone,
-            CallLogger callLogger, CallStateMonitor callStateMonitor,
+    /* package */ static CallNotifier init(
+            PhoneGlobals app,
+            CallLogger callLogger,
+            CallStateMonitor callStateMonitor,
             BluetoothManager bluetoothManager) {
         synchronized (CallNotifier.class) {
             if (sInstance == null) {
-                sInstance = new CallNotifier(app, phone, callLogger, callStateMonitor,
-                        bluetoothManager);
+                sInstance = new CallNotifier(app, callLogger, callStateMonitor, bluetoothManager);
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
@@ -125,29 +137,38 @@
     }
 
     /** Private constructor; @see init() */
-    private CallNotifier(PhoneGlobals app, Phone phone, CallLogger callLogger,
-            CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager) {
+    private CallNotifier(
+            PhoneGlobals app,
+            CallLogger callLogger,
+            CallStateMonitor callStateMonitor,
+            BluetoothManager bluetoothManager) {
         mApplication = app;
         mCM = app.mCM;
         mCallLogger = callLogger;
         mBluetoothManager = bluetoothManager;
 
         mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
+        mTelephonyManager =
+                (TelephonyManager) mApplication.getSystemService(Context.TELEPHONY_SERVICE);
+        mSubscriptionManager = (SubscriptionManager) mApplication.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
 
         callStateMonitor.addListener(this);
 
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter != null) {
             adapter.getProfileProxy(mApplication.getApplicationContext(),
-                                    mBluetoothProfileServiceListener,
-                                    BluetoothProfile.HEADSET);
+                    mBluetoothProfileServiceListener,
+                    BluetoothProfile.HEADSET);
         }
 
-        TelephonyManager telephonyManager = (TelephonyManager) app.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        telephonyManager.listen(mPhoneStateListener,
-                PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
-                | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
+        mSubscriptionManager.registerOnSubscriptionsChangedListener(
+                new OnSubscriptionsChangedListener() {
+                    @Override
+                    public void onSubscriptionsChanged() {
+                        updatePhoneStateListeners();
+                    }
+                });
     }
 
     private void createSignalInfoToneGenerator() {
@@ -239,20 +260,6 @@
         }
     }
 
-    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onMessageWaitingIndicatorChanged(boolean visible) {
-            if (VDBG) log("onMessageWaitingIndicatorChanged(): " + visible);
-            mApplication.notificationMgr.updateMwi(visible);
-        }
-
-        @Override
-        public void onCallForwardingIndicatorChanged(boolean visible) {
-            if (VDBG) log("onCallForwardingIndicatorChanged(): " + visible);
-            mApplication.notificationMgr.updateCfi(visible);
-        }
-    };
-
     /**
      * Handles a "new ringing connection" event from the telephony layer.
      */
@@ -913,6 +920,58 @@
                 SHOW_MESSAGE_NOTIFICATION_TIME);
     }
 
+    public void updatePhoneStateListeners() {
+        List<SubscriptionInfo> subInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
+
+        // Unregister phone listeners for inactive subscriptions.
+        Iterator<Integer> itr = mPhoneStateListeners.keySet().iterator();
+        while (itr.hasNext()) {
+            int subId = itr.next();
+            if (subInfos == null || !containsSubId(subInfos, subId)) {
+                // Hide the outstanding notifications.
+                mApplication.notificationMgr.updateMwi(subId, false);
+                mApplication.notificationMgr.updateCfi(subId, false);
+
+                // Listening to LISTEN_NONE removes the listener.
+                mTelephonyManager.listen(
+                        mPhoneStateListeners.get(subId), PhoneStateListener.LISTEN_NONE);
+                itr.remove();
+            }
+        }
+
+        if (subInfos == null) {
+            return;
+        }
+
+        // Register new phone listeners for active subscriptions.
+        for (int i = 0; i < subInfos.size(); i++) {
+            int subId = subInfos.get(i).getSubscriptionId();
+            if (!mPhoneStateListeners.containsKey(subId)) {
+                CallNotifierPhoneStateListener listener = new CallNotifierPhoneStateListener(subId);
+                mTelephonyManager.listen(listener,
+                        PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
+                        | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
+                mPhoneStateListeners.put(subId, listener);
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} if the list contains SubscriptionInfo with the given subscription id.
+     */
+    private boolean containsSubId(List<SubscriptionInfo> subInfos, int subId) {
+        if (subInfos == null) {
+            return false;
+        }
+
+        for (int i = 0; i < subInfos.size(); i++) {
+            if (subInfos.get(i).getSubscriptionId() == subId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Helper class to play SignalInfo tones using the ToneGenerator.
      *
@@ -1014,14 +1073,32 @@
     }
 
     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
-        new BluetoothProfile.ServiceListener() {
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mBluetoothHeadset = (BluetoothHeadset) proxy;
-            if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
+           new BluetoothProfile.ServiceListener() {
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    mBluetoothHeadset = (BluetoothHeadset) proxy;
+                    if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
+                }
+
+                public void onServiceDisconnected(int profile) {
+                    mBluetoothHeadset = null;
+                }
+            };
+
+    private class CallNotifierPhoneStateListener extends PhoneStateListener {
+        public CallNotifierPhoneStateListener(int subId) {
+            super(subId);
         }
 
-        public void onServiceDisconnected(int profile) {
-            mBluetoothHeadset = null;
+        @Override
+        public void onMessageWaitingIndicatorChanged(boolean visible) {
+            if (VDBG) log("onMessageWaitingIndicatorChanged(): " + this.mSubId + " " + visible);
+            mApplication.notificationMgr.updateMwi(this.mSubId, visible);
+        }
+
+        @Override
+        public void onCallForwardingIndicatorChanged(boolean visible) {
+            if (VDBG) log("onCallForwardingIndicatorChanged(): " + this.mSubId + " " + visible);
+            mApplication.notificationMgr.updateCfi(this.mSubId, visible);
         }
     };
 
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 6e372c2..90b6595 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -35,8 +35,8 @@
 import android.telecom.PhoneAccount;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
@@ -83,6 +83,7 @@
     private StatusBarManager mStatusBarManager;
     private UserManager mUserManager;
     private Toast mToast;
+    private SubscriptionManager mSubscriptionManager;
 
     public StatusBarHelper statusBarHelper;
 
@@ -103,15 +104,7 @@
         mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
         mPhone = app.phone;  // TODO: better style to use mCM.getDefaultPhone() everywhere instead
         statusBarHelper = new StatusBarHelper();
-
-        SubscriptionManager.from(mContext).registerOnSubscriptionsChangedListener(
-                new OnSubscriptionsChangedListener() {
-                    @Override
-                    public void onSubscriptionsChanged() {
-                        // Update the message waiting indicator if the SIM is changed.
-                        updateMwi(mPhone.getMessageWaitingIndicator());
-                    }
-                });
+        mSubscriptionManager = SubscriptionManager.from(mContext);
     }
 
     /**
@@ -240,8 +233,8 @@
      *
      * @param visible true if there are messages waiting
      */
-    /* package */ void updateMwi(boolean visible) {
-        if (DBG) log("updateMwi(): " + visible);
+    /* package */ void updateMwi(int subId, boolean visible) {
+        if (DBG) log("updateMwi(): subId " + subId + " update to " + visible);
 
         if (!PhoneGlobals.sVoiceCapable) {
             // Do not show the message waiting indicator on devices which are not voice capable.
@@ -251,6 +244,12 @@
         }
 
         if (visible) {
+            Phone phone = PhoneGlobals.getPhone(subId);
+            if (phone == null) {
+                Log.w(LOG_TAG, "Null phone returned for " + subId);
+                return;
+            }
+
             int resId = android.R.drawable.stat_notify_voicemail;
 
             // This Notification can get a lot fancier once we have more
@@ -264,7 +263,7 @@
             // notification.
 
             String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
-            String vmNumber = mPhone.getVoiceMailNumber();
+            String vmNumber = phone.getVoiceMailNumber();
             if (DBG) log("- got vm number: '" + vmNumber + "'");
 
             // The voicemail number may be null because:
@@ -272,15 +271,14 @@
             //   (2) This phone has a voicemail number, but the SIM isn't ready yet. This may
             //       happen when the device first boots if we get a MWI notification when we
             //       register on the network before the SIM has loaded. In this case, the
-            //       SubscriptionListener this class registers on the SubscriptionManager will
-            //       call this method again once the SIM is loaded.
-            if ((vmNumber == null) && !mPhone.getIccRecordsLoaded()) {
+            //       SubscriptionListener in CallNotifier will update this once the SIM is loaded.
+            if ((vmNumber == null) && !phone.getIccRecordsLoaded()) {
                 if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
                 return;
             }
 
-            if (TelephonyCapabilities.supportsVoiceMessageCount(mPhone)) {
-                int vmCount = mPhone.getVoiceMessageCount();
+            if (TelephonyCapabilities.supportsVoiceMessageCount(phone)) {
+                int vmCount = phone.getVoiceMessageCount();
                 String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
                 notificationTitle = String.format(titleFormat, vmCount);
             }
@@ -297,8 +295,9 @@
 
             Intent intent = new Intent(Intent.ACTION_CALL,
                     Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null));
-            PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
-            Uri ringtoneUri = VoicemailNotificationSettingsUtil.getRingtoneUri(mPhone);
+            PendingIntent pendingIntent =
+                    PendingIntent.getActivity(mContext, subId /* requestCode */, intent, 0);
+            Uri ringtoneUri = VoicemailNotificationSettingsUtil.getRingtoneUri(phone);
 
             Notification.Builder builder = new Notification.Builder(mContext);
             builder.setSmallIcon(resId)
@@ -310,7 +309,7 @@
                     .setColor(mContext.getResources().getColor(R.color.dialer_theme_color))
                     .setOngoing(true);
 
-            if (VoicemailNotificationSettingsUtil.isVibrationEnabled(mPhone)) {
+            if (VoicemailNotificationSettingsUtil.isVibrationEnabled(phone)) {
                 builder.setDefaults(Notification.DEFAULT_VIBRATE);
             }
 
@@ -323,12 +322,17 @@
                         UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
                             && !user.isManagedProfile()) {
                     mNotificationManager.notifyAsUser(
-                            null /* tag */, VOICEMAIL_NOTIFICATION, notification, userHandle);
+                            Integer.toString(subId) /* tag */,
+                            VOICEMAIL_NOTIFICATION,
+                            notification,
+                            userHandle);
                 }
             }
         } else {
             mNotificationManager.cancelAsUser(
-                    null /* tag */, VOICEMAIL_NOTIFICATION, UserHandle.ALL);
+                    Integer.toString(subId) /* tag */,
+                    VOICEMAIL_NOTIFICATION,
+                    UserHandle.ALL);
         }
     }
 
@@ -337,7 +341,7 @@
      *
      * @param visible true if there are messages waiting
      */
-    /* package */ void updateCfi(boolean visible) {
+    /* package */ void updateCfi(int subId, boolean visible) {
         if (DBG) log("updateCfi(): " + visible);
         if (visible) {
             // If Unconditional Call Forwarding (forward all calls) for VOICE
@@ -359,9 +363,12 @@
                     .setOngoing(true);
 
             Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
             intent.setClassName("com.android.phone", "com.android.phone.CallFeaturesSetting");
-            PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+            SubscriptionInfoHelper.addExtrasToIntent(
+                    intent, mSubscriptionManager.getActiveSubscriptionInfo(subId));
+            PendingIntent contentIntent =
+                    PendingIntent.getActivity(mContext, subId /* requestCode */, intent, 0);
 
             List<UserInfo> users = mUserManager.getUsers(true);
             for (int i = 0; i < users.size(); i++) {
@@ -371,12 +378,17 @@
                 }
                 UserHandle userHandle = user.getUserHandle();
                 builder.setContentIntent(userHandle.isOwner() ? contentIntent : null);
-                    mNotificationManager.notifyAsUser(
-                            null /* tag */, CALL_FORWARD_NOTIFICATION, builder.build(), userHandle);
+                mNotificationManager.notifyAsUser(
+                        Integer.toString(subId) /* tag */,
+                        CALL_FORWARD_NOTIFICATION,
+                        builder.build(),
+                        userHandle);
             }
         } else {
             mNotificationManager.cancelAsUser(
-                    null /* tag */, CALL_FORWARD_NOTIFICATION, UserHandle.ALL);
+                    Integer.toString(subId) /* tag */,
+                    CALL_FORWARD_NOTIFICATION,
+                    UserHandle.ALL);
         }
     }
 
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index fe40b17..03827ce 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -48,6 +48,7 @@
 import android.preference.PreferenceManager;
 import android.provider.Settings.System;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
 
@@ -402,8 +403,7 @@
             // asynchronous events from the telephony layer (like
             // launching the incoming-call UI when an incoming call comes
             // in.)
-            notifier = CallNotifier.init(this, phone, callLogger, callStateMonitor,
-                    bluetoothManager);
+            notifier = CallNotifier.init(this, callLogger, callStateMonitor, bluetoothManager);
 
             // register for ICC status
             IccCard sim = phone.getIccCard();
@@ -492,17 +492,8 @@
         return getInstance().phone;
     }
 
-    /**
-     * Returns a list of the currently active phones for the Telephony package.
-     */
-    public static List<Phone> getPhones() {
-        int[] subIds = SubscriptionController.getInstance().getActiveSubIdList();
-        List<Phone> phones = new ArrayList<Phone>(subIds.length);
-
-        for (int i = 0; i < subIds.length; i++) {
-            phones.add(PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subIds[i])));
-        }
-        return phones;
+    public static Phone getPhone(int subId) {
+        return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
     }
 
     /* package */ BluetoothManager getBluetoothManager() {
diff --git a/src/com/android/phone/SubscriptionInfoHelper.java b/src/com/android/phone/SubscriptionInfoHelper.java
index 1255452..347d00e 100644
--- a/src/com/android/phone/SubscriptionInfoHelper.java
+++ b/src/com/android/phone/SubscriptionInfoHelper.java
@@ -76,6 +76,10 @@
     }
 
     public static void addExtrasToIntent(Intent intent, SubscriptionInfo subscription) {
+        if (subscription == null) {
+            return;
+        }
+
         intent.putExtra(SubscriptionInfoHelper.SUB_ID_EXTRA, subscription.getSubscriptionId());
         intent.putExtra(
                 SubscriptionInfoHelper.SUB_LABEL_EXTRA, subscription.getDisplayName().toString());
diff --git a/src/com/android/phone/settings/VoicemailRingtonePreference.java b/src/com/android/phone/settings/VoicemailRingtonePreference.java
index fa3cc70..3dbb99f 100644
--- a/src/com/android/phone/settings/VoicemailRingtonePreference.java
+++ b/src/com/android/phone/settings/VoicemailRingtonePreference.java
@@ -39,6 +39,10 @@
                 }
             }
         };
+    }
+
+    public void init(Phone phone) {
+        mPhone = phone;
 
         final Preference preference = this;
         final String preferenceKey =
@@ -58,10 +62,6 @@
         updateRingtoneName();
     }
 
-    public void init(Phone phone) {
-        mPhone = phone;
-    }
-
     @Override
     protected Uri onRestoreRingtone() {
         return VoicemailNotificationSettingsUtil.getRingtoneUri(mPhone);