Merge "fix typo in DIGEST-MD5 charset" into nyc-mr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2accdab..b5fd399 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -622,7 +622,7 @@
             </intent-filter>
         </provider>
         <receiver android:name="com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"
-            android:exported="true"
+            android:exported="false"
             androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.VOICEMAIL_SMS_RECEIVED"/>
diff --git a/res/xml/vvm_config.xml b/res/xml/vvm_config.xml
index fad246f..19c667e 100644
--- a/res/xml/vvm_config.xml
+++ b/res/xml/vvm_config.xml
@@ -36,6 +36,10 @@
     </string-array>
     <string name="vvm_type_string">vvm_type_omtp</string>
     <boolean name="vvm_cellular_data_required_bool" value="true"/>
+    <string-array name="vvm_disabled_capabilities_string_array">
+      <!-- b/32365569 -->
+      <item value="STARTTLS"/>
+    </string-array>
   </pbundle_as_map>
 
   <pbundle_as_map>
diff --git a/sip/src/com/android/services/telephony/sip/SipEditor.java b/sip/src/com/android/services/telephony/sip/SipEditor.java
index f6e9c22..07e5b62 100644
--- a/sip/src/com/android/services/telephony/sip/SipEditor.java
+++ b/sip/src/com/android/services/telephony/sip/SipEditor.java
@@ -259,7 +259,7 @@
      *
      * @param p The {@link SipProfile} to delete.
      */
-    private void deleteAndUnregisterProfile(SipProfile p) {
+    private void deleteAndUnregisterProfile(SipProfile p) throws IOException {
         if (p == null) return;
         mProfileDb.deleteProfile(p);
         mSipAccountRegistry.stopSipService(this, p.getProfileName());
diff --git a/sip/src/com/android/services/telephony/sip/SipProfileDb.java b/sip/src/com/android/services/telephony/sip/SipProfileDb.java
index e7b201b..bb1c7ec 100644
--- a/sip/src/com/android/services/telephony/sip/SipProfileDb.java
+++ b/sip/src/com/android/services/telephony/sip/SipProfileDb.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.net.sip.SipProfile;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 
 import java.io.File;
@@ -66,9 +67,13 @@
         mSipPreferences = new SipPreferences(mContext);
     }
 
-    public void deleteProfile(SipProfile p) {
+    public void deleteProfile(SipProfile p) throws IOException {
         synchronized(SipProfileDb.class) {
-            deleteProfile(new File(mProfilesDirectory + p.getProfileName()));
+            File profileFile = new File(mProfilesDirectory, p.getProfileName());
+            if (!isChild(new File(mProfilesDirectory), profileFile)) {
+                throw new IOException("Invalid Profile Credentials!");
+            }
+            deleteProfile(profileFile);
             if (mProfilesCount < 0) retrieveSipProfileListInternal();
         }
     }
@@ -93,7 +98,10 @@
     public void saveProfile(SipProfile p) throws IOException {
         synchronized(SipProfileDb.class) {
             if (mProfilesCount < 0) retrieveSipProfileListInternal();
-            File f = new File(mProfilesDirectory + p.getProfileName());
+            File f = new File(mProfilesDirectory, p.getProfileName());
+            if (!isChild(new File(mProfilesDirectory), f)) {
+                throw new IOException("Invalid Profile Credentials!");
+            }
             if (!f.exists()) f.mkdirs();
             AtomicFile atomicFile = new AtomicFile(new File(f, PROFILE_OBJ_FILE));
             FileOutputStream fos = null;
@@ -173,4 +181,19 @@
     private static void log(String msg) {
         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
     }
+
+    /**
+     * Verifies that the file is a direct child of the base directory.
+     */
+    private boolean isChild(File base, File file) {
+        if (base == null || file == null) {
+            return false;
+        }
+        if (!base.equals(file.getAbsoluteFile().getParentFile())) {
+            Log.w(SipUtil.LOG_TAG, "isChild, file is not a child of the base dir.");
+            EventLog.writeEvent(0x534e4554, "31530456", -1, "");
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
index 3678c46..cf03dd3 100644
--- a/sip/src/com/android/services/telephony/sip/SipUtil.java
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -164,7 +164,12 @@
                 }
                 Log.i(LOG_TAG, "(Migration) Deleting SIP profile: " +
                         profileToMove.getProfileName());
-                dbDeStorage.deleteProfile(profileToMove);
+                try {
+                    dbDeStorage.deleteProfile(profileToMove);
+                } catch (IOException e) {
+                    Log.w(LOG_TAG, "Error Deleting file: " +
+                            profileToMove.getProfileName(), e);
+                }
             }
         }
         // Delete supporting structures if they exist
diff --git a/src/com/android/phone/settings/VoicemailChangePinActivity.java b/src/com/android/phone/settings/VoicemailChangePinActivity.java
index 52c97a7..33da27a 100644
--- a/src/com/android/phone/settings/VoicemailChangePinActivity.java
+++ b/src/com/android/phone/settings/VoicemailChangePinActivity.java
@@ -35,6 +35,7 @@
 import android.text.InputFilter.LengthFilter;
 import android.text.TextWatcher;
 import android.view.KeyEvent;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.WindowManager;
@@ -436,6 +437,15 @@
         }
     }
 
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            onBackPressed();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
       if (!mNextButton.isEnabled()) {
         return true;
diff --git a/src/com/android/phone/vvm/omtp/ActivationTask.java b/src/com/android/phone/vvm/omtp/ActivationTask.java
index 3a9893a..72f1f54 100644
--- a/src/com/android/phone/vvm/omtp/ActivationTask.java
+++ b/src/com/android/phone/vvm/omtp/ActivationTask.java
@@ -24,7 +24,6 @@
 import android.os.Bundle;
 import android.provider.Settings;
 import android.provider.Settings.Global;
-import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
@@ -101,13 +100,6 @@
             return;
         }
 
-        // Check for signal before activating. The event often happen while boot and the
-        // network is not connected yet. Launching activation will likely to cause the SMS
-        // sending to fail and waste unnecessary time waiting for time out.
-        if (!hasSignal(context, subId)) {
-            VvmLog.i(TAG, "Activation requested while not in service, rejecting");
-        }
-
         Intent intent = BaseTask.createIntent(context, ActivationTask.class, subId);
         if (messageData != null) {
             intent.putExtra(EXTRA_MESSAGE_DATA_BUNDLE, messageData);
@@ -140,12 +132,6 @@
             return;
         }
 
-        if (!hasSignal(getContext(), subId)) {
-            VvmLog.i(TAG, "Service lost during activation, aborting");
-            // Don't retry, a new activation will be started after the signal returned.
-            return;
-        }
-
         OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(getContext(), subId);
         if (!helper.isValid()) {
             VvmLog.i(TAG, "VVM not supported on subId " + subId);
@@ -167,6 +153,7 @@
 
         if (!OmtpVvmSourceManager.getInstance(getContext())
                 .isVvmSourceRegistered(phoneAccountHandle)) {
+            // This account has not been activated before during the lifetime of this boot.
             VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(getContext(),
                     phoneAccountHandle);
             if (preferences.getString(OmtpConstants.SERVER_ADDRESS, null) == null) {
@@ -178,12 +165,20 @@
             } else {
                 // The account has been activated on this device before. Pretend it is already
                 // activated. If there are any activation error it will overwrite this status.
-                VoicemailStatus.edit(getContext(), phoneAccountHandle)
-                        .setConfigurationState(Status.CONFIGURATION_STATE_OK)
-                        .apply();
+                helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle),
+                        OmtpEvents.CONFIG_ACTIVATING_SUBSEQUENT);
             }
 
         }
