Merge "Fix String alignment issue while displaying MCC/MNC pair." into mnc-dev
diff --git a/res/xml/gsm_umts_options.xml b/res/xml/gsm_umts_options.xml
index 3f85ea8..86fe41d 100644
--- a/res/xml/gsm_umts_options.xml
+++ b/res/xml/gsm_umts_options.xml
@@ -31,11 +31,6 @@
         android:title="@string/networks"
         android:summary="@string/sum_carrier_select"
         android:persistent="false">
-
-        <intent android:action="android.intent.action.MAIN"
-            android:targetPackage="com.android.phone"
-            android:targetClass="com.android.phone.NetworkSetting" />
-
     </PreferenceScreen>
 
     <PreferenceScreen
diff --git a/src/com/android/phone/GsmUmtsOptions.java b/src/com/android/phone/GsmUmtsOptions.java
index d12bac9..2e9d88a 100644
--- a/src/com/android/phone/GsmUmtsOptions.java
+++ b/src/com/android/phone/GsmUmtsOptions.java
@@ -25,6 +25,7 @@
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
+import android.content.ComponentName;
 
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
@@ -41,6 +42,7 @@
     private static final String BUTTON_APN_EXPAND_KEY = "button_apn_key";
     private static final String BUTTON_OPERATOR_SELECTION_EXPAND_KEY = "button_carrier_sel_key";
     private static final String BUTTON_CARRIER_SETTINGS_KEY = "carrier_settings_key";
+    public static final String EXTRA_SUB_ID = "sub_id";
     private PreferenceActivity mPrefActivity;
     private PreferenceScreen mPrefScreen;
     private int mSubId;
@@ -117,7 +119,21 @@
                             final Intent intent = new Intent(Settings.ACTION_APN_SETTINGS);
                             // This will setup the Home and Search affordance
                             intent.putExtra(":settings:show_fragment_as_subsetting", true);
