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