Merge "Implementation of business logic for voicemail status."
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 806b33f..547f3e7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1619,4 +1619,26 @@
 
     <!-- The header in the call log used to identify items that have been already consumed [CHAR LIMIT=10] -->
     <string name="call_log_old_header">Older</string>
+
+    <!--  Voicemail status message shown at the top of call log to notify the user that no new
+      voicemails are currently available. This can happen when both notification as well as data
+      connection to the voicemail server is lost. [CHAR LIMIT=64] -->
+    <string name="voicemail_status_voicemail_not_available">Cannot connect to voicemail server.</string>
+    <!--  Voicemail status message shown at the top of call log to notify the user that there is no
+      data connection to the voicemail server, but there are new voicemails waiting on the server.
+      [CHAR LIMIT=64] -->
+    <string name="voicemail_status_messages_waiting">Cannot connect to voicemail server. New voicemails waiting.</string>
+   <!--  Voicemail status message shown at the top of call log to invite the user to configure
+      visual voicemail. [CHAR LIMIT=64] -->
+    <string name="voicemail_status_configure_voicemail">Configure your voicemail.</string>
+   <!--  Voicemail status message shown at the top of call details screen to notify the user that
+      the audio of this voicemail is not available. [CHAR LIMIT=64] -->
+    <string name="voicemail_status_audio_not_available">Audio not available.</string>
+
+   <!--  User action prompt shown next to a voicemail status message to let the user configure
+   visual voicemail. [CHAR LIMIT=20] -->
+    <string name="voicemail_status_action_configure">Configure</string>
+   <!--  User action prompt shown next to a voicemail status message to let the user call voicemail
+   server directly to listen to the voicemails. [CHAR LIMIT=20] -->
+    <string name="voicemail_status_action_call_server">Call voicemail</string>
 </resources>