-                            intent.putExtra("sub_id", mSubId);
+                            intent.putExtra(EXTRA_SUB_ID, mSubId);
+                            mPrefActivity.startActivity(intent);
+                            return true;
+                        }
+            });
+        }
+        if (mPrefScreen.findPreference(BUTTON_OPERATOR_SELECTION_EXPAND_KEY) != null) {
+            mButtonOperatorSelectionExpand.setOnPreferenceClickListener(
+                    new Preference.OnPreferenceClickListener() {
+                        @Override
+                        public boolean onPreferenceClick(Preference preference) {
+                            final Intent intent = new Intent(Intent.ACTION_MAIN);
+                            intent.setComponent(new ComponentName("com.android.phone",
+                                    "com.android.phone.NetworkSetting"));
+                            intent.putExtra(EXTRA_SUB_ID, mSubId);
                             mPrefActivity.startActivity(intent);
                             return true;
                         }
diff --git a/src/com/android/phone/INetworkQueryService.aidl b/src/com/android/phone/INetworkQueryService.aidl
index 0733d73..81eb8e6 100644
--- a/src/com/android/phone/INetworkQueryService.aidl
+++ b/src/com/android/phone/INetworkQueryService.aidl
@@ -33,7 +33,7 @@
      * then just add the callback to the list of notifications
      * that will be sent upon query completion.
      */
-    void startNetworkQuery(in INetworkQueryServiceCallback cb);
+    void startNetworkQuery(in INetworkQueryServiceCallback cb, in int phoneId);
 
     /**
      * Tells the service that the requested query is to be ignored.
diff --git a/src/com/android/phone/NetworkQueryService.java b/src/com/android/phone/NetworkQueryService.java
index b38b110..1a497b4 100644
--- a/src/com/android/phone/NetworkQueryService.java
+++ b/src/com/android/phone/NetworkQueryService.java
@@ -57,9 +57,6 @@
     /** state of the query service */
     private int mState;
     
-    /** local handle to the phone object */
-    private Phone mPhone;
-    
     /**
      * Class for clients to access.  Because we know this service always
      * runs in the same process as its clients, we don't need to deal with
@@ -109,7 +106,7 @@
          * callback object in the queue to be notified upon request 
          * completion.
          */
-        public void startNetworkQuery(INetworkQueryServiceCallback cb) {
+        public void startNetworkQuery(INetworkQueryServiceCallback cb, int phoneId) {
             if (cb != null) {
                 // register the callback to the list of callbacks.
                 synchronized (mCallbacks) {
@@ -120,10 +117,17 @@
                         case QUERY_READY:
                             // TODO: we may want to install a timeout here in case we
                             // do not get a timely response from the RIL.
-                            mPhone.getAvailableNetworks(
-                                    mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
-                            mState = QUERY_IS_RUNNING;
-                            if (DBG) log("starting new query");
+                            Phone phone = PhoneFactory.getPhone(phoneId);
+                            if (phone != null) {
+                                phone.getAvailableNetworks(
+                                        mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
+                                mState = QUERY_IS_RUNNING;
+                                if (DBG) log("starting new query");
+                            } else {
+                                if (DBG) {
+                                    log("phone is null");
+                                }
+                            }
                             break;
                             
                         // do nothing if we're currently busy.
@@ -166,8 +170,6 @@
     @Override
     public void onCreate() {
         mState = QUERY_READY;        
-        mPhone = PhoneFactory.getPhone(
-                SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubId()));
     }
 
     /**
diff --git a/src/com/android/phone/NetworkSetting.java b/src/com/android/phone/NetworkSetting.java
index d386f3c..ad2fea3 100644
--- a/src/com/android/phone/NetworkSetting.java
+++ b/src/com/android/phone/NetworkSetting.java
@@ -36,9 +36,11 @@
 import android.preference.PreferenceScreen;
 import android.text.TextUtils;
 import android.util.Log;
+import android.telephony.SubscriptionManager;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.OperatorInfo;
 
 import java.util.HashMap;
@@ -72,7 +74,7 @@
     //map of network controls to the network data.
     private HashMap<Preference, OperatorInfo> mNetworkMap;
 
-    Phone mPhone;
+    int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
     protected boolean mIsForeground = false;
 
     private UserManager mUm;
@@ -109,9 +111,6 @@
                         displayNetworkSelectionSucceeded();
                     }
 
-                    // update the phone in case replaced as part of selection
-                    mPhone = PhoneGlobals.getPhone();
-
                     break;
                 case EVENT_AUTO_SELECT_DONE:
                     if (DBG) log("hideProgressPanel");
@@ -137,9 +136,6 @@
                         displayNetworkSelectionSucceeded();
                     }
 
-                    // update the phone in case replaced as part of selection
-                    mPhone = PhoneGlobals.getPhone();
-
                     break;
             }
 
@@ -206,11 +202,16 @@
             if (DBG) log("selected network: " + networkStr);
 
             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
-            mPhone.selectNetworkManually(mNetworkMap.get(selectedCarrier), msg);
+            Phone phone = PhoneFactory.getPhone(mPhoneId);
+            if (phone != null) {
+                phone.selectNetworkManually(mNetworkMap.get(selectedCarrier), msg);
+                displayNetworkSeletionInProgress(networkStr);
+                handled = true;
+            } else {
+                log("Error selecting network. phone is null.");
+            }
 
-            displayNetworkSeletionInProgress(networkStr);
 
-            handled = true;
         }
 
         return handled;
@@ -248,7 +249,14 @@
 
         addPreferencesFromResource(R.xml.carrier_select);
 
-        mPhone = PhoneGlobals.getPhone();
+        int subId;
+        Intent intent = getIntent();
+        if (intent != null && intent.getExtras() != null) {
+            subId = intent.getExtras().getInt(GsmUmtsOptions.EXTRA_SUB_ID);
+            if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                mPhoneId = SubscriptionManager.getPhoneId(subId);
+            }
+        }
 
         mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY);
         mNetworkMap = new HashMap<Preference, OperatorInfo>();
@@ -404,7 +412,7 @@
 
         // delegate query request to the service.
         try {
-            mNetworkQueryService.startNetworkQuery(mCallback);
+            mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId);
         } catch (RemoteException e) {
             log("loadNetworksList: exception from startNetworkQuery " + e);
             if (mIsForeground) {
@@ -513,7 +521,10 @@
         }
 
         Message msg = mHandler.obtainMessage(EVENT_AUTO_SELECT_DONE);
-        mPhone.setNetworkSelectionModeAutomatic(msg);
+        Phone phone = PhoneFactory.getPhone(mPhoneId);
+        if (phone != null) {
+            phone.setNetworkSelectionModeAutomatic(msg);
+        }
     }
 
     private void log(String msg) {
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 885b797..c579fe0 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -573,6 +573,7 @@
         // Use NetworkSetting to handle the selection intent
         intent.setComponent(new ComponentName("com.android.phone",
                 "com.android.phone.NetworkSetting"));
+        intent.putExtra(GsmUmtsOptions.EXTRA_SUB_ID, mPhone.getSubId());
         PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
 
         List<UserInfo> users = mUserManager.getUsers(true);
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 1faf0ee..315929b 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -344,12 +344,8 @@
             startService(intent);
 
             mCM = CallManager.getInstance();
-            boolean hasCdmaPhoneType = false;
             for (Phone phone : PhoneFactory.getPhones()) {
                 mCM.registerPhone(phone);
-                if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
-                    hasCdmaPhoneType = true;
-                }
             }
 
             // Create the NotificationMgr singleton, which is used to display
@@ -358,11 +354,9 @@
 
             mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE);
 
-            if (hasCdmaPhoneType) {
-                // Create an instance of CdmaPhoneCallState and initialize it to IDLE
-                cdmaPhoneCallState = new CdmaPhoneCallState();
-                cdmaPhoneCallState.CdmaPhoneCallStateInit();
-            }
+            // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+            cdmaPhoneCallState = new CdmaPhoneCallState();
+            cdmaPhoneCallState.CdmaPhoneCallStateInit();
 
             // before registering for phone state changes
             mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
@@ -962,6 +956,15 @@
     }
 
     /**
+     * Dismisses the message waiting (voicemail) indicator.
+     *
+     * @param subId the subscription id we should dismiss the notification for.
+     */
+    public void clearMwiIndicator(int subId) {
+        notificationMgr.updateMwi(subId, false);
+    }
+
+    /**
      * "Call origin" may be used by Contacts app to specify where the phone call comes from.
      * Currently, the only permitted value for this extra is {@link #ALLOWED_EXTRA_CALL_ORIGIN}.
      * Any other value will be ignored, to make sure that malicious apps can't trick the in-call
diff --git a/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java b/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
index 45ad1cb..888a5f9 100644
--- a/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
+++ b/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
@@ -39,6 +39,10 @@
     // If a carrier vvm app is installed, Google visual voicemail is automatically switched off
     // however, the user can override this setting.
     private static final String IS_USER_SET = "is_user_set";
+    // Record the timestamp of the last full sync so that duplicate syncs can be reduced.
+    private static final String LAST_FULL_SYNC_TIMESTAMP = "last_full_sync_timestamp";
+    // Constant indicating that there has never been a full sync.
+    public static final long NO_PRIOR_FULL_SYNC = -1;
 
     // Setting for how often retries should be done.
     private static final String SYNC_RETRY_INTERVAL = "sync_retry_interval";
@@ -142,6 +146,24 @@
         editor.commit();
     }
 
+    public static void setVisualVoicemailLastFullSyncTime(Context context,
+            PhoneAccountHandle phoneAccount, long timestamp) {
+        SharedPreferences.Editor editor =
+                PreferenceManager.getDefaultSharedPreferences(context).edit();
+        editor.putLong(getVisualVoicemailSharedPrefsKey(LAST_FULL_SYNC_TIMESTAMP, phoneAccount),
+                timestamp);
+        editor.commit();
+
+    }
+
+    public static long getVisualVoicemailLastFullSyncTime(Context context,
+            PhoneAccountHandle phoneAccount) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        return prefs.getLong(
+                getVisualVoicemailSharedPrefsKey(LAST_FULL_SYNC_TIMESTAMP, phoneAccount),
+                NO_PRIOR_FULL_SYNC);
+    }
+
     private static String getVisualVoicemailSharedPrefsKey(String key,
             PhoneAccountHandle phoneAccount) {
         return VISUAL_VOICEMAIL_SHARED_PREFS_KEY_PREFIX + key + "_" + phoneAccount.getId();
diff --git a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
index 13f7d6b..1387215 100644
--- a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
@@ -90,8 +90,8 @@
     /** request code when invoking subactivity */
     private static final int CONTACTS_PICKER_CODE = 200;
     /** projection for phone number query */
-    private static final String NUM_PROJECTION[] = {PeopleColumns.DISPLAY_NAME,
-        PhonesColumns.NUMBER};
+    private static final String[] NUM_PROJECTION = new String[] {CommonDataKinds.Phone.DISPLAY_NAME,
+            CommonDataKinds.Phone.NUMBER};
     /** static intent to invoke phone number picker */
     private static final Intent CONTACT_IMPORT_INTENT;
     static {
diff --git a/src/com/android/phone/vvm/omtp/LocalLogHelper.java b/src/com/android/phone/vvm/omtp/LocalLogHelper.java
new file mode 100644
index 0000000..a3de74f
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/LocalLogHelper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source 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.phone.vvm.omtp;
+
+import com.android.internal.telephony.PhoneFactory;
+
+/**
+ * Helper methods for adding to Telephony local logs.
+ */
+public class LocalLogHelper {
+    public static final String KEY = "OmtpVvm";
+    private static final int MAX_OMTP_VVM_LOGS = 20;
+
+    public static void log(String tag, String log) {
+        try {
+            PhoneFactory.addLocalLog(KEY, MAX_OMTP_VVM_LOGS);
+        } catch (IllegalArgumentException e){
+        } finally {
+            PhoneFactory.localLog(KEY, tag + ": " + log);
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 5ffc9ea..9aff2d7 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -18,6 +18,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
@@ -43,6 +44,11 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
+            Log.v(TAG, "Received broadcast for user that is not owner.");
+            return;
+        }
+
         final String action = intent.getAction();
         if (action == null) {
             Log.w(TAG, "Null action for intent.");
@@ -83,6 +89,9 @@
                     }
 
                     if (isEnabled) {
+                        LocalLogHelper.log(TAG, "Sim state or carrier config changed: requesting"
+                                + " activation for " + phoneAccount.getId());
+
                         // Add a phone state listener so that changes to the communication channels
                         // can be recorded.
                         OmtpVvmSourceManager.getInstance(context).addPhoneStateListener(
diff --git a/src/com/android/phone/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
similarity index 67%
rename from src/com/android/phone/VvmPhoneStateListener.java
rename to src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
index d2d84b2..1b2e34e 100644
--- a/src/com/android/phone/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
-package com.android.phone;
+package com.android.phone.vvm.omtp;
 
 import android.content.Context;
 import android.content.Intent;
@@ -21,8 +21,10 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
+import android.util.Log;
 
-import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.PhoneGlobals;
+import com.android.phone.PhoneUtils;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.VoicemailStatusQueryHelper;
@@ -31,9 +33,12 @@
  * Check if service is lost and indicate this in the voicemail status.
  */
 public class VvmPhoneStateListener extends PhoneStateListener {
+    private static final String TAG = "VvmPhoneStateListener";
 
     private PhoneAccountHandle mPhoneAccount;
     private Context mContext;
+    private int mPreviousState = -1;
+
     public VvmPhoneStateListener(Context context, PhoneAccountHandle accountHandle) {
         super(PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle));
         mContext = context;
@@ -42,21 +47,34 @@
 
     @Override
     public void onServiceStateChanged(ServiceState serviceState) {
-        if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
+        int state = serviceState.getState();
+        if (state == mPreviousState || (state != ServiceState.STATE_IN_SERVICE
+                && mPreviousState != ServiceState.STATE_IN_SERVICE)) {
+            // Only interested in state changes or transitioning into or out of "in service".
+            // Otherwise just quit.
+            mPreviousState = state;
+            return;
+        }
+
+        if (state == ServiceState.STATE_IN_SERVICE) {
             VoicemailStatusQueryHelper voicemailStatusQueryHelper =
                     new VoicemailStatusQueryHelper(mContext);
             if (voicemailStatusQueryHelper.isVoicemailSourceConfigured(mPhoneAccount)) {
                 if (!voicemailStatusQueryHelper.isNotificationsChannelActive(mPhoneAccount)) {
+                    Log.v(TAG, "Notifications channel is active for " + mPhoneAccount.getId());
                     VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
                             VoicemailContract.Status.CONFIGURATION_STATE_OK,
                             VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
                             VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
-                    PhoneGlobals.getInstance().notificationMgr.updateMwi(
-                            PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount), false);
+                    PhoneGlobals.getInstance().clearMwiIndicator(
+                            PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount));
                 }
             }
 
             if (OmtpVvmSourceManager.getInstance(mContext).isVvmSourceRegistered(mPhoneAccount)) {
+                Log.v(TAG, "Signal returned: requesting resync for " + mPhoneAccount.getId());
+                LocalLogHelper.log(TAG,
+                        "Signal returned: requesting resync for " + mPhoneAccount.getId());
                 // If the source is already registered, run a full sync in case something was missed
                 // while signal was down.
                 Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
@@ -64,6 +82,9 @@
                         true /* firstAttempt */);
                 mContext.startService(serviceIntent);
             } else {
+                Log.v(TAG, "Signal returned: reattempting activation for " + mPhoneAccount.getId());
+                LocalLogHelper.log(TAG,
+                        "Signal returned: reattempting activation for " + mPhoneAccount.getId());
                 // Otherwise initiate an activation because this means that an OMTP source was
                 // recognized but either the activation text was not successfully sent or a response
                 // was not received.
@@ -72,10 +93,20 @@
                 carrierConfigHelper.startActivation();
             }
         } else {
+            Log.v(TAG, "Notifications channel is inactive for " + mPhoneAccount.getId());
+            mContext.stopService(OmtpVvmSyncService.getSyncIntent(
+                    mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, mPhoneAccount,
+                    true /* firstAttempt */));
+
+            if (!OmtpVvmSourceManager.getInstance(mContext).isVvmSourceRegistered(mPhoneAccount)) {
+                return;
+            }
+
             VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
                     VoicemailContract.Status.CONFIGURATION_STATE_OK,
                     VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION,
                     VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
         }
+        mPreviousState = state;
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java b/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
index bf5befd..eb6a175 100644
--- a/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
+++ b/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
@@ -61,7 +61,8 @@
                 outputStream.write(inputBytes);
             }
         } catch (IOException e) {
-            Log.e(TAG, "Error writing to file: ", e);
+            Log.w(TAG, String.format("File not found for %s", mUri));
+            return;
         } finally {
             IoUtils.closeQuietly(outputStream);
         }
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index 1d20dbd..d68a36b 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -194,6 +194,11 @@
             }
             message = mFolder.getMessage(uid);
             VoicemailPayload voicemailPayload = fetchVoicemailPayload(message);
+
+            if (voicemailPayload == null) {
+                return false;
+            }
+
             callback.setVoicemailContent(voicemailPayload);
             return true;
         } catch (MessagingException e) {
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index d768e17..3eefbee 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -23,11 +23,14 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
 import android.util.Log;
 
 import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
+import com.android.phone.vvm.omtp.LocalLogHelper;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
@@ -48,6 +51,11 @@
         mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
                 intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
 
+        if (mPhoneAccount == null) {
+            Log.w(TAG, "Received message for null phone account");
+            return;
+        }
+
         if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mContext, mPhoneAccount)) {
             Log.v(TAG, "Received vvm message for disabled vvm source.");
             return;
@@ -69,10 +77,12 @@
 
                 Log.v(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
                         " with event" + message.getSyncTriggerEvent());
-
+                LocalLogHelper.log(TAG, "Received SYNC sms for " + mPhoneAccount.getId() +
+                        " with event" + message.getSyncTriggerEvent());
                 processSync(message);
             } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
                 Log.v(TAG, "Received STATUS sms for " + mPhoneAccount.getId());
+                LocalLogHelper.log(TAG, "Received Status sms for " + mPhoneAccount.getId());
                 StatusMessage message = new StatusMessage(messageData);
                 updateSource(message);
             } else {
@@ -141,6 +151,9 @@
                     mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, mPhoneAccount,
                     true /* firstAttempt */);
             mContext.startService(serviceIntent);
+
+            PhoneGlobals.getInstance().clearMwiIndicator(
+                    PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount));
         } else {
             Log.w(TAG, "Visual voicemail not available for subscriber.");
             // Override default isEnabled setting to false since visual voicemail is unable to
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
index 286dde3..0520098 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
@@ -24,12 +24,12 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.phone.PhoneUtils;
-import com.android.phone.VvmPhoneStateListener;
+import com.android.phone.vvm.omtp.VvmPhoneStateListener;
 
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * A singleton class designed to remember the active OMTP visual voicemail sources. Because a
@@ -69,8 +69,10 @@
             mSubscriptionManager = SubscriptionManager.from(context);
             mTelephonyManager = (TelephonyManager)
                     mContext.getSystemService(Context.TELEPHONY_SERVICE);
-            mActiveVvmSources = new HashSet<PhoneAccountHandle>();
-            mPhoneStateListenerMap = new HashMap<PhoneAccountHandle, PhoneStateListener>();
+            mActiveVvmSources = Collections.newSetFromMap(
+                    new ConcurrentHashMap<PhoneAccountHandle, Boolean>(8, 0.9f, 1));
+            mPhoneStateListenerMap =
+                    new ConcurrentHashMap<PhoneAccountHandle, PhoneStateListener>(8, 0.9f, 1);
         }
     }
 
@@ -89,6 +91,13 @@
                 removeSource(phoneAccount);
             }
         }
+
+        // Remove any orphaned phone state listeners as well.
+        for (PhoneAccountHandle phoneAccount : mPhoneStateListenerMap.keySet()) {
+            if (!PhoneUtils.isPhoneAccountActive(mSubscriptionManager, phoneAccount)) {
+                removePhoneStateListener(phoneAccount);
+            }
+        }
     }
 
     public void removeSource(Phone phone) {
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index 363aab9..3c47975 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -32,6 +32,7 @@
 
 import com.android.phone.PhoneUtils;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
+import com.android.phone.vvm.omtp.LocalLogHelper;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 
 import java.util.HashMap;
@@ -57,6 +58,9 @@
     // Timeout used to call ConnectivityManager.requestNetwork
     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
 
+    // Minimum time allowed between full syncs
+    private static final int MINIMUM_FULL_SYNC_INTERVAL_MILLIS = 60 * 1000;
+
     // Number of retries
     private static final int NETWORK_RETRY_COUNT = 6;
 
@@ -137,7 +141,12 @@
         }
 
         String action = intent.getAction();
