blob: ff1786862ba36d2f81a8b81f323308fcb7abddbb [file] [log] [blame]
/*
* 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.dialer.voicemail;
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 android.database.Cursor;
import android.net.Uri;
import android.provider.VoicemailContract.Status;
import com.android.contacts.common.util.UriUtils;
import com.android.dialer.R;
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;
/** Projection on the voicemail_status table used by this class. */
public 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,
R.string.voicemail_status_audio_not_available),
/** Notifications working, but data channel is not working. Audio cannot be downloaded. */
NO_DATA(1, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
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,
R.string.voicemail_status_audio_not_available),
/** 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 mCallLogMessageId;
private final int mCallDetailsMessageId;
private OverallState(int priority, Action action, int callLogMessageId) {
this(priority, action, callLogMessageId, -1);
}
private OverallState(int priority, Action action, int callLogMessageId,
int callDetailsMessageId) {
mPriority = priority;
mAction = action;
mCallLogMessageId = callLogMessageId;
mCallDetailsMessageId = callDetailsMessageId;
}
public Action getAction() {
return mAction;
}
public int getPriority() {
return mPriority;
}
public int getCallLogMessageId() {
return mCallLogMessageId;
}
public int getCallDetailsMessageId() {
return mCallDetailsMessageId;
}
}
/** A wrapper on {@link StatusMessage} which additionally stores the priority of the message. */
private static class MessageStatusWithPriority {
private final StatusMessage mMessage;
private final int mPriority;
public MessageStatusWithPriority(StatusMessage message, int priority) {
mMessage = message;
mPriority = priority;
}
}
@Override
public List<StatusMessage> getStatusMessages(Cursor cursor) {
List<MessageStatusWithPriority> messages =
new ArrayList<VoicemailStatusHelperImpl.MessageStatusWithPriority>();
cursor.moveToPosition(-1);
while(cursor.moveToNext()) {
MessageStatusWithPriority message = getMessageForStatusEntry(cursor);
if (message != null) {
messages.add(message);
}
}
// Finally reorder the messages by their priority.
return reorderMessages(messages);
}
@Override
public int getNumberActivityVoicemailSources(Cursor cursor) {
int count = 0;
cursor.moveToPosition(-1);
while(cursor.moveToNext()) {
if (isVoicemailSourceActive(cursor)) {
++count;
}
}
return count;
}
/** Returns whether the source status in the cursor corresponds to an active source. */
private boolean isVoicemailSourceActive(Cursor cursor) {
return cursor.getString(SOURCE_PACKAGE_INDEX) != null
&& cursor.getInt(CONFIGURATION_STATE_INDEX) == Status.CONFIGURATION_STATE_OK;
}
private List<StatusMessage> reorderMessages(List<MessageStatusWithPriority> messageWrappers) {
Collections.sort(messageWrappers, new Comparator<MessageStatusWithPriority>() {
@Override
public int compare(MessageStatusWithPriority msg1, MessageStatusWithPriority msg2) {
return msg1.mPriority - msg2.mPriority;
}
});
List<StatusMessage> reorderMessages = new ArrayList<VoicemailStatusHelper.StatusMessage>();
// Copy the ordered message objects into the final list.
for (MessageStatusWithPriority messageWrapper : messageWrappers) {
reorderMessages.add(messageWrapper.mMessage);
}
return reorderMessages;
}
/**
* Returns the message for the status entry pointed to by the cursor.
*/
private MessageStatusWithPriority 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 = UriUtils.parseUriOrNull(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
// Even if actionUri is null, it is still be useful to show the notification.
} else if (action == Action.CONFIGURE_VOICEMAIL) {
actionUri = UriUtils.parseUriOrNull(cursor.getString(SETTINGS_URI_INDEX));
// If there is no settings URI, there is no point in showing the notification.
if (actionUri == null) {
return null;
}
}
return new MessageStatusWithPriority(
new StatusMessage(sourcePackage, overallState.getCallLogMessageId(),
overallState.getCallDetailsMessageId(), 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;
}
}