diff --git a/src/com/android/contacts/calllog/VoicemailStatusHelper.java b/src/com/android/contacts/calllog/VoicemailStatusHelper.java
new file mode 100644
index 0000000..d4a3965
--- /dev/null
+++ b/src/com/android/contacts/calllog/VoicemailStatusHelper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 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.contacts.calllog;
+
+import android.net.Uri;
+
+import java.util.List;
+
+/**
+ * Interface used by the call log UI to determine what user message, if any, related to voicemail
+ * source status needs to be shown. The messages are returned in the order of importance.
+ * <p>
+ * The implementation of this interface interacts with the voicemail content provider to fetch
+ * statuses of all the registered voicemail sources and determines if any status message needs to
+ * be shown. The user of this interface must observe/listen to provider changes and invoke
+ * this class to check if any message needs to be shown.
+ */
+public interface VoicemailStatusHelper {
+    public class Message {
+        /** Package of the source on behalf of which this message has to be shown.*/
+        public final String sourcePackage;
+        /** The string resource id of the status message that should be shown. */
+        public final int statusMessageId;
+        /** The string resource id of the action message that should be shown. */
+        public final int actionMessageId;
+        /** URI for the corrective action, where applicable. Null if no action URI is available. */
+        public final Uri actionUri;
+        public Message(String sourcePackage, int statusMessageId, int actionMessageId,
+                Uri actionUri) {
+            this.sourcePackage = sourcePackage;
+            this.statusMessageId = statusMessageId;
+            this.actionMessageId = actionMessageId;
+            this.actionUri = actionUri;
+        }
+    }
+
+    /**
+     * Returns a list of messages, in the order or priority that should be shown to the user. An
+     * empty list is returned if no message needs to be shown.
+     */
+    public List<Message> getStatusMessages();
+}
diff --git a/src/com/android/contacts/calllog/VoicemailStatusHelperImpl.java b/src/com/android/contacts/calllog/VoicemailStatusHelperImpl.java
new file mode 100644
index 0000000..9738fd7
--- /dev/null
+++ b/src/com/android/contacts/calllog/VoicemailStatusHelperImpl.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2011 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.contacts.calllog;
+
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_OK;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
+
+import com.android.common.io.MoreCloseables;
+import com.android.contacts.R;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.VoicemailContract.Status;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/** Implementation of {@link VoicemailStatusHelper}. */
+public class VoicemailStatusHelperImpl implements VoicemailStatusHelper {
+    private static final int SOURCE_PACKAGE_INDEX = 0;
+    private static final int CONFIGURATION_STATE_INDEX = 1;
+    private static final int DATA_CHANNEL_STATE_INDEX = 2;
+    private static final int NOTIFICATION_CHANNEL_STATE_INDEX = 3;
+    private static final int SETTINGS_URI_INDEX = 4;
+    private static final int VOICEMAIL_ACCESS_URI_INDEX = 5;
+    private static final int NUM_COLUMNS = 6;
+    private static final String[] PROJECTION = new String[NUM_COLUMNS];
+    static {
+        PROJECTION[SOURCE_PACKAGE_INDEX] = Status.SOURCE_PACKAGE;
+        PROJECTION[CONFIGURATION_STATE_INDEX] = Status.CONFIGURATION_STATE;
+        PROJECTION[DATA_CHANNEL_STATE_INDEX] = Status.DATA_CHANNEL_STATE;
+        PROJECTION[NOTIFICATION_CHANNEL_STATE_INDEX] = Status.NOTIFICATION_CHANNEL_STATE;
+        PROJECTION[SETTINGS_URI_INDEX] = Status.SETTINGS_URI;
+        PROJECTION[VOICEMAIL_ACCESS_URI_INDEX] = Status.VOICEMAIL_ACCESS_URI;
+    }
+
+    /** Possible user actions. */
+    public static enum Action {
+        NONE(-1),
+        CALL_VOICEMAIL(R.string.voicemail_status_action_call_server),
+        CONFIGURE_VOICEMAIL(R.string.voicemail_status_action_configure);
+
+        private final int mMessageId;
+        private Action(int messageId) {
+            mMessageId = messageId;
+        }
+
+        public int getMessageId() {
+            return mMessageId;
+        }
+    }
+
+    /**
+     * Overall state of the source status. Each state is associated with the corresponding display
+     * string and the corrective action. The states are also assigned a relative priority which is
+     * used to order the messages from different sources.
+     */
+    private static enum OverallState {
+        // TODO: Add separate string for call details and call log pages for the states that needs
+        // to be shown in both.
+        /** Both notification and data channel are not working. */
+        NO_CONNECTION(0, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available),
+        /** Notifications working, but data channel is not working. Audio cannot be downloaded. */
+        NO_DATA(1, Action.CALL_VOICEMAIL, R.string.voicemail_status_audio_not_available),
+        /** Messages are known to be waiting but data channel is not working. */
+        MESSAGE_WAITING(2, Action.CALL_VOICEMAIL, R.string.voicemail_status_messages_waiting),
+        /** Notification channel not working, but data channel is. */
+        NO_NOTIFICATIONS(3, Action.CALL_VOICEMAIL,
+                R.string.voicemail_status_voicemail_not_available),
+        /** Invite user to set up voicemail. */
+        INVITE_FOR_CONFIGURATION(4, Action.CONFIGURE_VOICEMAIL,
+                R.string.voicemail_status_configure_voicemail),
+        /**
+         * No detailed notifications, but data channel is working.
+         * This is normal mode of operation for certain sources. No action needed.
+         */
+        NO_DETAILED_NOTIFICATION(5, Action.NONE, -1),
+        /** Visual voicemail not yet set up. No local action needed. */
+        NOT_CONFIGURED(6, Action.NONE, -1),
+        /** Everything is OK. */
+        OK(7, Action.NONE, -1),
+        /** If one or more state value set by the source is not valid. */
+        INVALID(8, Action.NONE, -1);
+
+        private final int mPriority;
+        private final Action mAction;
+        private final int mMessageId;
+
+        private OverallState(int priority, Action action, int messageId) {
+            mPriority = priority;
+            mAction = action;
+            mMessageId = messageId;
+        }
+
+        public Action getAction() {
+            return mAction;
+        }
+
+        public int getPriority() {
+            return mPriority;
+        }
+
+        public int getMessageId() {
+            return mMessageId;
+        }
+    }
+
+    private final ContentResolver mContentResolver;
+
+    public VoicemailStatusHelperImpl(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    /** A wrapper on {@link Message} which additionally stores the priority of the message. */
+    private static class MessageWrapper {
+        private final Message mMessage;
+        private final int mPriority;
+
+        public MessageWrapper(Message message, int priority) {
+            mMessage = message;
+            mPriority = priority;
+        }
+    }
+
+    @Override
+    public List<Message> getStatusMessages() {
+        Cursor cursor = null;
+        try {
+            cursor = mContentResolver.query(Status.CONTENT_URI, PROJECTION, null, null, null);
+            List<MessageWrapper> messages =
+                    new ArrayList<VoicemailStatusHelperImpl.MessageWrapper>();
+            while(cursor.moveToNext()) {
+                MessageWrapper message = getMessageForStatusEntry(cursor);
+                if (message != null) {
+                    messages.add(message);
+                }
+            }
+            // Finally reorder the messages by their priority.
+            return reorderMessages(messages);
+        } finally {
+            MoreCloseables.closeQuietly(cursor);
+        }
+    }
+
+    private List<Message> reorderMessages(List<MessageWrapper> messageWrappers) {
+        Collections.sort(messageWrappers, new Comparator<MessageWrapper>() {
+            @Override
+            public int compare(MessageWrapper msg1, MessageWrapper msg2) {
+                return msg1.mPriority - msg2.mPriority;
+            }
+        });
+        List<Message> reorderMessages = new ArrayList<VoicemailStatusHelper.Message>();
+        // Copy the ordered message objects into the final list.
+        for (MessageWrapper messageWrapper : messageWrappers) {
+            reorderMessages.add(messageWrapper.mMessage);
+        }
+        return reorderMessages;
+    }
+
+    /**
+     * Returns the message for the status entry pointed to by the cursor.
+     */
+    private MessageWrapper getMessageForStatusEntry(Cursor cursor) {
+        final String sourcePackage = cursor.getString(SOURCE_PACKAGE_INDEX);
+        if (sourcePackage == null) {
+            return null;
+        }
+        final OverallState overallState = getOverallState(cursor.getInt(CONFIGURATION_STATE_INDEX),
+                cursor.getInt(DATA_CHANNEL_STATE_INDEX),
+                cursor.getInt(NOTIFICATION_CHANNEL_STATE_INDEX));
+        final Action action = overallState.getAction();
+
+        // No source package or no action, means no message shown.
+        if (action == Action.NONE) {
+            return null;
+        }
+
+        Uri actionUri = null;
+        if (action == Action.CALL_VOICEMAIL) {
+            actionUri = Uri.parse(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
+        } else if (action == Action.CONFIGURE_VOICEMAIL) {
+            actionUri = Uri.parse(cursor.getString(SETTINGS_URI_INDEX));
+        }
+        return new MessageWrapper(
+                new Message(sourcePackage, overallState.getMessageId(), action.getMessageId(),
+                        actionUri),
+                overallState.getPriority());
+    }
+
+    private OverallState getOverallState(int configurationState, int dataChannelState,
+            int notificationChannelState) {
+        if (configurationState == CONFIGURATION_STATE_OK) {
+            // Voicemail is configured. Let's see how is the data channel.
+            if (dataChannelState == DATA_CHANNEL_STATE_OK) {
+                // Data channel is fine. What about notification channel?
+                if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
+                    return OverallState.OK;
+                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
+                    return OverallState.NO_DETAILED_NOTIFICATION;
+                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
+                    return OverallState.NO_NOTIFICATIONS;
+                }
+            } else if (dataChannelState == DATA_CHANNEL_STATE_NO_CONNECTION) {
+                // Data channel is not working. What about notification channel?
+                if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
+                    return OverallState.NO_DATA;
+                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
+                    return OverallState.MESSAGE_WAITING;
+                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
+                    return OverallState.NO_CONNECTION;
+                }
+            }
+        } else if (configurationState == CONFIGURATION_STATE_CAN_BE_CONFIGURED) {
+            // Voicemail not configured. data/notification channel states are irrelevant.
+            return OverallState.INVITE_FOR_CONFIGURATION;
+        } else if (configurationState == Status.CONFIGURATION_STATE_NOT_CONFIGURED) {
+            // Voicemail not configured. data/notification channel states are irrelevant.
+            return OverallState.NOT_CONFIGURED;
+        }
+        // Will reach here only if the source has set an invalid value.
+        return OverallState.INVALID;
+    }
+}
diff --git a/tests/src/com/android/contacts/calllog/VoicemailStatusHelperImplTest.java b/tests/src/com/android/contacts/calllog/VoicemailStatusHelperImplTest.java
new file mode 100644
index 0000000..d577d4c
--- /dev/null
+++ b/tests/src/com/android/contacts/calllog/VoicemailStatusHelperImplTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2011 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.contacts.calllog;
+
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE;
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
+import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_NOT_CONFIGURED;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
+import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
+import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
+
+import com.android.contacts.R;
+import com.android.contacts.calllog.VoicemailStatusHelper.Message;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.VoicemailContract.Status;
+import android.test.AndroidTestCase;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link VoicemailStatusHelperImpl}.
+ */
+public class VoicemailStatusHelperImplTest extends AndroidTestCase {
+    private static final String TEST_PACKAGE_1 = "com.test.package1";
+    private static final String TEST_PACKAGE_2 = "com.test.package2";
+
+    private static final Uri TEST_SETTINGS_URI = Uri.parse("http://www.visual.voicemail.setup");
+    private static final Uri TEST_VOICEMAIL_URI = Uri.parse("tel:901");
+
+    private static final int ACTION_MSG_CALL_VOICEMAIL = R.string.voicemail_status_action_call_server;
+    private static final int ACTION_MSG_CONFIGURE = R.string.voicemail_status_action_configure;
+
+    private static final int STATUS_MSG_VOICEMAIL_NOT_AVAILABLE =
+            R.string.voicemail_status_voicemail_not_available;
+    private static final int STATUS_MSG_AUDIO_NOT_AVAIALABLE =
+            R.string.voicemail_status_audio_not_available;
+    private static final int STATUS_MSG_MESSAGE_WAITING = R.string.voicemail_status_messages_waiting;
+    private static final int STATUS_MSG_INVITE_FOR_CONFIGURATION =
+            R.string.voicemail_status_configure_voicemail;
+
+    // The packages whose status entries have been added during the test and needs to be cleaned
+    // up in teardown.
+    private Set<String> mPackagesToCleanup = new HashSet<String>();
+    // Object under test.
+    private VoicemailStatusHelper mStatusHelper;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mStatusHelper = new VoicemailStatusHelperImpl(getContentResolver());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (String sourcePackage : mPackagesToCleanup) {
+            deleteEntryForPackage(sourcePackage);
+        }
+        mPackagesToCleanup.clear();
+        // Set member variables to null so that they are garbage collected across different runs
+        // of the tests.
+        mStatusHelper = null;
+        mPackagesToCleanup = null;
+        super.tearDown();
+    }
+
+    public void testNoStatusEntries() {
+        assertEquals(0, mStatusHelper.getStatusMessages().size());
+    }
+
+    public void testAllOK() {
+        insertEntryForPackage(TEST_PACKAGE_1, getAllOkStatusValues());
+        insertEntryForPackage(TEST_PACKAGE_2, getAllOkStatusValues());
+        assertEquals(0, mStatusHelper.getStatusMessages().size());
+    }
+
+    public void testNotAllOKForOnePackage() {
+        insertEntryForPackage(TEST_PACKAGE_1, getAllOkStatusValues());
+        insertEntryForPackage(TEST_PACKAGE_2, getAllOkStatusValues());
+
+        ContentValues values = new ContentValues();
+        // No notification + good data channel - for now same as no connection.
+        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkExpectedMessage(TEST_PACKAGE_2, values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
+                ACTION_MSG_CALL_VOICEMAIL);
+
+        // Message waiting + good data channel - no action.
+        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
+        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkNoMessages(TEST_PACKAGE_2, values);
+
+        // Notification OK + no data channel - call voicemail/no audio.
+        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_OK);
+        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkExpectedMessage(TEST_PACKAGE_2, values, STATUS_MSG_AUDIO_NOT_AVAIALABLE,
+                ACTION_MSG_CALL_VOICEMAIL);
+
+        // No notification + no data channel - call voicemail/no connection.
+        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkExpectedMessage(TEST_PACKAGE_2, values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
+                ACTION_MSG_CALL_VOICEMAIL);
+
+        // Message waiting + no data channel - call voicemail.
+        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
+        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkExpectedMessage(TEST_PACKAGE_2, values, STATUS_MSG_MESSAGE_WAITING,
+                ACTION_MSG_CALL_VOICEMAIL);
+
+        // Not configured. No user action, so no message.
+        values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_NOT_CONFIGURED);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkNoMessages(TEST_PACKAGE_2, values);
+
+        // Can be configured - invite user for configure voicemail.
+        values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_CAN_BE_CONFIGURED);
+        updateEntryForPackage(TEST_PACKAGE_2, values);
+        checkExpectedMessage(TEST_PACKAGE_2, values, STATUS_MSG_INVITE_FOR_CONFIGURATION,
+                ACTION_MSG_CONFIGURE, TEST_SETTINGS_URI);
+    }
+
+    // Test that priority of messages are handled well.
+    public void testMessageOrdering() {
+        insertEntryForPackage(TEST_PACKAGE_1, getAllOkStatusValues());
+        insertEntryForPackage(TEST_PACKAGE_2, getAllOkStatusValues());
+
+        final ContentValues valuesNoNotificationGoodDataChannel = new ContentValues();
+        valuesNoNotificationGoodDataChannel.put(NOTIFICATION_CHANNEL_STATE,
+                NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+        valuesNoNotificationGoodDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
+
+        final ContentValues valuesNoNotificationNoDataChannel = new ContentValues();
+        valuesNoNotificationNoDataChannel.put(NOTIFICATION_CHANNEL_STATE,
+                NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+        valuesNoNotificationNoDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
+
+        // Package1 with valuesNoNotificationGoodDataChannel and
+        // package2 with  valuesNoNotificationNoDataChannel. Package2 should be above.
+        updateEntryForPackage(TEST_PACKAGE_1, valuesNoNotificationGoodDataChannel);
+        updateEntryForPackage(TEST_PACKAGE_2, valuesNoNotificationNoDataChannel);
+        List<Message> messages = mStatusHelper.getStatusMessages();
+        assertEquals(2, messages.size());
+        assertEquals(TEST_PACKAGE_1, messages.get(1).sourcePackage);
+        assertEquals(TEST_PACKAGE_2, messages.get(0).sourcePackage);
+
+        // Now reverse the values - ordering should be reversed as well.
+        updateEntryForPackage(TEST_PACKAGE_1, valuesNoNotificationNoDataChannel);
+        updateEntryForPackage(TEST_PACKAGE_2, valuesNoNotificationGoodDataChannel);
+        messages = mStatusHelper.getStatusMessages();
+        assertEquals(2, messages.size());
+        assertEquals(TEST_PACKAGE_1, messages.get(0).sourcePackage);
+        assertEquals(TEST_PACKAGE_2, messages.get(1).sourcePackage);
+    }
+
+    /** Checks for the expected message with given values and actionUri as TEST_VOICEMAIL_URI. */
+    private void checkExpectedMessage(String sourcePackage, ContentValues values,
+            int expectedStatusMsg, int expectedActionMsg) {
+        checkExpectedMessage(sourcePackage, values, expectedStatusMsg, expectedActionMsg,
+                TEST_VOICEMAIL_URI);
+    }
+
+    private void checkExpectedMessage(String sourcePackage, ContentValues values,
+            int expectedStatusMsg, int expectedActionMsg, Uri expectedUri) {
+        List<Message> messages = mStatusHelper.getStatusMessages();
+        assertEquals(1, messages.size());
+        checkMessageMatches(messages.get(0), sourcePackage, expectedStatusMsg, expectedActionMsg,
+                expectedUri);
+    }
+
+    private void checkMessageMatches(Message message, String expectedSourcePackage,
+            int expectedStatusMsg, int expectedActionMsg, Uri expectedUri) {
+        assertEquals(expectedSourcePackage, message.sourcePackage);
+        assertEquals(expectedStatusMsg, message.statusMessageId);
+        assertEquals(expectedActionMsg, message.actionMessageId);
+        if (expectedUri == null) {
+            assertNull(message.actionUri);
+        } else {
+            assertEquals(expectedUri, message.actionUri);
+        }
+    }
+
+    private void checkNoMessages(String sourcePackage, ContentValues values) {
+        assertEquals(1, updateEntryForPackage(sourcePackage, values));
+        List<Message> messages = mStatusHelper.getStatusMessages();
+        assertEquals(0, messages.size());
+    }
+
+    private ContentValues getAllOkStatusValues() {
+        ContentValues values = new ContentValues();
+        values.put(Status.SETTINGS_URI, TEST_SETTINGS_URI.toString());
+        values.put(Status.VOICEMAIL_ACCESS_URI, TEST_VOICEMAIL_URI.toString());
+        values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK);
+        values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK);
+        values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK);
+        return values;
+    }
+
+    private void insertEntryForPackage(String sourcePackage, ContentValues values) {
+        // If insertion fails then try update as the record might already exist.
+        if (getContentResolver().insert(Status.buildSourceUri(sourcePackage), values) == null) {
+            updateEntryForPackage(sourcePackage, values);
+        }
+        mPackagesToCleanup.add(sourcePackage);
+    }
+
+    private void deleteEntryForPackage(String sourcePackage) {
+        getContentResolver().delete(Status.buildSourceUri(sourcePackage), null, null);
+    }
+
+    private int updateEntryForPackage(String sourcePackage, ContentValues values) {
+        return getContentResolver().update(
+                Status.buildSourceUri(sourcePackage), values, null, null);
+    }
+
+    private ContentResolver getContentResolver() {
+        return getContext().getContentResolver();
+    }
+}