+
         PhoneAccountHandle phoneAccount = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT);
+
+        LocalLogHelper.log(TAG, "Sync requested: " + action +
+                " for all accounts: " + String.valueOf(phoneAccount == null));
+
         if (phoneAccount != null) {
             Log.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
             setupAndSendNetworkRequest(phoneAccount, action);
@@ -158,6 +167,20 @@
             return;
         }
 
+        if (SYNC_FULL_SYNC.equals(action)) {
+            long lastSyncTime = VisualVoicemailSettingsUtil.getVisualVoicemailLastFullSyncTime(
+                    this, phoneAccount);
+            long currentTime = System.currentTimeMillis();
+            if (currentTime - lastSyncTime < MINIMUM_FULL_SYNC_INTERVAL_MILLIS) {
+                // If it's been less than a minute since the last sync, bail.
+                Log.v(TAG, "Avoiding duplicate full sync: synced recently for "
+                        + phoneAccount.getId());
+                return;
+            }
+            VisualVoicemailSettingsUtil.setVisualVoicemailLastFullSyncTime(
+                    this, phoneAccount, currentTime);
+        }
+
         OmtpVvmNetworkRequestCallback networkCallback = new OmtpVvmNetworkRequestCallback(this,
                 phoneAccount, action);
         requestNetwork(networkCallback);
