Implement RemoteVvmTaskService

RemoteVvmTaskService manages the connection to the
VisualVoicemailService in the default dialer, notifying it service
connected, incoming VVM SMS and SIM removed events. It will held
resources for the dialer until it is signaled all events has been
processed.

Test: CTS / CTS verifier test
Change-Id: I713ace11c526a5ed838b6a85932c2588649cc14e
diff --git a/src/com/android/phone/Assert.java b/src/com/android/phone/Assert.java
index 143e66f..37ccda8 100644
--- a/src/com/android/phone/Assert.java
+++ b/src/com/android/phone/Assert.java
@@ -49,7 +49,11 @@
     }
 
     public static void fail() {
-        throw new AssertionError("Fail");
+        fail("Fail");
+    }
+
+    public static void fail(String reason) {
+        throw new AssertionError(reason);
     }
 
     /**
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 3e5438f..407d050 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -18,8 +18,10 @@
 
 import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
 
+import android.Manifest.permission;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -53,6 +55,7 @@
 import android.telephony.NeighboringCellInfo;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
+import android.telephony.SmsManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyHistogram;
@@ -63,6 +66,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
+
 import com.android.ims.ImsManager;
 import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.CellNetworkScanResult;
@@ -86,8 +90,11 @@
 import com.android.internal.util.HexDump;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.settings.VoicemailNotificationSettingsUtil;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -1985,11 +1992,33 @@
     }
 
     @Override
-    public VisualVoicemailSmsFilterSettings getSystemVisualVoicemailSmsFilterSettings(
-            String packageName, int subId) {
+    public VisualVoicemailSmsFilterSettings getActiveVisualVoicemailSmsFilterSettings(int subId) {
         enforceReadPrivilegedPermission();
         return VisualVoicemailSmsFilterConfig
-                .getVisualVoicemailSmsFilterSettings(mPhone.getContext(), packageName, subId);
+                .getActiveVisualVoicemailSmsFilterSettings(mPhone.getContext(), subId);
+    }
+
+    @Override
+    public void sendVisualVoicemailSmsForSubscriber(String callingPackage, int subId,
+            String number, int port, String text, PendingIntent sentIntent) {
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        enforceDefaultDialer(callingPackage);
+        enforceSendSmsPermission();
+        // Make the calls as the phone process.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
+            if (port == 0) {
+                smsManager.sendTextMessageWithSelfPermissions(number, null, text,
+                        sentIntent, null, false);
+            } else {
+                byte[] data = text.getBytes(StandardCharsets.UTF_8);
+                smsManager.sendDataMessageWithSelfPermissions(number, null,
+                        (short) port, data, sentIntent, null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
     /**
      * Returns the unread count of voicemails
@@ -3239,6 +3268,28 @@
     }
 
     /**
+     * Make sure either called from same process as self (phone) or IPC caller has send SMS
+     * permission.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforceSendSmsPermission() {
+        mApp.enforceCallingOrSelfPermission(permission.SEND_SMS, null);
+    }
+
+    /**
+     * Make sure called from the default dialer
+     *
+     * @throws SecurityException if the caller is not the default dialer
+     */
+    private void enforceDefaultDialer(String callingPackage) {
+        TelecomManager telecomManager = mPhone.getContext().getSystemService(TelecomManager.class);
+        if (!callingPackage.equals(telecomManager.getDefaultDialerPackage())) {
+            throw new SecurityException("Caller not default dialer.");
+        }
+    }
+
+    /**
      * Return the application ID for the app type.
      *
      * @param subId the subscription ID that this request applies to.
diff --git a/src/com/android/phone/VisualVoicemailSmsFilterConfig.java b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
index 2b2e2f5..2ffb477 100644
--- a/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
+++ b/src/com/android/phone/VisualVoicemailSmsFilterConfig.java
@@ -16,12 +16,15 @@
 package com.android.phone;
 
 import android.annotation.Nullable;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import android.util.ArraySet;
 
+import com.android.phone.vvm.RemoteVvmTaskManager;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -39,6 +42,7 @@
     private static final String PREFIX_KEY = "_prefix";
     private static final String ORIGINATING_NUMBERS_KEY = "_originating_numbers";
     private static final String DESTINATION_PORT_KEY = "_destination_port";
+    private static final String DEFAULT_PACKAGE = "com.android.phone";
 
     public static void enableVisualVoicemailSmsFilter(Context context, String callingPackage,
             int subId,
@@ -58,6 +62,21 @@
                 .apply();
     }
 
+    public static VisualVoicemailSmsFilterSettings getActiveVisualVoicemailSmsFilterSettings(
+            Context context, int subId) {
+        ComponentName componentName = RemoteVvmTaskManager.getRemotePackage(context);
+        String packageName;
+        if (componentName == null) {
+            packageName = DEFAULT_PACKAGE;
+        } else {
+            packageName = componentName.getPackageName();
+        }
+        return getVisualVoicemailSmsFilterSettings(
+                context,
+                packageName,
+                subId);
+    }
+
     @Nullable
     public static VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(
             Context context,
@@ -75,6 +94,7 @@
                         VisualVoicemailSmsFilterSettings.DEFAULT_DESTINATION_PORT))
                 .build();
     }
+
     private static SharedPreferences getSharedPreferences(Context context) {
         return PreferenceManager
                 .getDefaultSharedPreferences(context.createDeviceProtectedStorageContext());
diff --git a/src/com/android/phone/vvm/RemoteVvmTaskManager.java b/src/com/android/phone/vvm/RemoteVvmTaskManager.java
new file mode 100644
index 0000000..ce6fce6
--- /dev/null
+++ b/src/com/android/phone/vvm/RemoteVvmTaskManager.java
@@ -0,0 +1,251 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.VisualVoicemailService;
+import android.telephony.VisualVoicemailSms;
+
+import com.android.phone.Assert;
+import com.android.phone.vvm.omtp.VvmLog;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Service to manage tasks issued to the {@link VisualVoicemailService}. This service will bind to
+ * the default dialer on a visual voicemail event if it implements the VisualVoicemailService. The
+ * service will hold all resource for the VisualVoicemailService until {@link
+ * VisualVoicemailService.VisualVoicemailTask#finish()} has been called on all issued tasks.
+ *
+ * If the service is already running it will be reused for new events. The service will stop itself
+ * after all events are handled.
+ */
+public class RemoteVvmTaskManager extends Service {
+
+    private static final String TAG = "RemoteVvmTaskManager";
+
+    private static final String ACTION_START_CELL_SERVICE_CONNECTED =
+            "ACTION_START_CELL_SERVICE_CONNECTED";
+    private static final String ACTION_START_SMS_RECEIVED = "ACTION_START_SMS_RECEIVED";
+    private static final String ACTION_START_SIM_REMOVED = "ACTION_START_SIM_REMOVED";
+
+    // TODO(twyen): track task individually to have time outs.
+    private int mTaskReferenceCount;
+
+    private RemoteServiceConnection mConnection;
+
+    /**
+     * Handles incoming messages from the VisualVoicemailService.
+     */
+    private Messenger mMessenger;
+
+    public static void startCellServiceConnected(Context context,
+            PhoneAccountHandle phoneAccountHandle) {
+        Intent intent = new Intent(ACTION_START_CELL_SERVICE_CONNECTED, null, context,
+                RemoteVvmTaskManager.class);
+        intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+        context.startService(intent);
+    }
+
+    public static void startSmsReceived(Context context, VisualVoicemailSms sms) {
+        Intent intent = new Intent(ACTION_START_SMS_RECEIVED, null, context,
+                RemoteVvmTaskManager.class);
+        intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE,
+                sms.getPhoneAccountHandle());
+        intent.putExtra(VisualVoicemailService.DATA_SMS, sms);
+        context.startService(intent);
+    }
+
+    public static void startSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle) {
+        Intent intent = new Intent(ACTION_START_SIM_REMOVED, null, context,
+                RemoteVvmTaskManager.class);
+        intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+        context.startService(intent);
+    }
+
+    public static boolean hasRemoteService(Context context) {
+        return getRemotePackage(context) != null;
+    }
+
+    public static ComponentName getRemotePackage(Context context) {
+
+        ResolveInfo info = context.getPackageManager()
+                .resolveService(newBindIntent(context), PackageManager.MATCH_ALL);
+        if (info == null) {
+            return null;
+        }
+        return info.getComponentInfo().getComponentName();
+    }
+
+    @Override
+    public void onCreate() {
+        Assert.isMainThread();
+        mMessenger = new Messenger(new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                Assert.isMainThread();
+                switch (msg.what) {
+                    case VisualVoicemailService.MSG_TASK_ENDED:
+                        mTaskReferenceCount--;
+                        checkReference();
+                        break;
+                    default:
+                        VvmLog.wtf(TAG, "unexpected message " + msg.what);
+                }
+            }
+        });
+    }
+
+    @Override
+    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
+        Assert.isMainThread();
+        mTaskReferenceCount++;
+        switch (intent.getAction()) {
+            case ACTION_START_CELL_SERVICE_CONNECTED:
+                send(VisualVoicemailService.MSG_ON_CELL_SERVICE_CONNECTED, intent.getExtras());
+                break;
+            case ACTION_START_SMS_RECEIVED:
+                send(VisualVoicemailService.MSG_ON_SMS_RECEIVED, intent.getExtras());
+                break;
+            case ACTION_START_SIM_REMOVED:
+                send(VisualVoicemailService.MSG_ON_SIM_REMOVED, intent.getExtras());
+                break;
+            default:
+                Assert.fail("Unexpected action +" + intent.getAction());
+                break;
+        }
+        // Don't rerun service if processed is killed.
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    @Nullable
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private int getTaskId() {
+        // TODO(twyen): generate unique IDs. Reference counting is used now so it doesn't matter.
+        return 1;
+    }
+
+    /**
+     * Class for interacting with the main interface of the service.
+     */
+    private class RemoteServiceConnection implements ServiceConnection {
+
+        private final Queue<Message> mTaskQueue = new LinkedList<>();
+
+        private boolean mConnected;
+
+        /**
+         * A handler in the VisualVoicemailService
+         */
+        private Messenger mRemoteMessenger;
+
+        public void enqueue(Message message) {
+            mTaskQueue.add(message);
+            if (mConnected) {
+                runQueue();
+            }
+        }
+
+        public boolean isConnected() {
+            return mConnected;
+        }
+
+        public void onServiceConnected(ComponentName className,
+                IBinder service) {
+            mRemoteMessenger = new Messenger(service);
+            mConnected = true;
+            runQueue();
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mConnection = null;
+            mConnected = false;
+            mRemoteMessenger = null;
+            VvmLog.e(TAG, "Service disconnected, " + mTaskReferenceCount + " tasks dropped.");
+            mTaskReferenceCount = 0;
+            checkReference();
+        }
+
+        private void runQueue() {
+            Assert.isMainThread();
+            Message message = mTaskQueue.poll();
+            while (message != null) {
+                message.replyTo = mMessenger;
+                message.arg1 = getTaskId();
+
+                try {
+                    mRemoteMessenger.send(message);
+                } catch (RemoteException e) {
+                    VvmLog.e(TAG, "Error sending message to remote service", e);
+                }
+                message = mTaskQueue.poll();
+            }
+        }
+    }
+
+    private void send(int what, Bundle extras) {
+        Assert.isMainThread();
+        Message message = Message.obtain();
+        message.what = what;
+        message.setData(new Bundle(extras));
+        if (mConnection == null) {
+            mConnection = new RemoteServiceConnection();
+        }
+        mConnection.enqueue(message);
+
+        if (!mConnection.isConnected()) {
+            Intent intent = newBindIntent(this);
+            intent.setComponent(getRemotePackage(this));
+            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        }
+    }
+
+    private void checkReference() {
+        if (mTaskReferenceCount == 0) {
+            unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    private static Intent newBindIntent(Context context) {
+        Intent intent = new Intent();
+        intent.setAction(VisualVoicemailService.SERVICE_INTERFACE);
+        TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+        intent.setPackage(telecomManager.getDefaultDialerPackage());
+        return intent;
+    }
+}
diff --git a/src/com/android/phone/vvm/VvmSimStateTracker.java b/src/com/android/phone/vvm/VvmSimStateTracker.java
new file mode 100644
index 0000000..069111a
--- /dev/null
+++ b/src/com/android/phone/vvm/VvmSimStateTracker.java
@@ -0,0 +1,231 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemProperties;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.phone.PhoneUtils;
+import com.android.phone.vvm.omtp.VvmLog;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tracks the status of all inserted SIMs. Will notify {@link RemoteVvmTaskManager} of when a SIM
+ * connected to the service for the first time after it was inserted or the system booted, and when
+ * the SIM is removed. Losing cell signal or entering airplane mode will not cause the connected
+ * event to be triggered again. Reinserting the SIM will trigger the connected event. Changing the
+ * carrier config will also trigger the connected event. Events will be delayed until the device has
+ * been fully booted (and left FBE mode).
+ */
+public class VvmSimStateTracker extends BroadcastReceiver {
+
+    private static final String TAG = "VvmSimStateTracker";
+
+    /**
+     * Map to keep track of currently inserted SIMs. If the SIM hasn't been connected to the service
+     * before the value will be a {@link ServiceStateListener} that is still waiting for the
+     * connection. A value of {@code null} means the SIM has been connected to the service before.
+     */
+    private static Map<PhoneAccountHandle, ServiceStateListener> sListeners = new ArrayMap<>();
+
+    /**
+     * Accounts that has events before the device is booted. The events should be regenerated after
+     * the device has fully booted.
+     */
+    private static Set<PhoneAccountHandle> sPreBootHandles = new ArraySet<>();
+
+    /**
+     * Waits for the account to become {@link ServiceState#STATE_IN_SERVICE} and notify the
+     * connected event. Will unregister itself once the event has been triggered.
+     */
+    private class ServiceStateListener extends PhoneStateListener {
+
+        private final PhoneAccountHandle mPhoneAccountHandle;
+        private final Context mContext;
+
+        public ServiceStateListener(Context context, PhoneAccountHandle phoneAccountHandle) {
+            mContext = context;
+            mPhoneAccountHandle = phoneAccountHandle;
+        }
+
+        public void listen() {
+            getTelephonyManager(mContext, mPhoneAccountHandle)
+                    .listen(this, PhoneStateListener.LISTEN_SERVICE_STATE);
+        }
+
+        public void unlisten() {
+            getTelephonyManager(mContext, mPhoneAccountHandle)
+                    .listen(this, PhoneStateListener.LISTEN_NONE);
+            sListeners.put(mPhoneAccountHandle, null);
+        }
+
+        @Override
+        public void onServiceStateChanged(ServiceState serviceState) {
+            if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
+                VvmLog.i(TAG, "in service");
+                sendConnected(mContext, mPhoneAccountHandle);
+                unlisten();
+            }
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+        final String action = intent.getAction();
+        if (action == null) {
+            VvmLog.w(TAG, "Null action for intent.");
+            return;
+        }
+        VvmLog.i(TAG, action);
+        switch (action) {
+            case Intent.ACTION_BOOT_COMPLETED:
+                onBootCompleted(context);
+                break;
+            case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
+                if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(
+                        intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
+                    // onSimRemoved will scan all known accounts with isPhoneAccountActive() to find
+                    // which SIM is removed.
+                    // ACTION_SIM_STATE_CHANGED only provides subId which cannot be converted to a
+                    // PhoneAccountHandle when the SIM is absent.
+                    onSimRemoved(context);
+                }
+                break;
+            case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
+                int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                    VvmLog.i(TAG, "Received SIM change for invalid subscription id.");
+                    return;
+                }
+
+                PhoneAccountHandle phoneAccountHandle =
+                        PhoneAccountHandleConverter.fromSubId(subId);
+
+                if ("null".equals(phoneAccountHandle.getId())) {
+                    VvmLog.e(TAG,
+                            "null phone account handle ID, possible modem crash."
+                                    + " Ignoring carrier config changed event");
+                    return;
+                }
+                onCarrierConfigChanged(context, phoneAccountHandle);
+        }
+    }
+
+    private void onBootCompleted(Context context) {
+        for (PhoneAccountHandle phoneAccountHandle : sPreBootHandles) {
+            TelephonyManager telephonyManager = getTelephonyManager(context, phoneAccountHandle);
+            if (telephonyManager == null) {
+                continue;
+            }
+            if (telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
+                sListeners.put(phoneAccountHandle, null);
+                sendConnected(context, phoneAccountHandle);
+            } else {
+                listenToAccount(context, phoneAccountHandle);
+            }
+        }
+        sPreBootHandles.clear();
+    }
+
+    private void sendConnected(Context context, PhoneAccountHandle phoneAccountHandle) {
+        VvmLog.i(TAG, "Service connected on " + phoneAccountHandle);
+        RemoteVvmTaskManager.startCellServiceConnected(context, phoneAccountHandle);
+    }
+
+    private void onSimRemoved(Context context) {
+        SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+        if (!isBootCompleted()) {
+            for (PhoneAccountHandle phoneAccountHandle : sPreBootHandles) {
+                if (!PhoneUtils.isPhoneAccountActive(subscriptionManager, phoneAccountHandle)) {
+                    sPreBootHandles.remove(phoneAccountHandle);
+                }
+            }
+            return;
+        }
+        Set<PhoneAccountHandle> removeList = new ArraySet<>();
+        for (PhoneAccountHandle phoneAccountHandle : sListeners.keySet()) {
+            if (!PhoneUtils.isPhoneAccountActive(subscriptionManager, phoneAccountHandle)) {
+                removeList.add(phoneAccountHandle);
+                ServiceStateListener listener = sListeners.get(phoneAccountHandle);
+                if (listener != null) {
+                    listener.unlisten();
+                }
+                sendSimRemoved(context, phoneAccountHandle);
+            }
+        }
+
+        for (PhoneAccountHandle phoneAccountHandle : removeList) {
+            sListeners.remove(phoneAccountHandle);
+        }
+    }
+
+    private boolean isBootCompleted() {
+        return SystemProperties.getBoolean("sys.boot_completed", false);
+    }
+
+    private void sendSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle) {
+        VvmLog.i(TAG, "Sim removed on " + phoneAccountHandle);
+        RemoteVvmTaskManager.startSimRemoved(context, phoneAccountHandle);
+    }
+
+    private void onCarrierConfigChanged(Context context, PhoneAccountHandle phoneAccountHandle) {
+        if (!isBootCompleted()) {
+            sPreBootHandles.add(phoneAccountHandle);
+            return;
+        }
+        if (getTelephonyManager(context, phoneAccountHandle).getServiceState().getState()
+                == ServiceState.STATE_IN_SERVICE) {
+            sendConnected(context, phoneAccountHandle);
+            sListeners.put(phoneAccountHandle, null);
+        } else {
+            listenToAccount(context, phoneAccountHandle);
+        }
+    }
+
+    private void listenToAccount(Context context, PhoneAccountHandle phoneAccountHandle) {
+        ServiceStateListener listener = new ServiceStateListener(context, phoneAccountHandle);
+        listener.listen();
+        sListeners.put(phoneAccountHandle, listener);
+    }
+
+    @Nullable
+    private static TelephonyManager getTelephonyManager(Context context,
+            PhoneAccountHandle phoneAccountHandle) {
+        return context.getSystemService(TelephonyManager.class)
+                .createForPhoneAccountHandle(phoneAccountHandle);
+    }
+}
diff --git a/src/com/android/phone/vvm/VvmSmsReceiver.java b/src/com/android/phone/vvm/VvmSmsReceiver.java
new file mode 100644
index 0000000..91e7933
--- /dev/null
+++ b/src/com/android/phone/vvm/VvmSmsReceiver.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.VoicemailContract;
+import android.telephony.VisualVoicemailSms;
+
+import com.android.phone.vvm.omtp.VvmLog;
+import com.android.phone.vvm.omtp.sms.OmtpMessageReceiver;
+
+/**
+ * Receives the SMS filtered by {@link com.android.internal.telephony.VisualVoicemailSmsFilter} and
+ * redirect it to the visual voicemail client. The redirection is required to let telephony service
+ * handle tasks with {@link RemoteVvmTaskManager}
+ */
+public class VvmSmsReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "VvmSmsReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        VisualVoicemailSms sms = intent.getExtras()
+                .getParcelable(VoicemailContract.EXTRA_VOICEMAIL_SMS);
+
+        if (sms.getPhoneAccountHandle() == null) {
+            // This should never happen
+            VvmLog.e(TAG, "Received message for null phone account");
+            return;
+        }
+
+        if (RemoteVvmTaskManager.hasRemoteService(context)) {
+            VvmLog.i(TAG, "Sending SMS received event to remote service");
+            RemoteVvmTaskManager.startSmsReceived(context, sms);
+            return;
+        }
+
+        OmtpMessageReceiver
+                .onReceive(context, sms);
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 5b201aa..e305879 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -31,6 +31,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
+import com.android.phone.vvm.RemoteVvmTaskManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
@@ -54,6 +55,10 @@
             return;
         }
 
