Merge "Do not update state of Disconnected Connection" into nyc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 47987fa..2335d77 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -662,13 +662,22 @@
                 <data android:scheme="sms" />
             </intent-filter>
         </receiver>
-       <receiver android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
+        <receiver
+            android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
                 <action android:name="android.intent.action.SIM_STATE_CHANGED" />
             </intent-filter>
-       </receiver>
+        </receiver>
+        <receiver
+            android:name="com.android.phone.vvm.omtp.OmtpBootCompletedReceiver"
+            android:exported="true"
+            android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
        <receiver
            android:name="com.android.phone.vvm.omtp.fetch.FetchVoicemailReceiver"
            android:exported="true">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4f7cd53..49bc518 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -966,7 +966,7 @@
     <!-- In-call screen: call failure message displayed in an error dialog -->
     <string name="incall_error_no_phone_number_supplied">To place a call, enter a valid number.</string>
     <!-- In-call screen: call failure message displayed in an error dialog -->
-    <string name="incall_error_call_failed">Can\'t call.</string>
+    <string name="incall_error_call_failed">Call failed.</string>
     <!-- In-call screen: status message displayed in a dialog when starting an MMI -->
     <string name="incall_status_dialed_mmi">Starting MMI sequence\u2026</string>
     <!-- In-call screen: message displayed in an error dialog -->