@@ -231,6 +254,7 @@
                     }
 
                     Log.v(TAG, "Retrying " + mAction);
+                    LocalLogHelper.log(TAG, "Immediately retrying " + mAction);
                 } else {
                     // Nothing more to do here, just exit.
                     releaseNetwork(this);
@@ -247,25 +271,11 @@
         @Override
         public void onLost(Network network) {
             releaseNetwork(this);
-
-            if (mRetryCount > 0) {
-                mRetryCount--;
-                requestNetwork(this);
-            } else {
-                setRetryAlarm(mPhoneAccount, mAction);
-            }
         }
 
         @Override
         public void onUnavailable() {
             releaseNetwork(this);
-
-            if (mRetryCount> 0) {
-                mRetryCount--;
-                requestNetwork(this);
-            } else {
-                setRetryAlarm(mPhoneAccount, mAction);
-            }
         }
     }
 
@@ -295,6 +305,7 @@
                 phoneAccount);
 
         Log.v(TAG, "Retrying "+ action + " in " + retryInterval + "ms");
+        LocalLogHelper.log(TAG, "Retrying "+ action + " in " + retryInterval + "ms");
 
         AlarmManager alarmManager = (AlarmManager)
                 this.getSystemService(Context.ALARM_SERVICE);
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 6c4f48e..3dbf459 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -228,7 +228,10 @@
 
         // Specify the connection time of the conference to be the connection time of the original
         // connection.
-        setConnectTimeMillis(conferenceHost.getOriginalConnection().getConnectTime());
+        long connectTime = conferenceHost.getOriginalConnection().getConnectTime();
+        setConnectTimeMillis(connectTime);
+        // Set the connectTime in the connection as well.
+        conferenceHost.setConnectTimeMillis(connectTime);
 
         mTelephonyConnectionService = telephonyConnectionService;
         setConferenceHost(conferenceHost);