Home | History | Annotate | Download | only in voicemail
      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;
     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