1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.contacts.voicemail;
18
19import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
20import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_OK;
21import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
22import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
23import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
24import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
25import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
26
27import android.database.Cursor;
28import android.net.Uri;
29import android.provider.VoicemailContract.Status;
30
31import com.android.contacts.R;
32import com.android.contacts.util.UriUtils;
33
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.Comparator;
37import java.util.List;
38
39/** Implementation of {@link VoicemailStatusHelper}. */
40public class VoicemailStatusHelperImpl implements VoicemailStatusHelper {
41    private static final int SOURCE_PACKAGE_INDEX = 0;
42    private static final int CONFIGURATION_STATE_INDEX = 1;
43    private static final int DATA_CHANNEL_STATE_INDEX = 2;
44    private static final int NOTIFICATION_CHANNEL_STATE_INDEX = 3;
45    private static final int SETTINGS_URI_INDEX = 4;
46    private static final int VOICEMAIL_ACCESS_URI_INDEX = 5;
47    private static final int NUM_COLUMNS = 6;
48    /** Projection on the voicemail_status table used by this class. */
49    public static final String[] PROJECTION = new String[NUM_COLUMNS];
50    static {
51        PROJECTION[SOURCE_PACKAGE_INDEX] = Status.SOURCE_PACKAGE;
52        PROJECTION[CONFIGURATION_STATE_INDEX] = Status.CONFIGURATION_STATE;
53        PROJECTION[DATA_CHANNEL_STATE_INDEX] = Status.DATA_CHANNEL_STATE;
54        PROJECTION[NOTIFICATION_CHANNEL_STATE_INDEX] = Status.NOTIFICATION_CHANNEL_STATE;
55        PROJECTION[SETTINGS_URI_INDEX] = Status.SETTINGS_URI;
56        PROJECTION[VOICEMAIL_ACCESS_URI_INDEX] = Status.VOICEMAIL_ACCESS_URI;
57    }
58
59    /** Possible user actions. */
60    public static enum Action {
61        NONE(-1),
62        CALL_VOICEMAIL(R.string.voicemail_status_action_call_server),
63        CONFIGURE_VOICEMAIL(R.string.voicemail_status_action_configure);
64
65        private final int mMessageId;
66        private Action(int messageId) {
67            mMessageId = messageId;
68        }
69
70        public int getMessageId() {
71            return mMessageId;
72        }
73    }
74
75    /**
76     * Overall state of the source status. Each state is associated with the corresponding display
77     * string and the corrective action. The states are also assigned a relative priority which is
78     * used to order the messages from different sources.
79     */
80    private static enum OverallState {
81        // TODO: Add separate string for call details and call log pages for the states that needs
82        // to be shown in both.
83        /** Both notification and data channel are not working. */
84        NO_CONNECTION(0, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
85                R.string.voicemail_status_audio_not_available),
86        /** Notifications working, but data channel is not working. Audio cannot be downloaded. */
87        NO_DATA(1, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
88                R.string.voicemail_status_audio_not_available),
89        /** Messages are known to be waiting but data channel is not working. */
90        MESSAGE_WAITING(2, Action.CALL_VOICEMAIL, R.string.voicemail_status_messages_waiting,
91                R.string.voicemail_status_audio_not_available),
92        /** Notification channel not working, but data channel is. */
93        NO_NOTIFICATIONS(3, Action.CALL_VOICEMAIL,
94                R.string.voicemail_status_voicemail_not_available),
95        /** Invite user to set up voicemail. */
96        INVITE_FOR_CONFIGURATION(4, Action.CONFIGURE_VOICEMAIL,
97                R.string.voicemail_status_configure_voicemail),
98        /**
99         * No detailed notifications, but data channel is working.
100         * This is normal mode of operation for certain sources. No action needed.
101         */
102        NO_DETAILED_NOTIFICATION(5, Action.NONE, -1),
103        /** Visual voicemail not yet set up. No local action needed. */
104        NOT_CONFIGURED(6, Action.NONE, -1),
105        /** Everything is OK. */
106        OK(7, Action.NONE, -1),
107        /** If one or more state value set by the source is not valid. */
108        INVALID(8, Action.NONE, -1);
109
110        private final int mPriority;
111        private final Action mAction;
112        private final int mCallLogMessageId;
113        private final int mCallDetailsMessageId;
114
115        private OverallState(int priority, Action action, int callLogMessageId) {
116            this(priority, action, callLogMessageId, -1);
117        }
118
119        private OverallState(int priority, Action action, int callLogMessageId,
120                int callDetailsMessageId) {
121            mPriority = priority;
122            mAction = action;
123            mCallLogMessageId = callLogMessageId;
124            mCallDetailsMessageId = callDetailsMessageId;
125        }
126
127        public Action getAction() {
128            return mAction;
129        }
130
131        public int getPriority() {
132            return mPriority;
133        }
134
135        public int getCallLogMessageId() {
136            return mCallLogMessageId;
137        }
138
139        public int getCallDetailsMessageId() {
140            return mCallDetailsMessageId;
141        }
142    }
143
144    /** A wrapper on {@link StatusMessage} which additionally stores the priority of the message. */
145    private static class MessageStatusWithPriority {
146        private final StatusMessage mMessage;
147        private final int mPriority;
148
149        public MessageStatusWithPriority(StatusMessage message, int priority) {
150            mMessage = message;
151            mPriority = priority;
152        }
153    }
154
155    @Override
156    public List<StatusMessage> getStatusMessages(Cursor cursor) {
157        List<MessageStatusWithPriority> messages =
158            new ArrayList<VoicemailStatusHelperImpl.MessageStatusWithPriority>();
159        cursor.moveToPosition(-1);
160        while(cursor.moveToNext()) {
161            MessageStatusWithPriority message = getMessageForStatusEntry(cursor);
162            if (message != null) {
163                messages.add(message);
164            }
165        }
166        // Finally reorder the messages by their priority.
167        return reorderMessages(messages);
168    }
169
170    @Override
171    public int getNumberActivityVoicemailSources(Cursor cursor) {
172        int count = 0;
173        cursor.moveToPosition(-1);
174        while(cursor.moveToNext()) {
175            if (isVoicemailSourceActive(cursor)) {
176                ++count;
177            }
178        }
179        return count;
180    }
181
182    /** Returns whether the source status in the cursor corresponds to an active source. */
183    private boolean isVoicemailSourceActive(Cursor cursor) {
184        return cursor.getString(SOURCE_PACKAGE_INDEX) != null
185                &&  cursor.getInt(CONFIGURATION_STATE_INDEX) == Status.CONFIGURATION_STATE_OK;
186    }
187
188    private List<StatusMessage> reorderMessages(List<MessageStatusWithPriority> messageWrappers) {
189        Collections.sort(messageWrappers, new Comparator<MessageStatusWithPriority>() {
190            @Override
191            public int compare(MessageStatusWithPriority msg1, MessageStatusWithPriority msg2) {
192                return msg1.mPriority - msg2.mPriority;
193            }
194        });
195        List<StatusMessage> reorderMessages = new ArrayList<VoicemailStatusHelper.StatusMessage>();
196        // Copy the ordered message objects into the final list.
197        for (MessageStatusWithPriority messageWrapper : messageWrappers) {
198            reorderMessages.add(messageWrapper.mMessage);
199        }
200        return reorderMessages;
201    }
202
203    /**
204     * Returns the message for the status entry pointed to by the cursor.
205     */
206    private MessageStatusWithPriority getMessageForStatusEntry(Cursor cursor) {
207        final String sourcePackage = cursor.getString(SOURCE_PACKAGE_INDEX);
208        if (sourcePackage == null) {
209            return null;
210        }
211        final OverallState overallState = getOverallState(cursor.getInt(CONFIGURATION_STATE_INDEX),
212                cursor.getInt(DATA_CHANNEL_STATE_INDEX),
213                cursor.getInt(NOTIFICATION_CHANNEL_STATE_INDEX));
214        final Action action = overallState.getAction();
215
216        // No source package or no action, means no message shown.
217        if (action == Action.NONE) {
218            return null;
219        }
220
221        Uri actionUri = null;
222        if (action == Action.CALL_VOICEMAIL) {
223            actionUri = UriUtils.parseUriOrNull(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
224            // Even if actionUri is null, it is still be useful to show the notification.
225        } else if (action == Action.CONFIGURE_VOICEMAIL) {
226            actionUri = UriUtils.parseUriOrNull(cursor.getString(SETTINGS_URI_INDEX));
227            // If there is no settings URI, there is no point in showing the notification.
228            if (actionUri == null) {
229                return null;
230            }
231        }
232        return new MessageStatusWithPriority(
233                new StatusMessage(sourcePackage, overallState.getCallLogMessageId(),
234                        overallState.getCallDetailsMessageId(), action.getMessageId(),
235                        actionUri),
236                overallState.getPriority());
237    }
238
239    private OverallState getOverallState(int configurationState, int dataChannelState,
240            int notificationChannelState) {
241        if (configurationState == CONFIGURATION_STATE_OK) {
242            // Voicemail is configured. Let's see how is the data channel.
243            if (dataChannelState == DATA_CHANNEL_STATE_OK) {
244                // Data channel is fine. What about notification channel?
245                if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
246                    return OverallState.OK;
247                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
248                    return OverallState.NO_DETAILED_NOTIFICATION;
249                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
250                    return OverallState.NO_NOTIFICATIONS;
251                }
252            } else if (dataChannelState == DATA_CHANNEL_STATE_NO_CONNECTION) {
253                // Data channel is not working. What about notification channel?
254                if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
255                    return OverallState.NO_DATA;
256                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
257                    return OverallState.MESSAGE_WAITING;
258                } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
259                    return OverallState.NO_CONNECTION;
260                }
261            }
262        } else if (configurationState == CONFIGURATION_STATE_CAN_BE_CONFIGURED) {
263            // Voicemail not configured. data/notification channel states are irrelevant.
264            return OverallState.INVITE_FOR_CONFIGURATION;
265        } else if (configurationState == Status.CONFIGURATION_STATE_NOT_CONFIGURED) {
266            // Voicemail not configured. data/notification channel states are irrelevant.
267            return OverallState.NOT_CONFIGURED;
268        }
269        // Will reach here only if the source has set an invalid value.
270        return OverallState.INVALID;
271    }
272}
273