+        if (!hasSignal(getContext(), subId)) {
+            VvmLog.i(TAG, "Service lost during activation, aborting");
+            // Restore the "NO SIGNAL" state since it will be overwritten by the CONFIG_ACTIVATING
+            // event.
+            helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle),
+                    OmtpEvents.NOTIFICATION_SERVICE_LOST);
+            // Don't retry, a new activation will be started after the signal returned.
+            return;
+        }
 
         helper.activateSmsFilter();
         VoicemailStatus.Editor status = mRetryPolicy.getVoicemailStatusEditor();
diff --git a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
index 88943c9..0c19a6a 100644
--- a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
+++ b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.provider.VoicemailContract;
 import android.provider.VoicemailContract.Status;
+
 import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.OmtpEvents.Type;
 
@@ -49,6 +50,7 @@
     private static void handleConfigurationEvent(Context context, VoicemailStatus.Editor status,
             OmtpEvents event) {
         switch (event) {
+            case CONFIG_DEFAULT_PIN_REPLACED:
             case CONFIG_REQUEST_STATUS_SUCCESS:
             case CONFIG_PIN_SET:
                 status
@@ -61,8 +63,14 @@
                 // for this activation.
                 status
                         .setConfigurationState(Status.CONFIGURATION_STATE_CONFIGURING)
-                        .setDataChannelState(Status.DATA_CHANNEL_STATE_OK)
-                        .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK).apply();
+                        .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_OK).apply();
+                break;
+            case CONFIG_ACTIVATING_SUBSEQUENT:
+                status
+                        .setConfigurationState(Status.CONFIGURATION_STATE_OK)
+                        .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_OK).apply();
                 break;
             case CONFIG_SERVICE_NOT_AVAILABLE:
                 status
@@ -116,6 +124,7 @@
             case DATA_SSL_INVALID_HOST_NAME:
             case DATA_CANNOT_ESTABLISH_SSL_SESSION:
             case DATA_IOE_ON_OPEN:
+            case DATA_GENERIC_IMAP_IOE:
                 status
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR)
@@ -137,6 +146,7 @@
 
             case DATA_REJECTED_SERVER_RESPONSE:
             case DATA_INVALID_INITIAL_SERVER_RESPONSE:
+            case DATA_MAILBOX_OPEN_FAILED:
             case DATA_SSL_EXCEPTION:
             case DATA_ALL_SOCKET_CONNECTION_FAILED:
                 status