+        if (RemoteVvmTaskManager.hasRemoteService(context)) {
+            return;
+        }
+
         switch (action) {
             case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
                 if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(
@@ -90,6 +95,7 @@
                 }
 
                 VvmLog.d(TAG, "Carrier config changed");
+
                 if (UserManager.get(context).isUserUnlocked() && !isCryptKeeperMode()) {
                     processSubId(context, subId);
                 } else {
diff --git a/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java b/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java
index 9809c64..21cce31 100644
--- a/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java
+++ b/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java
@@ -22,6 +22,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.SubscriptionManager;
+import com.android.phone.vvm.RemoteVvmTaskManager;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 /**
@@ -45,6 +46,10 @@
             return;
         }
 
+        if (RemoteVvmTaskManager.hasRemoteService(context)) {
+            return;
+        }
+
         VvmLog.v(TAG, "processing subId list");
         for (PhoneAccountHandle handle : TelecomManager.from(context)
                 .getCallCapablePhoneAccounts()) {
diff --git a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
index 7e2472b..31c173d 100644
--- a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
@@ -20,9 +20,9 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
-
 import com.android.phone.PhoneUtils;
 import com.android.phone.VoicemailStatus;
+import com.android.phone.vvm.RemoteVvmTaskManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.SyncTask;
@@ -55,6 +55,10 @@
             return;
         }
 
+        if (RemoteVvmTaskManager.hasRemoteService(mContext)) {
+            return;
+        }
+
         int state = serviceState.getState();
         if (state == mPreviousState || (state != ServiceState.STATE_IN_SERVICE
                 && mPreviousState != ServiceState.STATE_IN_SERVICE)) {
@@ -67,6 +71,7 @@
         OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext, subId);
 
         if (state == ServiceState.STATE_IN_SERVICE) {
+
             VoicemailStatusQueryHelper voicemailStatusQueryHelper =
                     new VoicemailStatusQueryHelper(mContext);
             if (voicemailStatusQueryHelper.isVoicemailSourceConfigured(mPhoneAccount)) {
diff --git a/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java b/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java
index ba5bd70..6a5f349 100644
--- a/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java
+++ b/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java
@@ -17,10 +17,8 @@
 package com.android.phone.vvm.omtp.sms;
 
 import android.content.Context;
-import android.content.Intent;
 import android.os.Bundle;
-import android.provider.VoicemailContract;
-import android.telecom.PhoneAccountHandle;
+import android.telephony.VisualVoicemailSms;
 
 import com.android.internal.telephony.Phone;
 import com.android.phone.PhoneUtils;
@@ -37,15 +35,14 @@
 
     private static final String TAG = "LegacyModeSmsHandler";
 
-    public static void handle(Context context, Intent intent, PhoneAccountHandle handle) {
+    public static void handle(Context context, VisualVoicemailSms sms) {
         VvmLog.v(TAG, "processing VVM SMS on legacy mode");
-        String eventType = intent.getExtras()
-                .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
-        Bundle data = intent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS);
+        String eventType = sms.getPrefix();
+        Bundle data = sms.getFields();
 
         if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
             SyncMessage message = new SyncMessage(data);
-            VvmLog.v(TAG, "Received SYNC sms for " + handle.getId() +
+            VvmLog.v(TAG, "Received SYNC sms for " + sms.getPhoneAccountHandle() +
                     " with event " + message.getSyncTriggerEvent());
 
             switch (message.getSyncTriggerEvent()) {
@@ -56,7 +53,8 @@
                     // For some carriers new message count could be set to 0 even if there are still
                     // unread messages, to clear the message waiting indicator.
                     VvmLog.v(TAG, "updating MWI");
-                    Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(handle);
+                    Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(
+                            sms.getPhoneAccountHandle());
                     // Setting voicemail message count to non-zero will show the telephony voicemail
                     // notification, and zero will clear it.
                     phone.setVoiceMessageCount(message.getNewMessageCount());
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 397caf8..d42e70e 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -15,7 +15,6 @@
  */
 package com.android.phone.vvm.omtp.sms;
 
-import android.content.BroadcastReceiver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
@@ -25,6 +24,8 @@
 import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
+import android.telephony.VisualVoicemailSms;
+
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.ActivationTask;
 import com.android.phone.vvm.omtp.OmtpConstants;
@@ -40,46 +41,35 @@
 /**
  * Receive SMS messages and send for processing by the OMTP visual voicemail source.
  */
-public class OmtpMessageReceiver extends BroadcastReceiver {
+public class OmtpMessageReceiver {
 
     private static final String TAG = "OmtpMessageReceiver";
 
-    private Context mContext;
+    public static void onReceive(Context context, VisualVoicemailSms sms) {
 
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        mContext = context;
-        int subId = intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID);
-        PhoneAccountHandle phone = PhoneAccountHandleConverter.fromSubId(subId);
-
-        if (phone == null) {
-            // This should never happen
-            VvmLog.i(TAG, "Received message for null phone account on subId " + subId);
-            return;
-        }
-
+        PhoneAccountHandle phone = sms.getPhoneAccountHandle();
+        int subId = PhoneAccountHandleConverter.toSubId(phone);
         if (!UserManager.get(context).isUserUnlocked()) {
             VvmLog.i(TAG, "Received message on locked device");
             // LegacyModeSmsHandler can handle new message notifications without storage access
-            LegacyModeSmsHandler.handle(context, intent, phone);
+            LegacyModeSmsHandler.handle(context, sms);
             // A full sync will happen after the device is unlocked, so nothing else need to be
             // done.
             return;
         }
 
-        OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext, subId);
-        if (!VisualVoicemailSettingsUtil.isEnabled(mContext, phone)) {
+        OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, subId);
+        if (!VisualVoicemailSettingsUtil.isEnabled(context, phone)) {
             if (helper.isLegacyModeEnabled()) {
-                LegacyModeSmsHandler.handle(context, intent, phone);
+                LegacyModeSmsHandler.handle(context, sms);
             } else {
                 VvmLog.i(TAG, "Received vvm message for disabled vvm source.");
             }
             return;
         }
 
-        String eventType = intent.getExtras()
-                .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
-        Bundle data = intent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS);
+        String eventType = sms.getPrefix();
+        Bundle data = sms.getFields();
 
         if (eventType == null || data == null) {
             VvmLog.e(TAG, "Unparsable VVM SMS received, ignoring");
@@ -91,7 +81,7 @@
 
             VvmLog.v(TAG, "Received SYNC sms for " + subId +
                     " with event " + message.getSyncTriggerEvent());
-            processSync(phone, message);
+            processSync(context, phone, message);
         } else if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
             VvmLog.v(TAG, "Received Status sms for " + subId);
             // If the STATUS SMS is initiated by ActivationTask the TaskSchedulerService will reject
@@ -122,7 +112,8 @@
      *
      * @param message The sync message to extract data from.
      */
-    private void processSync(PhoneAccountHandle phone, SyncMessage message) {
+    private static void processSync(Context context, PhoneAccountHandle phone,
+            SyncMessage message) {
         Intent serviceIntent = null;
         switch (message.getSyncTriggerEvent()) {
             case OmtpConstants.NEW_MESSAGE:
@@ -137,18 +128,18 @@
                         .setPhoneAccount(phone)
                         .setSourceData(message.getId())
                         .setDuration(message.getLength())
-                        .setSourcePackage(mContext.getPackageName());
+                        .setSourcePackage(context.getPackageName());
                 Voicemail voicemail = builder.build();
 
-                VoicemailsQueryHelper queryHelper = new VoicemailsQueryHelper(mContext);
+                VoicemailsQueryHelper queryHelper = new VoicemailsQueryHelper(context);
                 if (queryHelper.isVoicemailUnique(voicemail)) {
-                    Uri uri = VoicemailContract.Voicemails.insert(mContext, voicemail);
+                    Uri uri = VoicemailContract.Voicemails.insert(context, voicemail);
                     voicemail = builder.setId(ContentUris.parseId(uri)).setUri(uri).build();
-                    SyncOneTask.start(mContext, phone, voicemail);
+                    SyncOneTask.start(context, phone, voicemail);
                 }
                 break;
             case OmtpConstants.MAILBOX_UPDATE:
-                SyncTask.start(mContext, phone, OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY);
+                SyncTask.start(context, phone, OmtpVvmSyncService.SYNC_DOWNLOAD_ONLY);
                 break;
             case OmtpConstants.GREETINGS_UPDATE:
                 // Not implemented in V1
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpProvisioningService.java b/src/com/android/phone/vvm/omtp/sms/OmtpProvisioningService.java
deleted file mode 100644
index 154eeeb..0000000
--- a/src/com/android/phone/vvm/omtp/sms/OmtpProvisioningService.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.sms;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.provider.VoicemailContract;
-import android.telecom.PhoneAccountHandle;
-
-import com.android.phone.PhoneUtils;
-import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
-import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
-
-/**
- * Performs visual voicemail provisioning in background thread. Not exported.
- */
-public class OmtpProvisioningService extends IntentService {
-
-    public OmtpProvisioningService() {
-        super("OmtpProvisioningService");
-    }
-
-    /**
-     * Create an intent to start OmtpProvisioningService from a {@link
-     * VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED} intent.
-     */
-    public static Intent getProvisionIntent(Context context, Intent messageIntent) {
-        Intent serviceIntent = new Intent(context, OmtpProvisioningService.class);
-
-        serviceIntent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID,
-                messageIntent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID));
-        serviceIntent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS,
-                messageIntent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS));
-
-        return serviceIntent;
-    }
-
-    @Override
-    public void onHandleIntent(Intent intent) {
-        int subId = intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID);
-        PhoneAccountHandle phone = PhoneAccountHandleConverter.fromSubId(subId);
-
-        Bundle data = intent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS);
-
-        StatusMessage message = new StatusMessage(data);
-        startProvisioning(phone, message, data);
-    }
-
-    private void startProvisioning(PhoneAccountHandle phone, StatusMessage message, Bundle data) {
-        OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(this,
-                PhoneUtils.getSubIdForPhoneAccountHandle(phone));
-
-    }
-}
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java b/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
index 69e4f5f..adaa2f4 100644
--- a/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
+++ b/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
@@ -27,13 +27,16 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.provider.VoicemailContract;
+import android.telecom.PhoneAccountHandle;
 import android.telephony.SmsManager;
+import android.telephony.VisualVoicemailSms;
 
 import com.android.phone.Assert;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -107,16 +110,17 @@
             return;
         }
 
-        int subId = intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID);
-
+        VisualVoicemailSms sms = intent.getExtras()
+                .getParcelable(VoicemailContract.EXTRA_VOICEMAIL_SMS);
+        PhoneAccountHandle phoneAccountHandle = sms.getPhoneAccountHandle();
+        int subId = PhoneAccountHandleConverter.toSubId(phoneAccountHandle);
         if (mSubId != subId) {
             return;
         }
-        String eventType = intent.getExtras()
-                .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
+        String eventType = sms.getPrefix();
 
         if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
-            mFuture.complete(intent.getBundleExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS));
+            mFuture.complete(sms.getFields());
             return;
         }
 
@@ -132,7 +136,7 @@
             return;
         }
         Bundle translatedBundle = protocol.translateStatusSmsBundle(helper, eventType,
-                intent.getBundleExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS));
+                sms.getFields());
 
         if (translatedBundle != null) {
             VvmLog.i(TAG, "Translated to STATUS SMS");