diff --git a/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java b/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java
new file mode 100644
index 0000000..a751e19
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Set;
+
+/**
+ * Stores subscription ID of SIMs while the device is locked to process them after the device is
+ * unlocked. This class is only intended to be used within {@link SimChangeReceiver}. subId is used
+ * for Visual voicemail activation/deactivation, which need to be done when the device is unlocked.
+ * But the enumeration of subIds happen on boot, when the device could be locked. This class is used
+ * to defer all activation/deactivation until the device is unlocked.
+ *
+ * The subIds are stored in device encrypted {@link SharedPreferences} (readable/writable even
+ * locked). after the device is unlocked the list is read and deleted.
+ */
+public class OmtpBootCompletedReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "OmtpBootCompletedRcvr";
+
+    private static final String DEFERRED_SUBID_LIST_KEY = "deferred_sub_id_key";
+
+    @VisibleForTesting
+    interface SubIdProcessor{
+        void process(Context context,int subId);
+    }
+
+    private SubIdProcessor mSubIdProcessor = new SubIdProcessor() {
+        @Override
+        public void process(Context context, int subId) {
+            SimChangeReceiver.processSubId(context,subId);
+        }
+    };
+
+    /**
+     * Write the subId to the the list.
+     */
+    public static void addDeferredSubId(Context context, int subId) {
+        SharedPreferences sharedPreferences = getSubIdSharedPreference(context);
+        Set<String> subIds =
+                new ArraySet<>(sharedPreferences.getStringSet(DEFERRED_SUBID_LIST_KEY, null));
+        subIds.add(String.valueOf(subId));
+        sharedPreferences.edit().putStringSet(DEFERRED_SUBID_LIST_KEY, subIds).apply();
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Listens to android.intent.action.BOOT_COMPLETED
+        if(!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+            return;
+        }
+
+        Log.v(TAG, "processing deferred subId list");
+        Set<Integer> subIds = readAndDeleteSubIds(context);
+        for (Integer subId : subIds) {
+            Log.v(TAG, "processing subId " + subId);
+            mSubIdProcessor.process(context, subId);
+        }
+    }
+
+    /**
+     * Read all subId from the list to a unique integer set, and delete the preference.
+     */
+    private static Set<Integer> readAndDeleteSubIds(Context context) {
+        SharedPreferences sharedPreferences = getSubIdSharedPreference(context);
+        Set<String> subIdStrings = sharedPreferences.getStringSet(DEFERRED_SUBID_LIST_KEY, null);
+        Set<Integer> subIds = new ArraySet<>();
+        if(subIdStrings == null) {
+            return subIds;
+        }
+        for(String string : subIdStrings){
+            subIds.add(Integer.valueOf(string));
+        }
+        getSubIdSharedPreference(context).edit().remove(DEFERRED_SUBID_LIST_KEY).apply();
+        return subIds;
+    }
+
+    @VisibleForTesting
+    void setSubIdProcessorForTest(SubIdProcessor processor){
+        mSubIdProcessor = processor;
+    }
+
+    private static SharedPreferences getSubIdSharedPreference(Context context) {
+        return PreferenceManager
+                .getDefaultSharedPreferences(context.createDeviceEncryptedStorageContext());
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 57e23a9..61cf6d3 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -34,7 +34,8 @@
  * retrieve carrier vvm configuration details before sending the appropriate texts.
  */
 public class OmtpVvmCarrierConfigHelper {
-    private static final String TAG = "OmtpVvmCarrierConfigHelper";
+
+    private static final String TAG = "OmtpVvmCarrierCfgHlpr";
     private Context mContext;
     private int mSubId;
     private PersistableBundle mCarrierConfig;
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index c42797a..0a37493 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -34,15 +34,16 @@
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 
 /**
- * This class listens to the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} and
- * {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED} to determine when a SIM is added, replaced,
- * or removed.
+ * This class listens to the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} and {@link
+ * TelephonyIntents#ACTION_SIM_STATE_CHANGED} to determine when a SIM is added, replaced, or
+ * removed.
  *
  * When a SIM is added, send an activate SMS. When a SIM is removed, remove the sync accounts and
  * change the status in the voicemail_status table.
  */
 public class SimChangeReceiver extends BroadcastReceiver {
-    private final String TAG = "SimChangeReceiver";
+
+    private static final String TAG = "SimChangeReceiver";
 
     @Override
     public void onReceive(Context context, Intent intent) {
@@ -74,46 +75,54 @@
                     return;
                 }
 
-                OmtpVvmCarrierConfigHelper carrierConfigHelper =
-                        new OmtpVvmCarrierConfigHelper(context, subId);
-                if (carrierConfigHelper.isOmtpVvmType()) {
-                    PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
-                            SubscriptionManager.getPhoneId(subId));
-
-                    boolean isUserSet = VisualVoicemailSettingsUtil.isVisualVoicemailUserSet(
-                            context, phoneAccount);
-                    boolean isEnabledInSettings =
-                            VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context,
-                            phoneAccount);
-                    boolean isSupported =
-                            context.getResources().getBoolean(R.bool.allow_visual_voicemail);
-                    boolean isEnabled = isSupported && (isUserSet ? isEnabledInSettings :
-                        carrierConfigHelper.isEnabledByDefault());
-
-                    if (!isUserSet) {
-                        // Preserve the previous setting for "isVisualVoicemailEnabled" if it is
-                        // set by the user, otherwise, set this value for the first time.
-                        VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(context, phoneAccount,
-                                isEnabled, /** isUserSet */ false);
-                    }
-
-                    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(
-                                phoneAccount);
-                        carrierConfigHelper.startActivation();
-                    } else {
-                        // It may be that the source was not registered to begin with but we want
-                        // to run through the steps to remove the source just in case.
-                        OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
-                        Log.v(TAG, "Sim change for disabled account.");
-                    }
+                if (!UserManager.get(context).isUserUnlocked()) {
+                    OmtpBootCompletedReceiver.addDeferredSubId(context, subId);
+                } else {
+                    processSubId(context, subId);
                 }
                 break;
         }
     }