diff --git a/src/com/android/phone/vvm/omtp/OmtpEvents.java b/src/com/android/phone/vvm/omtp/OmtpEvents.java
index 648e0d0..7a89418 100644
--- a/src/com/android/phone/vvm/omtp/OmtpEvents.java
+++ b/src/com/android/phone/vvm/omtp/OmtpEvents.java
@@ -17,6 +17,7 @@
 package com.android.phone.vvm.omtp;
 
 import android.annotation.IntDef;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -34,6 +35,8 @@
     // The voicemail PIN is replaced with a generated PIN, user should change it.
     CONFIG_DEFAULT_PIN_REPLACED(Type.CONFIGURATION, true),
     CONFIG_ACTIVATING(Type.CONFIGURATION, true),
+    // There are already activation records, this is only a book-keeping activation.
+    CONFIG_ACTIVATING_SUBSEQUENT(Type.CONFIGURATION, true),
     CONFIG_STATUS_SMS_TIME_OUT(Type.CONFIGURATION),
     CONFIG_SERVICE_NOT_AVAILABLE(Type.CONFIGURATION),
 
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index 8020996..29ccc6c 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -184,7 +184,7 @@
     }
 
     @Override
-    public void performConference(TelephonyConnection otherConnection) {
+    public void performConference(android.telecom.Connection otherConnection) {
         if (isImsConnection()) {
             super.performConference(otherConnection);
         } else {
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index e33c351..4acb6f8 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -439,7 +439,7 @@
     @Override
     public void onMerge(android.telecom.Connection connection) {
         try {
-            Phone phone = ((TelephonyConnection) connection).getPhone();
+            Phone phone = mConferenceHost.getPhone();
             if (phone != null) {
                 phone.conference();
             }
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 6669482..f7d587d 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -187,6 +187,7 @@
         Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
         HashSet<Conferenceable> conferenceableSet = new HashSet<>(mTelephonyConnections.size() +
                 mImsConferences.size());
+        HashSet<Conferenceable> conferenceParticipantsSet = new HashSet<>();
 
         // Loop through and collect all calls which are active or holding
         for (TelephonyConnection connection : mTelephonyConnections) {
@@ -240,6 +241,7 @@
                 case Connection.STATE_ACTIVE:
                     //fall through
                 case Connection.STATE_HOLDING:
+                    conferenceParticipantsSet.addAll(conference.getConnections());
                     conferenceableSet.add(conference);
                     continue;
                 default:
@@ -256,6 +258,16 @@
                         .stream()
                         .filter(conferenceable -> c != conferenceable)
                         .collect(Collectors.toList());
+                // TODO: Remove this once RemoteConnection#setConferenceableConnections is fixed.
+                // Add all conference participant connections as conferenceable with a standalone
+                // Connection.  We need to do this to ensure that RemoteConnections work properly.
+                // At the current time, a RemoteConnection will not be conferenceable with a
+                // Conference, so we need to add its children to ensure the user can merge the call
+                // into the conference.
+                // We should add support for RemoteConnection#setConferenceables, which accepts a
+                // list of remote conferences and connections in the future.
+                conferenceables.addAll(conferenceParticipantsSet);
+
                 ((Connection) c).setConferenceables(conferenceables);
             } else if (c instanceof Conference) {
                 // Remove all conferences from the set, since we can not conference a conference
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 365f99e..c4f9c9a 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -683,7 +683,7 @@
         }
     }
 
-    public void performConference(TelephonyConnection otherConnection) {
+    public void performConference(Connection otherConnection) {
         Log.d(this, "performConference - %s", this);
         if (getPhone() != null) {
             try {
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index becb570..5afdd72 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -616,14 +616,35 @@
         }
     }
 
+    /**
+     * Conferences two connections.
+     *
+     * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
+     * a limitation in that it can only specify conferenceables which are instances of
+     * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
+     * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
+     * a {@link Conference} and a {@link Connection}.  As a result when, merging a
+     * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
+     * require merging a {@link ConferenceParticipantConnection} which is a child of the
+     * {@link Conference} with a {@link TelephonyConnection}.  The
+     * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
+     * conference merge, so we need to call
+     * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
+     * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
+     *
+     * @param connection1 A connection to merge into a conference call.
+     * @param connection2 A connection to merge into a conference call.
+     */
     @Override
     public void onConference(Connection connection1, Connection connection2) {
-        if (connection1 instanceof TelephonyConnection &&
-                connection2 instanceof TelephonyConnection) {
-            ((TelephonyConnection) connection1).performConference(
-                (TelephonyConnection) connection2);
+        if (connection1 instanceof TelephonyConnection) {
+            ((TelephonyConnection) connection1).performConference(connection2);
+        } else if (connection2 instanceof TelephonyConnection) {
+            ((TelephonyConnection) connection2).performConference(connection1);
+        } else {
+            Log.w(this, "onConference - cannot merge connections " +
+                    "Connection1: %s, Connection2: %2", connection1, connection2);
         }
-
     }
 
     private boolean isRadioOn() {