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 
17 package com.android.dialer.voicemail;
18 
19 import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
20 import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_OK;
21 import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
22 import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
23 import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
24 import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
25 import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
26 
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.provider.VoicemailContract.Status;
30 
31 import com.android.contacts.common.util.UriUtils;
32 import com.android.dialer.R;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.List;
38 
39 /** Implementation of {@link VoicemailStatusHelper}. */
40 public 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;
Action(int messageId)66         private Action(int messageId) {
67             mMessageId = messageId;
68         }
69 
getMessageId()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 
OverallState(int priority, Action action, int callLogMessageId)115         private OverallState(int priority, Action action, int callLogMessageId) {
116             this(priority, action, callLogMessageId, -1);
117         }
118 
OverallState(int priority, Action action, int callLogMessageId, int callDetailsMessageId)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 
getAction()127         public Action getAction() {
128             return mAction;
129         }
130 
getPriority()131         public int getPriority() {
132             return mPriority;
133         }
134 
getCallLogMessageId()135         public int getCallLogMessageId() {
136             return mCallLogMessageId;
137         }
138 
getCallDetailsMessageId()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 
MessageStatusWithPriority(StatusMessage message, int priority)149         public MessageStatusWithPriority(StatusMessage message, int priority) {
150             mMessage = message;
151             mPriority = priority;
152         }
153     }
154 
155     @Override
getStatusMessages(Cursor cursor)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
getNumberActivityVoicemailSources(Cursor cursor)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. */
isVoicemailSourceActive(Cursor cursor)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 
reorderMessages(List<MessageStatusWithPriority> messageWrappers)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      */
getMessageForStatusEntry(Cursor cursor)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 
getOverallState(int configurationState, int dataChannelState, int notificationChannelState)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