+
+    public static void processSubId(Context context, int subId) {
+        OmtpVvmCarrierConfigHelper carrierConfigHelper =
+                new OmtpVvmCarrierConfigHelper(context, subId);
+        if (carrierConfigHelper.isOmtpVvmType()) {
+            PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
+                    SubscriptionManager.getPhoneId(subId));
+
+            boolean isUserSet = VisualVoicemailSettingsUtil.isVisualVoicemailUserSet(
+                    context, phoneAccount);
+            boolean isEnabledInSettings =
+                    VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context,
+                            phoneAccount);
+            boolean isSupported =
+                    context.getResources().getBoolean(R.bool.allow_visual_voicemail);
+            boolean isEnabled = isSupported && (isUserSet ? isEnabledInSettings :
+                    carrierConfigHelper.isEnabledByDefault());
+
+            if (!isUserSet) {
+                // Preserve the previous setting for "isVisualVoicemailEnabled" if it is
+                // set by the user, otherwise, set this value for the first time.
+                VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(context, phoneAccount,
+                        isEnabled, /** isUserSet */false);
+            }
+
+            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(
+                        phoneAccount);
+                carrierConfigHelper.startActivation();
+            } else {
+                // It may be that the source was not registered to begin with but we want
+                // to run through the steps to remove the source just in case.
+                OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
+                Log.v(TAG, "Sim change for disabled account.");
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 0950862..9ac37a4 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -19,16 +19,13 @@
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.NetworkRequest;
 import android.net.Uri;
+import android.os.UserManager;
 import android.provider.Telephony;
 import android.provider.VoicemailContract;
 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;
@@ -52,6 +49,12 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        if (!UserManager.get(context).isUserUnlocked()) {
+            Log.i(TAG, "Received message on locked device");
+            // A full sync will happen after the device is unlocked, so nothing need to be done.
+            return;
+        }
+
         mContext = context;
         mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
                 intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index 853ad65..fb8b45b 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -319,7 +319,12 @@
         public void onAvailable(Network network) {
             super.onAvailable(network);
             NetworkInfo info = getConnectivityManager().getNetworkInfo(network);
-            Log.d(TAG, "Network Type: " + info == null ? "Unknown" : info.getTypeName());
+            if (info == null) {
+                Log.d(TAG, "Network Type: Unknown");
+            } else {
+                Log.d(TAG, "Network Type: " + info.getTypeName());
+            }
+
             doSync(network, this, mPhoneAccount, mVoicemail, mAction);
         }
 
diff --git a/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java b/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java
new file mode 100644
index 0000000..a2d247a
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 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 android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+
+import com.android.phone.vvm.omtp.OmtpBootCompletedReceiver.SubIdProcessor;
+
+import java.util.Set;
+
+public class OmtpBootCompletedReceiverTests extends AndroidTestCase {
+    OmtpBootCompletedReceiver mReceiver = new OmtpBootCompletedReceiver();
+    @Override
+    public void setUp() {
+    }
+
+    @Override
+    public void tearDown() {
+        PreferenceManager
+                .getDefaultSharedPreferences(getContext().createDeviceEncryptedStorageContext())
+                .edit().clear().apply();
+    }
+
+    public void testReadWriteList() {
+        readWriteList(new int[] {1});
+    }
+
+    public void testReadWriteList_Multiple() {
+        readWriteList(new int[] {1, 2});
+    }
+
+    public void testReadWriteList_Duplicate() {
+        readWriteList(new int[] {1, 1});
+    }
+
+    private void readWriteList(int[] values) {
+        for (int value : values) {
+            OmtpBootCompletedReceiver.addDeferredSubId(getContext(), value);
+        }
+        TestSubIdProcessor processor = new TestSubIdProcessor(values);
+        mReceiver.setSubIdProcessorForTest(processor);
+        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+        mReceiver.onReceive(getContext(), intent);
+        processor.assertMatch();
+        // after onReceive() is called the list should be empty
+        TestSubIdProcessor emptyProcessor = new TestSubIdProcessor(new int[] {});
+        mReceiver.setSubIdProcessorForTest(processor);
+        mReceiver.onReceive(getContext(), intent);
+        processor.assertMatch();
+    }
+
+    private static class TestSubIdProcessor implements SubIdProcessor {
+        private final Set<Integer> mExpectedSubIds;
+
+        public TestSubIdProcessor(int[] expectedSubIds) {
+            mExpectedSubIds = new ArraySet<>();
+            for(int subId : expectedSubIds){
+                mExpectedSubIds.add(subId);
+            }
+        }
+
+        @Override
+        public void process(Context context, int subId){
+            assertTrue(mExpectedSubIds.contains(subId));
+            mExpectedSubIds.remove(subId);
+        }
+
+        public void assertMatch(){
+            assertTrue(mExpectedSubIds.isEmpty());
+        }
+    }
+}