diff options
Diffstat (limited to 'java/com/android/dialer/voicemailstatus')
5 files changed, 529 insertions, 0 deletions
diff --git a/java/com/android/dialer/voicemailstatus/AndroidManifest.xml b/java/com/android/dialer/voicemailstatus/AndroidManifest.xml new file mode 100644 index 000000000..a39894c88 --- /dev/null +++ b/java/com/android/dialer/voicemailstatus/AndroidManifest.xml @@ -0,0 +1,3 @@ +<manifest + package="com.android.dialer.voicemailstatus"> +</manifest> diff --git a/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java b/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java new file mode 100644 index 000000000..142bb63ed --- /dev/null +++ b/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 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.voicemailstatus; + +import android.content.Context; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import com.android.dialer.database.CallLogQueryHandler; + +/** + * Helper class to check whether visual voicemail is enabled. + * + * <p>Call isVisualVoicemailEnabled() to retrieve the result. + * + * <p>The result is cached and saved in a SharedPreferences, stored as a boolean in + * PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER. Every time a new instance is created, it will try to + * restore the cached result from the SharedPreferences. + * + * <p>Call asyncUpdate() to make a CallLogQuery to check the actual status. This is a async call so + * isVisualVoicemailEnabled() will not be affected immediately. + * + * <p>If the status has changed as a result of asyncUpdate(), + * Callback.onVisualVoicemailEnabledStatusChanged() will be called with the new value. + */ +public class VisualVoicemailEnabledChecker implements CallLogQueryHandler.Listener { + + public static final String PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER = + "has_active_voicemail_provider"; + private SharedPreferences mPrefs; + private boolean mHasActiveVoicemailProvider; + private CallLogQueryHandler mCallLogQueryHandler; + private VoicemailStatusHelper mVoicemailStatusHelper; + private Context mContext; + private Callback mCallback; + + public VisualVoicemailEnabledChecker(Context context, @Nullable Callback callback) { + mContext = context; + mCallback = callback; + mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); + mHasActiveVoicemailProvider = mPrefs.getBoolean(PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false); + } + + /** + * @return whether visual voicemail is enabled. Result is cached, call asyncUpdate() to update the + * result. + */ + public boolean isVisualVoicemailEnabled() { + return mHasActiveVoicemailProvider; + } + + /** + * Perform an async query into the system to check the status of visual voicemail. If the status + * has changed, Callback.onVisualVoicemailEnabledStatusChanged() will be called. + */ + public void asyncUpdate() { + mCallLogQueryHandler = new CallLogQueryHandler(mContext, mContext.getContentResolver(), this); + mCallLogQueryHandler.fetchVoicemailStatus(); + } + + @Override + public void onVoicemailStatusFetched(Cursor statusCursor) { + boolean hasActiveVoicemailProvider = + mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0; + if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) { + mHasActiveVoicemailProvider = hasActiveVoicemailProvider; + mPrefs.edit().putBoolean(PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, mHasActiveVoicemailProvider); + if (mCallback != null) { + mCallback.onVisualVoicemailEnabledStatusChanged(mHasActiveVoicemailProvider); + } + } + } + + @Override + public void onVoicemailUnreadCountFetched(Cursor cursor) { + // Do nothing + } + + @Override + public void onMissedCallsUnreadCountFetched(Cursor cursor) { + // Do nothing + } + + @Override + public boolean onCallsFetched(Cursor combinedCursor) { + // Do nothing + return false; + } + + public interface Callback { + + /** Callback to notify enabled status has changed to the @param newValue */ + void onVisualVoicemailEnabledStatusChanged(boolean newValue); + } +} diff --git a/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java new file mode 100644 index 000000000..16bfe704d --- /dev/null +++ b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java @@ -0,0 +1,96 @@ +/* + * 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.voicemailstatus; + +import android.database.Cursor; +import android.net.Uri; +import android.provider.VoicemailContract.Status; +import android.support.annotation.VisibleForTesting; +import java.util.List; + +/** + * Interface used by the call log UI to determine what user message, if any, related to voicemail + * source status needs to be shown. The messages are returned in the order of importance. + * + * <p>The implementation of this interface interacts with the voicemail content provider to fetch + * statuses of all the registered voicemail sources and determines if any status message needs to be + * shown. The user of this interface must observe/listen to provider changes and invoke this class + * to check if any message needs to be shown. + */ +public interface VoicemailStatusHelper { + + /** + * Returns a list of messages, in the order or priority that should be shown to the user. An empty + * list is returned if no message needs to be shown. + * + * @param cursor The cursor pointing to the query on {@link Status#CONTENT_URI}. The projection to + * be used is defined by the implementation class of this interface. + */ + @VisibleForTesting + List<StatusMessage> getStatusMessages(Cursor cursor); + + /** + * Returns the number of active voicemail sources installed. + * + * <p>The number of sources is counted by querying the voicemail status table. + */ + int getNumberActivityVoicemailSources(Cursor cursor); + + @VisibleForTesting + class StatusMessage { + + /** Package of the source on behalf of which this message has to be shown. */ + public final String sourcePackage; + /** + * The string resource id of the status message that should be shown in the call log page. Set + * to -1, if this message is not to be shown in call log. + */ + public final int callLogMessageId; + /** + * The string resource id of the status message that should be shown in the call details page. + * Set to -1, if this message is not to be shown in call details page. + */ + public final int callDetailsMessageId; + /** The string resource id of the action message that should be shown. */ + public final int actionMessageId; + /** URI for the corrective action, where applicable. Null if no action URI is available. */ + public final Uri actionUri; + + public StatusMessage( + String sourcePackage, + int callLogMessageId, + int callDetailsMessageId, + int actionMessageId, + Uri actionUri) { + this.sourcePackage = sourcePackage; + this.callLogMessageId = callLogMessageId; + this.callDetailsMessageId = callDetailsMessageId; + this.actionMessageId = actionMessageId; + this.actionUri = actionUri; + } + + /** Whether this message should be shown in the call log page. */ + public boolean showInCallLog() { + return callLogMessageId != -1; + } + + /** Whether this message should be shown in the call details page. */ + public boolean showInCallDetails() { + return callDetailsMessageId != -1; + } + } +} diff --git a/java/com/android/dialer/voicemailstatus/VoicemailStatusHelperImpl.java b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelperImpl.java new file mode 100644 index 000000000..404897fde --- /dev/null +++ b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelperImpl.java @@ -0,0 +1,278 @@ +/* + * 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.voicemailstatus; + +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.database.VoicemailStatusQuery; +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 { + + @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. A source is + * active if its' configuration state is not NOT_CONFIGURED. For most voicemail sources, only OK + * and NOT_CONFIGURED are used. The OMTP visual voicemail client has the same behavior pre-NMR1. + * NMR1 visual voicemail will only set it to NOT_CONFIGURED when it is deactivated. As soon as + * activation is attempted, it will transition into CONFIGURING then into OK or other error state, + * NOT_CONFIGURED is never set through an error. + */ + private boolean isVoicemailSourceActive(Cursor cursor) { + return cursor.getString(VoicemailStatusQuery.SOURCE_PACKAGE_INDEX) != null + && cursor.getInt(VoicemailStatusQuery.CONFIGURATION_STATE_INDEX) + != Status.CONFIGURATION_STATE_NOT_CONFIGURED; + } + + 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(VoicemailStatusQuery.SOURCE_PACKAGE_INDEX); + if (sourcePackage == null) { + return null; + } + final OverallState overallState = + getOverallState( + cursor.getInt(VoicemailStatusQuery.CONFIGURATION_STATE_INDEX), + cursor.getInt(VoicemailStatusQuery.DATA_CHANNEL_STATE_INDEX), + cursor.getInt(VoicemailStatusQuery.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(VoicemailStatusQuery.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(VoicemailStatusQuery.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; + } + + /** Possible user actions. */ + public 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; + + 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 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; + + OverallState(int priority, Action action, int callLogMessageId) { + this(priority, action, callLogMessageId, -1); + } + + 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; + } + } +} diff --git a/java/com/android/dialer/voicemailstatus/res/values/strings.xml b/java/com/android/dialer/voicemailstatus/res/values/strings.xml new file mode 100644 index 000000000..495ddf2e2 --- /dev/null +++ b/java/com/android/dialer/voicemailstatus/res/values/strings.xml @@ -0,0 +1,41 @@ +<!-- + ~ Copyright (C) 2012 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 + --> + +<resources> + + <!-- Voicemail status message shown at the top of call log to notify the user that no new + voicemails are currently available. This can happen when both notification as well as data + connection to the voicemail server is lost. [CHAR LIMIT=64] --> + <string name="voicemail_status_voicemail_not_available">Voicemail updates not available</string> + <!-- Voicemail status message shown at the top of call log to notify the user that there is no + data connection to the voicemail server, but there are new voicemails waiting on the server. + [CHAR LIMIT=64] --> + <string name="voicemail_status_messages_waiting">New voicemail waiting. Can\'t load right now.</string> + <!-- Voicemail status message shown at the top of call log to invite the user to configure + visual voicemail. [CHAR LIMIT=64] --> + <string name="voicemail_status_configure_voicemail">Set up your voicemail</string> + <!-- Voicemail status message shown at the top of call details screen to notify the user that + the audio of this voicemail is not available. [CHAR LIMIT=64] --> + <string name="voicemail_status_audio_not_available">Audio not available</string> + + <!-- User action prompt shown next to a voicemail status message to let the user configure + visual voicemail. [CHAR LIMIT=20] --> + <string name="voicemail_status_action_configure">Set up</string> + <!-- User action prompt shown next to a voicemail status message to let the user call voicemail + server directly to listen to the voicemails. [CHAR LIMIT=20] --> + <string name="voicemail_status_action_call_server">Call voicemail</string> + +</resources> |