summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/voicemail
diff options
context:
space:
mode:
authoruabdullah <uabdullah@google.com>2018-01-22 18:19:07 -0800
committerCopybara-Service <copybara-piper@google.com>2018-01-22 18:32:12 -0800
commit4deaebc5a988eb83440693721f1ab28b180d8779 (patch)
tree6ef19b009a788ed8f6d4b88b0a08b03adf9d1e30 /java/com/android/dialer/voicemail
parentc979f257060fbcb95cbd4871461dd5eb1ae04f1b (diff)
Move legacy voicemail/error to nui voicemail/listui/error
All packages relating to NUI voicemail should be under third_party/java_src/android_app/dialer/java/com/android/dialer/voicemail/listui. Since there is a chance that during NUI development the legacy third_party/java_src/android_app/dialer/java/com/android/dialer/app/voicemail/error might undergo changes, it makes sense to move this package to the nui, so that no changes in the legacy code are missed for nui. This refactoring would also allow us to ensure that the strings do not need to be translated and that most of the code can be re-used for nui by hooking up the fragment and adapter to voicemail/listui/error. Bug: 71700117 Test: Unit tests PiperOrigin-RevId: 182868896 Change-Id: I23329654df5ce2bf612101708ed001ca308ae1ac
Diffstat (limited to 'java/com/android/dialer/voicemail')
-rw-r--r--java/com/android/dialer/voicemail/listui/error/AndroidManifest.xml21
-rw-r--r--java/com/android/dialer/voicemail/listui/error/OmtpVoicemailMessageCreator.java322
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailErrorAlert.java175
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessage.java256
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessageCreator.java55
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailStatus.java310
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailStatusCorruptionHandler.java114
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailStatusReader.java25
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java72
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailTosMessage.java25
-rw-r--r--java/com/android/dialer/voicemail/listui/error/VoicemailTosMessageCreator.java554
-rw-r--r--java/com/android/dialer/voicemail/listui/error/Vvm3VoicemailMessageCreator.java308
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable-hdpi/ic_voicemail_error_24px.pngbin0 -> 638 bytes
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable-mdpi/ic_voicemail_error_24px.pngbin0 -> 339 bytes
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable-xhdpi/ic_voicemail_error_24px.pngbin0 -> 589 bytes
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable-xxhdpi/ic_voicemail_error_24px.pngbin0 -> 813 bytes
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable-xxxhdpi/ic_voicemail_error_24px.pngbin0 -> 1043 bytes
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable/shadow.xml25
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/drawable/voicemail_tos_image.pngbin0 -> 14695 bytes
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_error_message_fragment.xml112
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_tos_fragment.xml97
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/values/dimens.xml34
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/values/strings.xml214
-rw-r--r--java/com/android/dialer/voicemail/listui/error/res/values/styles.xml82
24 files changed, 2801 insertions, 0 deletions
diff --git a/java/com/android/dialer/voicemail/listui/error/AndroidManifest.xml b/java/com/android/dialer/voicemail/listui/error/AndroidManifest.xml
new file mode 100644
index 000000000..07cb77f58
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<!-- Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.dialer.app.voicemail.error">
+
+ <uses-permission android:name="android.permission.CALL_PHONE"/>
+
+</manifest>
diff --git a/java/com/android/dialer/voicemail/listui/error/OmtpVoicemailMessageCreator.java b/java/com/android/dialer/voicemail/listui/error/OmtpVoicemailMessageCreator.java
new file mode 100644
index 000000000..29b698942
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/OmtpVoicemailMessageCreator.java
@@ -0,0 +1,322 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.content.Context;
+import android.preference.PreferenceManager;
+import android.provider.VoicemailContract.Status;
+import android.support.annotation.Nullable;
+import android.telecom.PhoneAccountHandle;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.PerAccountSharedPreferences;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action;
+import com.android.voicemail.VoicemailClient;
+import com.android.voicemail.VoicemailComponent;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Create error message from {@link VoicemailStatus} for OMTP visual voicemail. This is also the
+ * default behavior if other message creator does not handle the status.
+ */
+public class OmtpVoicemailMessageCreator {
+
+ private static final float QUOTA_NEAR_FULL_THRESHOLD = 0.9f;
+ private static final float QUOTA_FULL_THRESHOLD = 0.99f;
+ protected static final String VOICEMAIL_PROMO_DISMISSED_KEY =
+ "voicemail_archive_promo_was_dismissed";
+ protected static final String VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY =
+ "voicemail_archive_almost_full_promo_was_dismissed";
+
+ @Nullable
+ public static VoicemailErrorMessage create(
+ Context context, VoicemailStatus status, final VoicemailStatusReader statusReader) {
+ VoicemailErrorMessage tosMessage =
+ new VoicemailTosMessageCreator(context, status, statusReader).maybeCreateTosMessage();
+ if (tosMessage != null) {
+ return tosMessage;
+ }
+
+ if (Status.CONFIGURATION_STATE_OK == status.configurationState
+ && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState
+ && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) {
+ return checkQuota(context, status, statusReader);
+ }
+ // Initial state when the source is activating. Other error might be written into data and
+ // notification channel during activation.
+ if (Status.CONFIGURATION_STATE_CONFIGURING == status.configurationState
+ && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState
+ && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_activating_title),
+ context.getString(R.string.voicemail_error_activating_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()));
+ }
+
+ if (Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION == status.notificationChannelState) {
+ return createNoSignalMessage(context, status);
+ }
+
+ if (Status.CONFIGURATION_STATE_FAILED == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_activation_failed_title),
+ context.getString(R.string.voicemail_error_activation_failed_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ if (Status.DATA_CHANNEL_STATE_NO_CONNECTION == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_no_data_title),
+ context.getString(R.string.voicemail_error_no_data_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ if (Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_no_data_title),
+ context.getString(R.string.voicemail_error_no_data_cellular_required_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ if (Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_bad_config_title),
+ context.getString(R.string.voicemail_error_bad_config_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ if (Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_communication_title),
+ context.getString(R.string.voicemail_error_communication_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ if (Status.DATA_CHANNEL_STATE_SERVER_ERROR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_server_title),
+ context.getString(R.string.voicemail_error_server_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ if (Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_server_connection_title),
+ context.getString(R.string.voicemail_error_server_connection_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ VoicemailErrorMessage.createRetryAction(context, status));
+ }
+
+ // This should be an assertion error, but there's a bug in NYC-DR (a bug) that will
+ // sometimes give status mixed from multiple SIMs. There's no meaningful message to be displayed
+ // from it, so just suppress the message.
+ LogUtil.e("OmtpVoicemailMessageCreator.create", "Unhandled status: " + status);
+ return null;
+ }
+
+ public static boolean isSyncBlockingError(VoicemailStatus status) {
+ if (status.notificationChannelState != Status.NOTIFICATION_CHANNEL_STATE_OK) {
+ return true;
+ }
+
+ if (status.dataChannelState != Status.DATA_CHANNEL_STATE_OK) {
+ return true;
+ }
+
+ switch (status.configurationState) {
+ case Status.CONFIGURATION_STATE_OK:
+ // allow activation to be queued again in case it is interrupted
+ case Status.CONFIGURATION_STATE_CONFIGURING:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @Nullable
+ private static VoicemailErrorMessage checkQuota(
+ Context context, VoicemailStatus status, VoicemailStatusReader statusReader) {
+ if (status.quotaOccupied != Status.QUOTA_UNAVAILABLE
+ && status.quotaTotal != Status.QUOTA_UNAVAILABLE) {
+ return createInboxErrorMessage(context, status, statusReader);
+ }
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_QUOTA_CHECK_UNAVAILABLE);
+ return null;
+ }
+
+ @Nullable
+ private static VoicemailErrorMessage createInboxErrorMessage(
+ Context context, VoicemailStatus status, VoicemailStatusReader statusReader) {
+
+ float voicemailOccupiedFraction = (float) status.quotaOccupied / (float) status.quotaTotal;
+
+ if (voicemailOccupiedFraction < QUOTA_NEAR_FULL_THRESHOLD) {
+ return null;
+ }
+
+ boolean isFull = voicemailOccupiedFraction >= QUOTA_FULL_THRESHOLD;
+
+ PhoneAccountHandle phoneAccountHandle = status.getPhoneAccountHandle();
+
+ PerAccountSharedPreferences sharedPreferenceForAccount =
+ new PerAccountSharedPreferences(
+ context, phoneAccountHandle, PreferenceManager.getDefaultSharedPreferences(context));
+
+ VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient();
+
+ boolean shouldShowPromoForArchive =
+ !isPromoForArchiveDismissed(sharedPreferenceForAccount, isFull)
+ && !voicemailClient.isVoicemailArchiveEnabled(context, phoneAccountHandle)
+ && voicemailClient.isVoicemailArchiveAvailable(context);
+
+ if (!shouldShowPromoForArchive) {
+ if (isFull) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_ERROR_MESSAGE);
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_inbox_full_title),
+ context.getString(R.string.voicemail_error_inbox_full_message));
+ } else {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_ERROR_MESSAGE);
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_inbox_near_full_title),
+ context.getString(R.string.voicemail_error_inbox_near_full_message));
+ }
+ }
+
+ String title;
+ CharSequence message;
+ DialerImpression.Type enabledImpression;
+ DialerImpression.Type dismissedImpression;
+ String dismissedKey;
+
+ if (isFull) {
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_PROMO);
+ title = context.getString(R.string.voicemail_error_inbox_full_turn_archive_on_title);
+ message = context.getText(R.string.voicemail_error_inbox_full_turn_archive_on_message);
+ enabledImpression = DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_FULL_PROMO;
+ dismissedImpression = DialerImpression.Type.VVM_USER_DISMISSED_VM_FULL_PROMO;
+ dismissedKey = VOICEMAIL_PROMO_DISMISSED_KEY;
+ } else {
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_PROMO);
+ title = context.getString(R.string.voicemail_error_inbox_almost_full_turn_archive_on_title);
+ message = context.getText(R.string.voicemail_error_inbox_almost_full_turn_archive_on_message);
+ enabledImpression = DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_ALMOST_FULL_PROMO;
+ dismissedImpression = DialerImpression.Type.VVM_USER_DISMISSED_VM_ALMOST_FULL_PROMO;
+ dismissedKey = VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY;
+ }
+
+ return createVMQuotaPromo(
+ context,
+ phoneAccountHandle,
+ status,
+ statusReader,
+ voicemailClient,
+ sharedPreferenceForAccount,
+ title,
+ message,
+ enabledImpression,
+ dismissedImpression,
+ dismissedKey);
+ }
+
+ private static boolean isPromoForArchiveDismissed(
+ PerAccountSharedPreferences sharedPreferenceForAccount, boolean isFull) {
+ if (isFull) {
+ return sharedPreferenceForAccount.getBoolean(VOICEMAIL_PROMO_DISMISSED_KEY, false);
+ } else {
+ return sharedPreferenceForAccount.getBoolean(
+ VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY, false);
+ }
+ }
+
+ private static VoicemailErrorMessage createVMQuotaPromo(
+ Context context,
+ PhoneAccountHandle phoneAccountHandle,
+ VoicemailStatus status,
+ VoicemailStatusReader statusReader,
+ VoicemailClient voicemailClient,
+ PerAccountSharedPreferences sharedPreferenceForAccount,
+ String title,
+ CharSequence message,
+ DialerImpression.Type impressionToLogOnEnable,
+ DialerImpression.Type impressionToLogOnDismiss,
+ String preferenceKeyToUpdate) {
+ return new VoicemailErrorMessage(
+ title,
+ message,
+ VoicemailErrorMessage.createTurnArchiveOnAction(
+ context,
+ impressionToLogOnEnable,
+ status,
+ statusReader,
+ voicemailClient,
+ phoneAccountHandle),
+ VoicemailErrorMessage.createDismissTurnArchiveOnAction(
+ context,
+ impressionToLogOnDismiss,
+ statusReader,
+ sharedPreferenceForAccount,
+ preferenceKeyToUpdate));
+ }
+
+ @Nullable
+ private static VoicemailErrorMessage createNoSignalMessage(
+ Context context, VoicemailStatus status) {
+ CharSequence title;
+ CharSequence description;
+ List<Action> actions = new ArrayList<>();
+ if (Status.CONFIGURATION_STATE_OK == status.configurationState) {
+ if (Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED == status.dataChannelState) {
+ title = context.getString(R.string.voicemail_error_no_signal_title);
+ description =
+ context.getString(R.string.voicemail_error_no_signal_cellular_required_message);
+ } else {
+ title = context.getString(R.string.voicemail_error_no_signal_title);
+ if (status.isAirplaneMode) {
+ description = context.getString(R.string.voicemail_error_no_signal_airplane_mode_message);
+ } else {
+ description = context.getString(R.string.voicemail_error_no_signal_message);
+ }
+ actions.add(VoicemailErrorMessage.createSyncAction(context, status));
+ }
+ } else {
+ title = context.getString(R.string.voicemail_error_not_activate_no_signal_title);
+ if (status.isAirplaneMode) {
+ description =
+ context.getString(
+ R.string.voicemail_error_not_activate_no_signal_airplane_mode_message);
+ } else {
+ description = context.getString(R.string.voicemail_error_not_activate_no_signal_message);
+ actions.add(VoicemailErrorMessage.createRetryAction(context, status));
+ }
+ }
+ if (status.isAirplaneMode) {
+ actions.add(VoicemailErrorMessage.createChangeAirplaneModeAction(context));
+ }
+ return new VoicemailErrorMessage(title, description, actions);
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailErrorAlert.java b/java/com/android/dialer/voicemail/listui/error/VoicemailErrorAlert.java
new file mode 100644
index 000000000..364659d4f
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailErrorAlert.java
@@ -0,0 +1,175 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.text.method.LinkMovementMethod;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.dialer.app.alert.AlertManager;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action;
+import java.util.List;
+
+/**
+ * UI for the voicemail error message, which will be inserted to the top of the voicemail tab if any
+ * occurred.
+ */
+public class VoicemailErrorAlert {
+
+ private final Context context;
+ private final AlertManager alertManager;
+ private final VoicemailErrorMessageCreator messageCreator;
+
+ private final View view;
+ private final TextView header;
+ private final TextView details;
+ private final TextView primaryAction;
+ private final TextView secondaryAction;
+ private final TextView primaryActionRaised;
+ private final TextView secondaryActionRaised;
+ private final AlertManager modalAlertManager;
+ private View modalView;
+
+ public VoicemailErrorAlert(
+ Context context,
+ AlertManager alertManager,
+ AlertManager modalAlertManager,
+ VoicemailErrorMessageCreator messageCreator) {
+ this.context = context;
+ this.alertManager = alertManager;
+ this.modalAlertManager = modalAlertManager;
+ this.messageCreator = messageCreator;
+
+ view = alertManager.inflate(R.layout.voicemail_error_message_fragment);
+ header = (TextView) view.findViewById(R.id.error_card_header);
+ details = (TextView) view.findViewById(R.id.error_card_details);
+ primaryAction = (TextView) view.findViewById(R.id.primary_action);
+ secondaryAction = (TextView) view.findViewById(R.id.secondary_action);
+ primaryActionRaised = (TextView) view.findViewById(R.id.primary_action_raised);
+ secondaryActionRaised = (TextView) view.findViewById(R.id.secondary_action_raised);
+ }
+
+ public void updateStatus(List<VoicemailStatus> statuses, VoicemailStatusReader statusReader) {
+ LogUtil.i("VoicemailErrorAlert.updateStatus", "%d status", statuses.size());
+ VoicemailErrorMessage message = null;
+ view.setVisibility(View.VISIBLE);
+ for (VoicemailStatus status : statuses) {
+ message = messageCreator.create(context, status, statusReader);
+ if (message != null) {
+ break;
+ }
+ }
+
+ alertManager.clear();
+ modalAlertManager.clear();
+ if (message != null) {
+ LogUtil.i(
+ "VoicemailErrorAlert.updateStatus",
+ "isModal: %b, %s",
+ message.isModal(),
+ message.getTitle());
+ if (message.isModal()) {
+ if (message instanceof VoicemailTosMessage) {
+ modalView = getTosView(modalAlertManager, (VoicemailTosMessage) message);
+ } else {
+ throw new IllegalArgumentException("Modal message type is undefined!");
+ }
+ modalAlertManager.add(modalView);
+ } else {
+ loadMessage(message);
+ alertManager.add(view);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public View getView() {
+ return view;
+ }
+
+ @VisibleForTesting
+ public View getModalView() {
+ return modalView;
+ }
+
+ void loadMessage(VoicemailErrorMessage message) {
+ header.setText(message.getTitle());
+ details.setText(message.getDescription());
+ bindActions(message);
+ }
+
+ private View getTosView(AlertManager alertManager, VoicemailTosMessage message) {
+ View view = alertManager.inflate(R.layout.voicemail_tos_fragment);
+ TextView tosTitle = (TextView) view.findViewById(R.id.tos_message_title);
+ tosTitle.setText(message.getTitle());
+ TextView tosDetails = (TextView) view.findViewById(R.id.tos_message_details);
+ tosDetails.setText(message.getDescription());
+ tosDetails.setMovementMethod(LinkMovementMethod.getInstance());
+
+ Assert.checkArgument(message.getActions().size() == 2);
+ Action primaryAction = message.getActions().get(0);
+ TextView primaryButton = (TextView) view.findViewById(R.id.voicemail_tos_button_decline);
+ primaryButton.setText(primaryAction.getText());
+ primaryButton.setOnClickListener(primaryAction.getListener());
+ Action secondaryAction = message.getActions().get(1);
+ TextView secondaryButton = (TextView) view.findViewById(R.id.voicemail_tos_button_accept);
+ secondaryButton.setText(secondaryAction.getText());
+ secondaryButton.setOnClickListener(secondaryAction.getListener());
+
+ if (message.getImageResourceId() != null) {
+ ImageView voicemailTosImage = (ImageView) view.findViewById(R.id.voicemail_image);
+ voicemailTosImage.setImageResource(message.getImageResourceId());
+ voicemailTosImage.setVisibility(View.VISIBLE);
+ }
+
+ return view;
+ }
+
+ /**
+ * Attach actions to buttons until all buttons are assigned. If there are not enough actions the
+ * rest of the buttons will be removed. If there are more actions then buttons the extra actions
+ * will be dropped. {@link VoicemailErrorMessage#getActions()} will specify what actions should be
+ * shown and in what order.
+ */
+ private void bindActions(VoicemailErrorMessage message) {
+ TextView[] buttons = new TextView[] {primaryAction, secondaryAction};
+ TextView[] raisedButtons = new TextView[] {primaryActionRaised, secondaryActionRaised};
+ for (int i = 0; i < buttons.length; i++) {
+ if (message.getActions() != null && i < message.getActions().size()) {
+ VoicemailErrorMessage.Action action = message.getActions().get(i);
+ TextView button;
+ if (action.isRaised()) {
+ button = raisedButtons[i];
+ buttons[i].setVisibility(View.GONE);
+ } else {
+ button = buttons[i];
+ raisedButtons[i].setVisibility(View.GONE);
+ }
+ button.setText(action.getText());
+ button.setOnClickListener(action.getListener());
+ button.setVisibility(View.VISIBLE);
+ } else {
+ buttons[i].setVisibility(View.GONE);
+ raisedButtons[i].setVisibility(View.GONE);
+ }
+ }
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessage.java b/java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessage.java
new file mode 100644
index 000000000..14d6cffdc
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessage.java
@@ -0,0 +1,256 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.provider.VoicemailContract;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.PhoneAccountHandle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.PerAccountSharedPreferences;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.precall.PreCall;
+import com.android.dialer.voicemail.settings.VoicemailChangePinActivity;
+import com.android.voicemail.VoicemailClient;
+import com.android.voicemail.VoicemailComponent;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents an error determined from the current {@link
+ * android.provider.VoicemailContract.Status}. The message will contain a title, a description, and
+ * a list of actions that can be performed.
+ */
+public class VoicemailErrorMessage {
+
+ private final CharSequence title;
+ private final CharSequence description;
+ private final List<Action> actions;
+
+ private boolean modal;
+ private Integer imageResourceId;
+
+ /** Something the user can click on to resolve an error, such as retrying or calling voicemail */
+ public static class Action {
+
+ private final CharSequence text;
+ private final View.OnClickListener listener;
+ private final boolean raised;
+
+ public Action(CharSequence text, View.OnClickListener listener) {
+ this(text, listener, false);
+ }
+
+ public Action(CharSequence text, View.OnClickListener listener, boolean raised) {
+ this.text = text;
+ this.listener = listener;
+ this.raised = raised;
+ }
+
+ public CharSequence getText() {
+ return text;
+ }
+
+ public View.OnClickListener getListener() {
+ return listener;
+ }
+
+ public boolean isRaised() {
+ return raised;
+ }
+ }
+
+ public CharSequence getTitle() {
+ return title;
+ }
+
+ public CharSequence getDescription() {
+ return description;
+ }
+
+ @Nullable
+ public List<Action> getActions() {
+ return actions;
+ }
+
+ public boolean isModal() {
+ return modal;
+ }
+
+ public VoicemailErrorMessage setModal(boolean value) {
+ modal = value;
+ return this;
+ }
+
+ @Nullable
+ public Integer getImageResourceId() {
+ return imageResourceId;
+ }
+
+ public VoicemailErrorMessage setImageResourceId(Integer imageResourceId) {
+ this.imageResourceId = imageResourceId;
+ return this;
+ }
+
+ public VoicemailErrorMessage(CharSequence title, CharSequence description, Action... actions) {
+ this(title, description, Arrays.asList(actions));
+ }
+
+ public VoicemailErrorMessage(
+ CharSequence title, CharSequence description, @Nullable List<Action> actions) {
+ this.title = title;
+ this.description = description;
+ this.actions = actions;
+ }
+
+ @NonNull
+ public static Action createChangeAirplaneModeAction(final Context context) {
+ return new Action(
+ context.getString(R.string.voicemail_action_turn_off_airplane_mode),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_CHANGE_AIRPLANE_MODE_CLICKED);
+ Intent intent = new Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS);
+ context.startActivity(intent);
+ }
+ });
+ }
+
+ @NonNull
+ public static Action createSetPinAction(
+ final Context context, PhoneAccountHandle phoneAccountHandle) {
+ return new Action(
+ context.getString(R.string.voicemail_action_set_pin),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VOICEMAIL_ALERT_SET_PIN_CLICKED);
+ Intent intent = new Intent(VoicemailChangePinActivity.ACTION_CHANGE_PIN);
+ intent.putExtra(VoicemailClient.PARAM_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ context.startActivity(intent);
+ }
+ });
+ }
+
+ @NonNull
+ public static Action createCallVoicemailAction(
+ final Context context, final PhoneAccountHandle phoneAccountHandle) {
+ return new Action(
+ context.getString(R.string.voicemail_action_call_voicemail),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_CALL_VOICEMAIL_CLICKED);
+ PreCall.start(
+ context,
+ CallIntentBuilder.forVoicemail(
+ phoneAccountHandle, CallInitiationType.Type.VOICEMAIL_ERROR_MESSAGE));
+ }
+ });
+ }
+
+ @NonNull
+ public static Action createSyncAction(final Context context, final VoicemailStatus status) {
+ return new Action(
+ context.getString(R.string.voicemail_action_sync),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SYNC);
+ Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
+ intent.setPackage(status.sourcePackage);
+ context.sendBroadcast(intent);
+ }
+ });
+ }
+
+ @NonNull
+ public static Action createRetryAction(final Context context, final VoicemailStatus status) {
+ return new Action(
+ context.getString(R.string.voicemail_action_retry),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_RETRY);
+ Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
+ intent.setPackage(status.sourcePackage);
+ context.sendBroadcast(intent);
+ }
+ });
+ }
+
+ @NonNull
+ public static Action createTurnArchiveOnAction(
+ final Context context,
+ DialerImpression.Type impressionToLog,
+ final VoicemailStatus status,
+ VoicemailStatusReader statusReader,
+ VoicemailClient voicemailClient,
+ PhoneAccountHandle phoneAccountHandle) {
+ return new Action(
+ context.getString(R.string.voicemail_action_turn_archive_on),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Assert.checkArgument(
+ VoicemailComponent.get(context)
+ .getVoicemailClient()
+ .isVoicemailArchiveAvailable(context));
+ Logger.get(context).logImpression(impressionToLog);
+ voicemailClient.setVoicemailArchiveEnabled(context, phoneAccountHandle, true);
+ Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
+ intent.setPackage(status.sourcePackage);
+ context.sendBroadcast(intent);
+ statusReader.refresh();
+ }
+ });
+ }
+
+ @NonNull
+ public static Action createDismissTurnArchiveOnAction(
+ final Context context,
+ DialerImpression.Type impressionToLog,
+ VoicemailStatusReader statusReader,
+ PerAccountSharedPreferences sharedPreferenceForAccount,
+ String preferenceKeyToUpdate) {
+ return new Action(
+ context.getString(R.string.voicemail_action_dimiss),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Assert.checkArgument(
+ VoicemailComponent.get(context)
+ .getVoicemailClient()
+ .isVoicemailArchiveAvailable(context));
+ Logger.get(context).logImpression(impressionToLog);
+ sharedPreferenceForAccount.edit().putBoolean(preferenceKeyToUpdate, true).apply();
+ statusReader.refresh();
+ }
+ });
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessageCreator.java b/java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessageCreator.java
new file mode 100644
index 000000000..43da6afbe
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailErrorMessageCreator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.content.Context;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.Nullable;
+import com.android.voicemail.VisualVoicemailTypeExtensions;
+
+/**
+ * Given a VoicemailStatus, {@link VoicemailErrorMessageCreator#create(Context, VoicemailStatus)}
+ * will return a {@link VoicemailErrorMessage} representing the message to be shown to the user, or
+ * <code>null</code> if no message should be shown.
+ */
+public class VoicemailErrorMessageCreator {
+
+ @Nullable
+ public VoicemailErrorMessage create(
+ Context context, VoicemailStatus status, VoicemailStatusReader statusReader) {
+ // Never return error message before NMR1. Voicemail status is not supported on those.
+ if (VERSION.SDK_INT < VERSION_CODES.N_MR1) {
+ return null;
+ }
+ switch (status.type) {
+ case VisualVoicemailTypeExtensions.VVM_TYPE_VVM3:
+ return Vvm3VoicemailMessageCreator.create(context, status, statusReader);
+ default:
+ return OmtpVoicemailMessageCreator.create(context, status, statusReader);
+ }
+ }
+
+ public boolean isSyncBlockingError(VoicemailStatus status) {
+ switch (status.type) {
+ case VisualVoicemailTypeExtensions.VVM_TYPE_VVM3:
+ return Vvm3VoicemailMessageCreator.isSyncBlockingError(status);
+ default:
+ return OmtpVoicemailMessageCreator.isSyncBlockingError(status);
+ }
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailStatus.java b/java/com/android/dialer/voicemail/listui/error/VoicemailStatus.java
new file mode 100644
index 000000000..3447f03df
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailStatus.java
@@ -0,0 +1,310 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.annotation.TargetApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.provider.VoicemailContract.Status;
+import android.support.annotation.Nullable;
+import android.support.v4.os.BuildCompat;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.database.VoicemailStatusQuery;
+
+/** Structured data from {@link android.provider.VoicemailContract.Status} */
+public class VoicemailStatus {
+
+ public final String sourcePackage;
+ public final String type;
+
+ public final String phoneAccountComponentName;
+ public final String phoneAccountId;
+
+ @Nullable public final Uri settingsUri;
+ @Nullable public final Uri voicemailAccessUri;
+
+ public final int configurationState;
+ public final int dataChannelState;
+ public final int notificationChannelState;
+
+ public final int quotaOccupied;
+ public final int quotaTotal;
+
+ // System status
+
+ public final boolean isAirplaneMode;
+
+ /** Wraps the row currently pointed by <code>statusCursor</code> */
+ public VoicemailStatus(Context context, Cursor statusCursor) {
+ sourcePackage = getString(statusCursor, VoicemailStatusQuery.SOURCE_PACKAGE_INDEX, "");
+
+ settingsUri = getUri(statusCursor, VoicemailStatusQuery.SETTINGS_URI_INDEX);
+ voicemailAccessUri = getUri(statusCursor, VoicemailStatusQuery.VOICEMAIL_ACCESS_URI_INDEX);
+
+ if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
+ type =
+ getString(
+ statusCursor, VoicemailStatusQuery.SOURCE_TYPE_INDEX, TelephonyManager.VVM_TYPE_OMTP);
+ phoneAccountComponentName =
+ getString(statusCursor, VoicemailStatusQuery.PHONE_ACCOUNT_COMPONENT_NAME, "");
+ phoneAccountId = getString(statusCursor, VoicemailStatusQuery.PHONE_ACCOUNT_ID, "");
+ } else {
+ type = TelephonyManager.VVM_TYPE_OMTP;
+ phoneAccountComponentName = "";
+ phoneAccountId = "";
+ }
+
+ configurationState =
+ getInt(
+ statusCursor,
+ VoicemailStatusQuery.CONFIGURATION_STATE_INDEX,
+ Status.CONFIGURATION_STATE_NOT_CONFIGURED);
+ dataChannelState =
+ getInt(
+ statusCursor,
+ VoicemailStatusQuery.DATA_CHANNEL_STATE_INDEX,
+ Status.DATA_CHANNEL_STATE_NO_CONNECTION);
+
+ /* Before O, the NOTIFICATION_CHANNEL_STATE in the voicemail status table for the system
+ * visual voicemail client always correspond to the service state (cellular signal availability)
+ * Tracking the state in the background is redundant because it will not be visible to the
+ * user. It is much simpler to poll the status on the UI side. The result is injected back to
+ * the status query result so the handling will be consistent with other voicemail clients.
+ */
+ if (BuildCompat.isAtLeastO() && sourcePackage.equals(context.getPackageName())) {
+ notificationChannelState =
+ getNotificationChannelStateFormTelephony(context, getPhoneAccountHandle());
+ } else {
+ notificationChannelState =
+ getInt(
+ statusCursor,
+ VoicemailStatusQuery.NOTIFICATION_CHANNEL_STATE_INDEX,
+ Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+ }
+ isAirplaneMode =
+ Settings.System.getInt(context.getContentResolver(), Global.AIRPLANE_MODE_ON, 0) != 0;
+
+ if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ quotaOccupied =
+ getInt(statusCursor, VoicemailStatusQuery.QUOTA_OCCUPIED_INDEX, Status.QUOTA_UNAVAILABLE);
+ quotaTotal =
+ getInt(statusCursor, VoicemailStatusQuery.QUOTA_TOTAL_INDEX, Status.QUOTA_UNAVAILABLE);
+ } else {
+ quotaOccupied = Status.QUOTA_UNAVAILABLE;
+ quotaTotal = Status.QUOTA_UNAVAILABLE;
+ }
+ }
+
+ private VoicemailStatus(Builder builder) {
+ sourcePackage = builder.sourcePackage;
+ phoneAccountComponentName = builder.phoneAccountComponentName;
+ phoneAccountId = builder.phoneAccountId;
+ type = builder.type;
+ settingsUri = builder.settingsUri;
+ voicemailAccessUri = builder.voicemailAccessUri;
+ configurationState = builder.configurationState;
+ dataChannelState = builder.dataChannelState;
+ notificationChannelState = builder.notificationChannelState;
+ quotaOccupied = builder.quotaOccupied;
+ quotaTotal = builder.quotaTotal;
+ isAirplaneMode = builder.isAirplaneMode;
+ }
+
+ @TargetApi(VERSION_CODES.O)
+ private static int getNotificationChannelStateFormTelephony(
+ Context context, PhoneAccountHandle phoneAccountHandle) {
+ TelephonyManager telephonyManager =
+ context
+ .getSystemService(TelephonyManager.class)
+ .createForPhoneAccountHandle(phoneAccountHandle);
+ if (telephonyManager == null) {
+ LogUtil.e("VoicemailStatus.constructor", "invalid PhoneAccountHandle");
+ return Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
+ } else {
+ int state = telephonyManager.getServiceState().getState();
+ if (state == ServiceState.STATE_IN_SERVICE) {
+ return Status.NOTIFICATION_CHANNEL_STATE_OK;
+ } else {
+ return Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
+ }
+ }
+ }
+
+ static class Builder {
+
+ private String sourcePackage = "";
+ private String type = TelephonyManager.VVM_TYPE_OMTP;
+ private String phoneAccountComponentName = "";
+ private String phoneAccountId = "";
+
+ @Nullable private Uri settingsUri;
+ @Nullable private Uri voicemailAccessUri;
+
+ private int configurationState = Status.CONFIGURATION_STATE_NOT_CONFIGURED;
+ private int dataChannelState = Status.DATA_CHANNEL_STATE_NO_CONNECTION;
+ private int notificationChannelState = Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
+
+ private int quotaOccupied = Status.QUOTA_UNAVAILABLE;
+ private int quotaTotal = Status.QUOTA_UNAVAILABLE;
+
+ private boolean isAirplaneMode;
+
+ public VoicemailStatus build() {
+ return new VoicemailStatus(this);
+ }
+
+ public Builder setSourcePackage(String sourcePackage) {
+ this.sourcePackage = sourcePackage;
+ return this;
+ }
+
+ public Builder setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder setPhoneAccountComponentName(String name) {
+ this.phoneAccountComponentName = name;
+ return this;
+ }
+
+ public Builder setPhoneAccountId(String id) {
+ this.phoneAccountId = id;
+ return this;
+ }
+
+ public Builder setSettingsUri(Uri settingsUri) {
+ this.settingsUri = settingsUri;
+ return this;
+ }
+
+ public Builder setVoicemailAccessUri(Uri voicemailAccessUri) {
+ this.voicemailAccessUri = voicemailAccessUri;
+ return this;
+ }
+
+ public Builder setConfigurationState(int configurationState) {
+ this.configurationState = configurationState;
+ return this;
+ }
+
+ public Builder setDataChannelState(int dataChannelState) {
+ this.dataChannelState = dataChannelState;
+ return this;
+ }
+
+ public Builder setNotificationChannelState(int notificationChannelState) {
+ this.notificationChannelState = notificationChannelState;
+ return this;
+ }
+
+ public Builder setQuotaOccupied(int quotaOccupied) {
+ this.quotaOccupied = quotaOccupied;
+ return this;
+ }
+
+ public Builder setQuotaTotal(int quotaTotal) {
+ this.quotaTotal = quotaTotal;
+ return this;
+ }
+
+ public Builder setAirplaneMode(boolean isAirplaneMode) {
+ this.isAirplaneMode = isAirplaneMode;
+ return this;
+ }
+ }
+
+ public boolean isActive() {
+ switch (configurationState) {
+ case Status.CONFIGURATION_STATE_NOT_CONFIGURED:
+ case Status.CONFIGURATION_STATE_DISABLED:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "VoicemailStatus["
+ + "sourcePackage: "
+ + sourcePackage
+ + ", type:"
+ + type
+ + ", settingsUri: "
+ + settingsUri
+ + ", voicemailAccessUri: "
+ + voicemailAccessUri
+ + ", configurationState: "
+ + configurationState
+ + ", dataChannelState: "
+ + dataChannelState
+ + ", notificationChannelState: "
+ + notificationChannelState
+ + ", quotaOccupied: "
+ + quotaOccupied
+ + ", quotaTotal: "
+ + quotaTotal
+ + ", isAirplaneMode: "
+ + isAirplaneMode
+ + "]";
+ }
+
+ @Nullable
+ private static Uri getUri(Cursor cursor, int index) {
+ if (cursor.getString(index) != null) {
+ return Uri.parse(cursor.getString(index));
+ }
+ return null;
+ }
+
+ private static int getInt(Cursor cursor, int index, int defaultValue) {
+ if (cursor.isNull(index)) {
+ return defaultValue;
+ }
+ return cursor.getInt(index);
+ }
+
+ private static String getString(Cursor cursor, int index, String defaultValue) {
+ if (cursor.isNull(index)) {
+ return defaultValue;
+ }
+ return cursor.getString(index);
+ }
+
+ @Nullable
+ public PhoneAccountHandle getPhoneAccountHandle() {
+ if (TextUtils.isEmpty(phoneAccountComponentName) || TextUtils.isEmpty(phoneAccountId)) {
+ return null;
+ }
+ ComponentName componentName = ComponentName.unflattenFromString(phoneAccountComponentName);
+ if (componentName == null) {
+ return null;
+ }
+ return new PhoneAccountHandle(componentName, phoneAccountId);
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailStatusCorruptionHandler.java b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusCorruptionHandler.java
new file mode 100644
index 000000000..630a17d8f
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusCorruptionHandler.java
@@ -0,0 +1,114 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.VoicemailContract.Status;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.telephony.TelephonyManagerCompat;
+import com.android.dialer.configprovider.ConfigProviderBindings;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+
+/**
+ * This class will detect the corruption in the voicemail status and log it so we can track how many
+ * users are affected.
+ */
+public class VoicemailStatusCorruptionHandler {
+
+ /** Where the check is made so logging can be done. */
+ public enum Source {
+ Activity,
+ Notification
+ }
+
+ private static final String CONFIG_VVM_STATUS_FIX_DISABLED = "vvm_status_fix_disabled";
+
+ public static void maybeFixVoicemailStatus(Context context, Cursor statusCursor, Source source) {
+
+ if (ConfigProviderBindings.get(context).getBoolean(CONFIG_VVM_STATUS_FIX_DISABLED, false)) {
+ return;
+ }
+
+ if (VERSION.SDK_INT != VERSION_CODES.N_MR1) {
+ // This issue is specific to N MR1, it is fixed in future SDK.
+ return;
+ }
+
+ if (statusCursor.getCount() == 0) {
+ return;
+ }
+
+ statusCursor.moveToFirst();
+ VoicemailStatus status = new VoicemailStatus(context, statusCursor);
+ PhoneAccountHandle phoneAccountHandle =
+ new PhoneAccountHandle(
+ ComponentName.unflattenFromString(status.phoneAccountComponentName),
+ status.phoneAccountId);
+
+ TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+
+ boolean visualVoicemailEnabled =
+ TelephonyManagerCompat.isVisualVoicemailEnabled(telephonyManager, phoneAccountHandle);
+ LogUtil.i(
+ "VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus",
+ "Source="
+ + source
+ + ", CONFIGURATION_STATE="
+ + status.configurationState
+ + ", visualVoicemailEnabled="
+ + visualVoicemailEnabled);
+
+ // If visual voicemail is enabled, the CONFIGURATION_STATE should be either OK, PIN_NOT_SET,
+ // or other failure code. CONFIGURATION_STATE_NOT_CONFIGURED means that the client has been
+ // shut down improperly (a bug). The client should be reset or the VVM tab will be
+ // missing.
+ if (Status.CONFIGURATION_STATE_NOT_CONFIGURED == status.configurationState
+ && visualVoicemailEnabled) {
+ LogUtil.e(
+ "VoicemailStatusCorruptionHandler.maybeFixVoicemailStatus",
+ "VVM3 voicemail status corrupted");
+
+ switch (source) {
+ case Activity:
+ Logger.get(context)
+ .logImpression(
+ DialerImpression.Type
+ .VOICEMAIL_CONFIGURATION_STATE_CORRUPTION_DETECTED_FROM_ACTIVITY);
+ break;
+ case Notification:
+ Logger.get(context)
+ .logImpression(
+ DialerImpression.Type
+ .VOICEMAIL_CONFIGURATION_STATE_CORRUPTION_DETECTED_FROM_NOTIFICATION);
+ break;
+ default:
+ Assert.fail("this should never happen");
+ break;
+ }
+ // At this point we could attempt to work around the issue by disabling and re-enabling
+ // voicemail. Unfortunately this work around is buggy so we'll do nothing for now.
+ }
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailStatusReader.java b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusReader.java
new file mode 100644
index 000000000..cb644c924
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusReader.java
@@ -0,0 +1,25 @@
+/*
+ * 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.voicemail.listui.error;
+
+/**
+ * A source that is generating the voicemail status to show error messages, used by {@link
+ * VoicemailErrorMessageCreator} to inform the source that the status should be updated
+ */
+public interface VoicemailStatusReader {
+ void refresh();
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java
new file mode 100644
index 000000000..20e46ee2d
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.listui.error;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Build.VERSION_CODES;
+import android.provider.VoicemailContract.Status;
+import android.support.annotation.Nullable;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.database.VoicemailStatusQuery;
+import com.android.dialer.telecom.TelecomUtil;
+import com.android.voicemail.VoicemailComponent;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Worker for {@link com.android.dialer.common.concurrent.DialerExecutors} to fetch voicemail status
+ */
+@TargetApi(VERSION_CODES.M)
+public class VoicemailStatusWorker implements Worker<Context, List<VoicemailStatus>> {
+
+ @Nullable
+ @Override
+ public List<VoicemailStatus> doInBackground(@Nullable Context context) throws Throwable {
+ List<VoicemailStatus> statuses = new ArrayList<>();
+ if (!TelecomUtil.hasReadWriteVoicemailPermissions(context)) {
+ return statuses;
+ }
+ StringBuilder where = new StringBuilder();
+ java.util.List<String> selectionArgs = new ArrayList<>();
+
+ VoicemailComponent.get(context)
+ .getVoicemailClient()
+ .appendOmtpVoicemailStatusSelectionClause(context, where, selectionArgs);
+
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(
+ Status.CONTENT_URI,
+ VoicemailStatusQuery.getProjection(),
+ where.toString(),
+ selectionArgs.toArray(new String[selectionArgs.size()]),
+ null)) {
+ if (cursor == null) {
+ return statuses;
+ }
+
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ statuses.add(new VoicemailStatus(context, cursor));
+ }
+ }
+
+ return statuses;
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailTosMessage.java b/java/com/android/dialer/voicemail/listui/error/VoicemailTosMessage.java
new file mode 100644
index 000000000..f6a8f07cc
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailTosMessage.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.listui.error;
+
+/** Voicemail TOS message. */
+public class VoicemailTosMessage extends VoicemailErrorMessage {
+
+ public VoicemailTosMessage(CharSequence title, CharSequence description, Action... actions) {
+ super(title, description, actions);
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailTosMessageCreator.java b/java/com/android/dialer/voicemail/listui/error/VoicemailTosMessageCreator.java
new file mode 100644
index 000000000..ec1a95a1a
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/VoicemailTosMessageCreator.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2017 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.listui.error;
+
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AlignmentSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.URLSpan;
+import android.view.View;
+import android.view.View.OnClickListener;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.telephony.TelephonyManagerCompat;
+import com.android.dialer.configprovider.ConfigProviderBindings;
+import com.android.dialer.constants.Constants;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action;
+import com.android.dialer.voicemail.settings.VoicemailSettingsFragment;
+import com.android.voicemail.VisualVoicemailTypeExtensions;
+import com.android.voicemail.VoicemailClient;
+import com.android.voicemail.VoicemailComponent;
+import com.android.voicemail.VoicemailVersionConstants;
+import java.util.Locale;
+
+/**
+ * Create error message from {@link VoicemailStatus} for voicemail. This is will show different
+ * terms of service for Verizon and for other carriers.
+ */
+public class VoicemailTosMessageCreator {
+ private static final String ISO639_SPANISH = "es";
+
+ private final Context context;
+ private final VoicemailStatus status;
+ private final VoicemailStatusReader statusReader;
+ private final SharedPreferences preferences;
+
+ VoicemailTosMessageCreator(
+ final Context context,
+ final VoicemailStatus status,
+ final VoicemailStatusReader statusReader) {
+ this.context = context;
+ this.status = status;
+ this.statusReader = statusReader;
+ this.preferences =
+ PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
+ }
+
+ @Nullable
+ VoicemailErrorMessage maybeCreateTosMessage() {
+ if (!canShowTos()) {
+ return null;
+ } else if (shouldShowTos()) {
+ logTosCreatedImpression();
+ return getTosMessage();
+ } else if (shouldShowPromo()) {
+ return getPromoMessage();
+ } else {
+ return null;
+ }
+ }
+
+ private VoicemailErrorMessage getTosMessage() {
+ return new VoicemailTosMessage(
+ getNewUserTosTitle(),
+ getNewUserTosMessageText(),
+ new Action(
+ getDeclineText(),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtil.i("VoicemailTosMessageCreator.getTosMessage", "decline clicked");
+ PhoneAccountHandle handle =
+ new PhoneAccountHandle(
+ ComponentName.unflattenFromString(status.phoneAccountComponentName),
+ status.phoneAccountId);
+ logTosDeclinedImpression();
+ showDeclineTosDialog(handle);
+ }
+ }),
+ new Action(
+ getAcceptText(),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtil.i("VoicemailTosMessageCreator.getTosMessage", "accept clicked");
+ recordTosAcceptance();
+ // Accepting the TOS also acknowledges the latest features
+ recordFeatureAcknowledgement();
+ logTosAcceptedImpression();
+ statusReader.refresh();
+ }
+ },
+ true /* raised */))
+ .setModal(true)
+ .setImageResourceId(R.drawable.voicemail_tos_image);
+ }
+
+ private VoicemailErrorMessage getPromoMessage() {
+ return new VoicemailTosMessage(
+ getExistingUserTosTitle(),
+ getExistingUserTosMessageText(),
+ new Action(
+ context.getString(R.string.dialer_terms_and_conditions_existing_user_setings),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtil.i("VoicemailTosMessageCreator.getPromoMessage", "open settings");
+ Intent intent =
+ new Intent(Intent.ACTION_VIEW)
+ .setComponent(
+ new ComponentName(context, Constants.get().getSettingsActivity()))
+ .setData(
+ Uri.fromParts(
+ "header", VoicemailSettingsFragment.class.getName(), null));
+ context.startActivity(intent);
+ }
+ }),
+ new Action(
+ context.getString(R.string.dialer_terms_and_conditions_existing_user_ack),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ LogUtil.i("VoicemailTosMessageCreator.getPromoMessage", "acknowledge clicked");
+ // Feature acknowledgement also means accepting TOS
+ recordTosAcceptance();
+ recordFeatureAcknowledgement();
+ statusReader.refresh();
+ }
+ },
+ true /* raised */))
+ .setModal(true)
+ .setImageResourceId(R.drawable.voicemail_tos_image);
+ }
+
+ private boolean canShowTos() {
+ if (!isValidVoicemailType(status.type)) {
+ LogUtil.i("VoicemailTosMessageCreator.canShowTos", "unsupported type: " + status.type);
+ return false;
+ }
+
+ if (status.getPhoneAccountHandle() == null
+ || status.getPhoneAccountHandle().getComponentName() == null) {
+ LogUtil.i("VoicemailTosMessageCreator.canShowTos", "invalid phone account");
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean shouldShowTos() {
+ if (hasAcceptedTos()) {
+ LogUtil.i("VoicemailTosMessageCreator.shouldShowTos", "already accepted TOS");
+ return false;
+ }
+
+ if (isVvm3()) {
+ LogUtil.i("VoicemailTosMessageCreator.shouldShowTos", "showing TOS for verizon");
+ return true;
+ }
+
+ if (isVoicemailTranscriptionAvailable() && !isLegacyVoicemailUser()) {
+ LogUtil.i(
+ "VoicemailTosMessageCreator.shouldShowTos", "showing TOS for Google transcription users");
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean shouldShowPromo() {
+ if (hasAcknowledgedFeatures()) {
+ LogUtil.i(
+ "VoicemailTosMessageCreator.shouldShowPromo", "already acknowledeged latest features");
+ return false;
+ }
+
+ if (isVoicemailTranscriptionAvailable()) {
+ LogUtil.i(
+ "VoicemailTosMessageCreator.shouldShowPromo",
+ "showing promo for Google transcription users");
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean isValidVoicemailType(String type) {
+ if (type == null) {
+ return false;
+ }
+ switch (type) {
+ case TelephonyManager.VVM_TYPE_OMTP:
+ case TelephonyManager.VVM_TYPE_CVVM:
+ case VisualVoicemailTypeExtensions.VVM_TYPE_VVM3:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isVoicemailTranscriptionAvailable() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ && ConfigProviderBindings.get(context)
+ .getBoolean("voicemail_transcription_available", false);
+ }
+
+ private void showDeclineTosDialog(final PhoneAccountHandle handle) {
+ if (isVvm3() && Vvm3VoicemailMessageCreator.PIN_NOT_SET == status.configurationState) {
+ LogUtil.i(
+ "VoicemailTosMessageCreator.showDeclineTosDialog", "PIN_NOT_SET, showing set PIN dialog");
+ showSetPinBeforeDeclineDialog(handle);
+ return;
+ }
+ LogUtil.i(
+ "VoicemailTosMessageCreator.showDeclineVerizonTosDialog",
+ "showing decline ToS dialog, status=" + status);
+ final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.terms_and_conditions_decline_dialog_title);
+ builder.setMessage(getTosDeclinedDialogMessageId());
+ builder.setPositiveButton(
+ getTosDeclinedDialogDowngradeId(),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINED);
+ VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient();
+ if (voicemailClient.isVoicemailModuleEnabled()) {
+ voicemailClient.setVoicemailEnabled(context, status.getPhoneAccountHandle(), false);
+ } else {
+ TelephonyManagerCompat.setVisualVoicemailEnabled(telephonyManager, handle, false);
+ }
+ }
+ });
+
+ builder.setNegativeButton(
+ android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+
+ builder.setCancelable(true);
+ builder.show();
+ }
+
+ private void showSetPinBeforeDeclineDialog(PhoneAccountHandle phoneAccountHandle) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setMessage(R.string.verizon_terms_and_conditions_decline_set_pin_dialog_message);
+ builder.setPositiveButton(
+ R.string.verizon_terms_and_conditions_decline_set_pin_dialog_set_pin,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINE_CHANGE_PIN_SHOWN);
+ Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
+ intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ context.startActivity(intent);
+ }
+ });
+
+ builder.setNegativeButton(
+ android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+
+ builder.setCancelable(true);
+ builder.show();
+ }
+
+ private boolean isVvm3() {
+ return VisualVoicemailTypeExtensions.VVM_TYPE_VVM3.equals(status.type);
+ }
+
+ private boolean useSpanish() {
+ return Locale.getDefault().getLanguage().equals(new Locale(ISO639_SPANISH).getLanguage());
+ }
+
+ private boolean hasAcceptedTos() {
+ if (isVvm3()) {
+ return preferences.getInt(VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION;
+ } else {
+ return preferences.getInt(VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION;
+ }
+ }
+
+ private void recordTosAcceptance() {
+ if (isVvm3()) {
+ preferences
+ .edit()
+ .putInt(
+ VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY,
+ VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION)
+ .apply();
+ } else {
+ preferences
+ .edit()
+ .putInt(
+ VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY,
+ VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION)
+ .apply();
+ }
+
+ PhoneAccountHandle handle =
+ new PhoneAccountHandle(
+ ComponentName.unflattenFromString(status.phoneAccountComponentName),
+ status.phoneAccountId);
+ VoicemailComponent.get(context).getVoicemailClient().onTosAccepted(context, handle);
+ }
+
+ private boolean hasAcknowledgedFeatures() {
+ if (isVvm3()) {
+ return true;
+ }
+
+ return preferences.getInt(
+ VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
+ >= VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION;
+ }
+
+ private void recordFeatureAcknowledgement() {
+ preferences
+ .edit()
+ .putInt(
+ VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY,
+ VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION)
+ .apply();
+ }
+
+ private boolean isLegacyVoicemailUser() {
+ return preferences.getInt(
+ VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
+ == VoicemailVersionConstants.LEGACY_VOICEMAIL_FEATURE_VERSION;
+ }
+
+ private void logTosCreatedImpression() {
+ if (isVvm3()) {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_CREATED);
+ } else {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_CREATED);
+ }
+ }
+
+ private void logTosDeclinedImpression() {
+ if (isVvm3()) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_DECLINE_CLICKED);
+ } else {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_DECLINE_CLICKED);
+ }
+ }
+
+ private void logTosAcceptedImpression() {
+ if (isVvm3()) {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_ACCEPTED);
+ } else {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_ACCEPTED);
+ }
+ }
+
+ private CharSequence getVvm3Tos() {
+ String policyUrl = context.getString(R.string.verizon_terms_and_conditions_policy_url);
+ return useSpanish()
+ ? context.getString(R.string.verizon_terms_and_conditions_1_1_spanish, policyUrl)
+ : context.getString(R.string.verizon_terms_and_conditions_1_1_english, policyUrl);
+ }
+
+ private CharSequence getVvmDialerTos() {
+ return context.getString(R.string.dialer_terms_and_conditions_for_verizon_1_0);
+ }
+
+ private CharSequence getNewUserDialerTos() {
+ if (!isVoicemailTranscriptionAvailable()) {
+ return "";
+ }
+
+ String learnMoreText = context.getString(R.string.dialer_terms_and_conditions_learn_more);
+ return context.getString(R.string.dialer_terms_and_conditions_1_0, learnMoreText);
+ }
+
+ private CharSequence getExistingUserDialerTos() {
+ if (!isVoicemailTranscriptionAvailable()) {
+ return "";
+ }
+
+ String learnMoreText = context.getString(R.string.dialer_terms_and_conditions_learn_more);
+ return context.getString(R.string.dialer_terms_and_conditions_existing_user, learnMoreText);
+ }
+
+ private CharSequence getAcceptText() {
+ if (isVvm3()) {
+ return useSpanish()
+ ? context.getString(R.string.verizon_terms_and_conditions_accept_spanish)
+ : context.getString(R.string.verizon_terms_and_conditions_accept_english);
+ } else {
+ return useSpanish()
+ ? context.getString(R.string.dialer_terms_and_conditions_accept_spanish)
+ : context.getString(R.string.dialer_terms_and_conditions_accept_english);
+ }
+ }
+
+ private CharSequence getDeclineText() {
+ if (isVvm3()) {
+ return useSpanish()
+ ? context.getString(R.string.verizon_terms_and_conditions_decline_spanish)
+ : context.getString(R.string.verizon_terms_and_conditions_decline_english);
+ } else {
+ return useSpanish()
+ ? context.getString(R.string.dialer_terms_and_conditions_decline_spanish)
+ : context.getString(R.string.dialer_terms_and_conditions_decline_english);
+ }
+ }
+
+ private CharSequence getNewUserTosTitle() {
+ return isVvm3()
+ ? context.getString(R.string.verizon_terms_and_conditions_title)
+ : context.getString(R.string.dialer_terms_and_conditions_title);
+ }
+
+ private CharSequence getExistingUserTosTitle() {
+ return isVvm3()
+ ? context.getString(R.string.verizon_terms_and_conditions_title)
+ : context.getString(R.string.dialer_terms_and_conditions_existing_user_title);
+ }
+
+ private CharSequence getNewUserTosMessageText() {
+ SpannableString spannableTos;
+ if (isVvm3()) {
+ // For verizon the TOS consist of three pieces: google dialer TOS, Verizon TOS message and
+ // Verizon TOS details.
+ CharSequence vvm3Details = getVvm3Tos();
+ CharSequence tos =
+ context.getString(
+ R.string.verizon_terms_and_conditions_message, getVvmDialerTos(), vvm3Details);
+ spannableTos = new SpannableString(tos);
+ // Set the text style for the details part of the TOS
+ int start = spannableTos.length() - vvm3Details.length();
+ spannableTos.setSpan(
+ new TextAppearanceSpan(context, R.style.TosDetailsTextStyle),
+ start,
+ start + vvm3Details.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ // Add verizon policy link
+ String linkUrl = context.getString(R.string.verizon_terms_and_conditions_policy_url);
+ return addLink(spannableTos, linkUrl, linkUrl);
+ } else {
+ // The TOS for everyone else, there are no details, but change to center alignment.
+ CharSequence tos =
+ context.getString(R.string.dialer_terms_and_conditions_message, getNewUserDialerTos());
+ spannableTos = new SpannableString(tos);
+ spannableTos.setSpan(
+ new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
+ 0,
+ tos.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // Add 'Learn more' link for dialer TOS
+ String learnMore = context.getString(R.string.dialer_terms_and_conditions_learn_more);
+ return addLink(spannableTos, learnMore, getLearnMoreUrl());
+ }
+ }
+
+ private CharSequence getExistingUserTosMessageText() {
+ SpannableString spannableTos;
+ // Change to center alignment.
+ CharSequence tos =
+ context.getString(R.string.dialer_terms_and_conditions_message, getExistingUserDialerTos());
+ spannableTos = new SpannableString(tos);
+ spannableTos.setSpan(
+ new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
+ 0,
+ tos.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // Add 'Learn more' link for dialer TOS
+ String learnMore = context.getString(R.string.dialer_terms_and_conditions_learn_more);
+ return addLink(spannableTos, learnMore, getLearnMoreUrl());
+ }
+
+ private SpannableString addLink(SpannableString spannable, String linkText, String linkUrl) {
+ if (TextUtils.isEmpty(linkUrl) || TextUtils.isEmpty(linkText)) {
+ return spannable;
+ }
+
+ int start = spannable.toString().indexOf(linkText);
+ if (start != -1) {
+ int end = start + linkText.length();
+ spannable.setSpan(new URLSpan(linkUrl), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spannable.setSpan(
+ new TextAppearanceSpan(context, R.style.TosLinkStyle),
+ start,
+ end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ return spannable;
+ }
+
+ private String getLearnMoreUrl() {
+ return ConfigProviderBindings.get(context)
+ .getString(
+ "voicemail_transcription_learn_more_url",
+ context.getString(R.string.dialer_terms_and_conditions_learn_more_url));
+ }
+
+ private int getTosDeclinedDialogMessageId() {
+ return isVvm3()
+ ? R.string.verizon_terms_and_conditions_decline_dialog_message
+ : R.string.dialer_terms_and_conditions_decline_dialog_message;
+ }
+
+ private int getTosDeclinedDialogDowngradeId() {
+ return isVvm3()
+ ? R.string.verizon_terms_and_conditions_decline_dialog_downgrade
+ : R.string.dialer_terms_and_conditions_decline_dialog_downgrade;
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/Vvm3VoicemailMessageCreator.java b/java/com/android/dialer/voicemail/listui/error/Vvm3VoicemailMessageCreator.java
new file mode 100644
index 000000000..17173d82b
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/Vvm3VoicemailMessageCreator.java
@@ -0,0 +1,308 @@
+/*
+ * 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.voicemail.listui.error;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build.VERSION_CODES;
+import android.provider.VoicemailContract.Status;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.view.View;
+import android.view.View.OnClickListener;
+import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action;
+
+/**
+ * Create error message from {@link VoicemailStatus} for VVM3 visual voicemail. VVM3 is used only by
+ * Verizon Wireless.
+ */
+@RequiresApi(VERSION_CODES.N_MR1)
+public class Vvm3VoicemailMessageCreator {
+
+ // Copied from com.android.phone.vvm.omtp.protocol.Vvm3EventHandler
+ // TODO(a bug): unbundle VVM client so we can access these values directly
+ public static final int VMS_DNS_FAILURE = -9001;
+ public static final int VMG_DNS_FAILURE = -9002;
+ public static final int SPG_DNS_FAILURE = -9003;
+ public static final int VMS_NO_CELLULAR = -9004;
+ public static final int VMG_NO_CELLULAR = -9005;
+ public static final int SPG_NO_CELLULAR = -9006;
+ public static final int VMS_TIMEOUT = -9007;
+ public static final int VMG_TIMEOUT = -9008;
+ public static final int STATUS_SMS_TIMEOUT = -9009;
+
+ public static final int SUBSCRIBER_BLOCKED = -9990;
+ public static final int UNKNOWN_USER = -9991;
+ public static final int UNKNOWN_DEVICE = -9992;
+ public static final int INVALID_PASSWORD = -9993;
+ public static final int MAILBOX_NOT_INITIALIZED = -9994;
+ public static final int SERVICE_NOT_PROVISIONED = -9995;
+ public static final int SERVICE_NOT_ACTIVATED = -9996;
+ public static final int USER_BLOCKED = -9998;
+ public static final int IMAP_GETQUOTA_ERROR = -9997;
+ public static final int IMAP_SELECT_ERROR = -9989;
+ public static final int IMAP_ERROR = -9999;
+
+ public static final int VMG_INTERNAL_ERROR = -101;
+ public static final int VMG_DB_ERROR = -102;
+ public static final int VMG_COMMUNICATION_ERROR = -103;
+ public static final int SPG_URL_NOT_FOUND = -301;
+
+ // Non VVM3 codes:
+ public static final int VMG_UNKNOWN_ERROR = -1;
+ public static final int PIN_NOT_SET = -100;
+ public static final int SUBSCRIBER_UNKNOWN = -99;
+
+ @Nullable
+ public static VoicemailErrorMessage create(
+ final Context context,
+ final VoicemailStatus status,
+ final VoicemailStatusReader statusReader) {
+ VoicemailErrorMessage tosMessage =
+ new VoicemailTosMessageCreator(context, status, statusReader).maybeCreateTosMessage();
+ if (tosMessage != null) {
+ return tosMessage;
+ }
+
+ if (VMS_DNS_FAILURE == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_vms_dns_failure_title),
+ getCustomerSupportString(context, R.string.vvm3_error_vms_dns_failure_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (VMG_DNS_FAILURE == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_vmg_dns_failure_title),
+ getCustomerSupportString(context, R.string.vvm3_error_vmg_dns_failure_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (SPG_DNS_FAILURE == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_spg_dns_failure_title),
+ getCustomerSupportString(context, R.string.vvm3_error_spg_dns_failure_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (VMS_NO_CELLULAR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_vms_no_cellular_title),
+ getCustomerSupportString(context, R.string.vvm3_error_vms_no_cellular_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (VMG_NO_CELLULAR == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_vmg_no_cellular_title),
+ getCustomerSupportString(context, R.string.vvm3_error_vmg_no_cellular_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (SPG_NO_CELLULAR == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_spg_no_cellular_title),
+ getCustomerSupportString(context, R.string.vvm3_error_spg_no_cellular_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (VMS_TIMEOUT == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_vms_timeout_title),
+ getCustomerSupportString(context, R.string.vvm3_error_vms_timeout_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (VMG_TIMEOUT == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_vmg_timeout_title),
+ getCustomerSupportString(context, R.string.vvm3_error_vmg_timeout_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (STATUS_SMS_TIMEOUT == status.notificationChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_status_sms_timeout_title),
+ getCustomerSupportString(context, R.string.vvm3_error_status_sms_timeout_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (SUBSCRIBER_BLOCKED == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_subscriber_blocked_title),
+ getCustomerSupportString(context, R.string.vvm3_error_subscriber_blocked_message),
+ VoicemailErrorMessage.createRetryAction(context, status),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (UNKNOWN_USER == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_unknown_user_title),
+ getCustomerSupportString(context, R.string.vvm3_error_unknown_user_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (UNKNOWN_DEVICE == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_unknown_device_title),
+ getCustomerSupportString(context, R.string.vvm3_error_unknown_device_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (INVALID_PASSWORD == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_invalid_password_title),
+ getCustomerSupportString(context, R.string.vvm3_error_invalid_password_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (MAILBOX_NOT_INITIALIZED == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_mailbox_not_initialized_title),
+ getCustomerSupportString(context, R.string.vvm3_error_mailbox_not_initialized_message),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (SERVICE_NOT_PROVISIONED == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_service_not_provisioned_title),
+ getCustomerSupportString(context, R.string.vvm3_error_service_not_provisioned_message),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (SERVICE_NOT_ACTIVATED == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_service_not_activated_title),
+ getCustomerSupportString(context, R.string.vvm3_error_service_not_activated_message),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (USER_BLOCKED == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_user_blocked_title),
+ getCustomerSupportString(context, R.string.vvm3_error_user_blocked_message),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (SUBSCRIBER_UNKNOWN == status.configurationState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_subscriber_unknown_title),
+ getCustomerSupportString(context, R.string.vvm3_error_subscriber_unknown_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (IMAP_GETQUOTA_ERROR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_imap_getquota_error_title),
+ getCustomerSupportString(context, R.string.vvm3_error_imap_getquota_error_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (IMAP_SELECT_ERROR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_imap_select_error_title),
+ getCustomerSupportString(context, R.string.vvm3_error_imap_select_error_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (IMAP_ERROR == status.dataChannelState) {
+ return new VoicemailErrorMessage(
+ context.getString(R.string.vvm3_error_imap_error_title),
+ getCustomerSupportString(context, R.string.vvm3_error_imap_error_message),
+ VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
+ createCallCustomerSupportAction(context));
+ }
+
+ if (PIN_NOT_SET == status.configurationState) {
+ Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_ALERT_SET_PIN_SHOWN);
+ return new VoicemailErrorMessage(
+ context.getString(R.string.voicemail_error_pin_not_set_title),
+ getCustomerSupportString(context, R.string.voicemail_error_pin_not_set_message),
+ VoicemailErrorMessage.createSetPinAction(context, status.getPhoneAccountHandle()));
+ }
+
+ return OmtpVoicemailMessageCreator.create(context, status, statusReader);
+ }
+
+ public static boolean isSyncBlockingError(VoicemailStatus status) {
+ if (status.notificationChannelState != Status.NOTIFICATION_CHANNEL_STATE_OK) {
+ return true;
+ }
+
+ if (status.dataChannelState != Status.DATA_CHANNEL_STATE_OK) {
+ return true;
+ }
+
+ switch (status.configurationState) {
+ case PIN_NOT_SET:
+ case Status.CONFIGURATION_STATE_OK:
+ // allow activation to be queued again in case it is interrupted
+ case Status.CONFIGURATION_STATE_CONFIGURING:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @NonNull
+ private static CharSequence getCustomerSupportString(Context context, int id) {
+ // TODO(twyen): get number based on the country the user is currently in.
+ return ContactDisplayUtils.getTtsSpannedPhoneNumber(
+ context.getResources(),
+ id,
+ context.getString(R.string.verizon_domestic_customer_support_display_number));
+ }
+
+ @NonNull
+ private static Action createCallCustomerSupportAction(final Context context) {
+ return new Action(
+ context.getString(R.string.voicemail_action_call_customer_support),
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent =
+ new Intent(
+ Intent.ACTION_CALL,
+ Uri.parse(
+ "tel:"
+ + context.getString(
+ R.string.verizon_domestic_customer_support_number)));
+ context.startActivity(intent);
+ }
+ });
+ }
+}
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable-hdpi/ic_voicemail_error_24px.png b/java/com/android/dialer/voicemail/listui/error/res/drawable-hdpi/ic_voicemail_error_24px.png
new file mode 100644
index 000000000..1a5744bce
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable-hdpi/ic_voicemail_error_24px.png
Binary files differ
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable-mdpi/ic_voicemail_error_24px.png b/java/com/android/dialer/voicemail/listui/error/res/drawable-mdpi/ic_voicemail_error_24px.png
new file mode 100644
index 000000000..9936f0be6
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable-mdpi/ic_voicemail_error_24px.png
Binary files differ
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable-xhdpi/ic_voicemail_error_24px.png b/java/com/android/dialer/voicemail/listui/error/res/drawable-xhdpi/ic_voicemail_error_24px.png
new file mode 100644
index 000000000..9eff40989
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable-xhdpi/ic_voicemail_error_24px.png
Binary files differ
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable-xxhdpi/ic_voicemail_error_24px.png b/java/com/android/dialer/voicemail/listui/error/res/drawable-xxhdpi/ic_voicemail_error_24px.png
new file mode 100644
index 000000000..5b25f7300
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable-xxhdpi/ic_voicemail_error_24px.png
Binary files differ
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable-xxxhdpi/ic_voicemail_error_24px.png b/java/com/android/dialer/voicemail/listui/error/res/drawable-xxxhdpi/ic_voicemail_error_24px.png
new file mode 100644
index 000000000..d47ee4cdc
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable-xxxhdpi/ic_voicemail_error_24px.png
Binary files differ
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable/shadow.xml b/java/com/android/dialer/voicemail/listui/error/res/drawable/shadow.xml
new file mode 100644
index 000000000..925c8d290
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable/shadow.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <gradient
+ android:startColor="#3333"
+ android:endColor="#0333"
+ android:type="linear"
+ android:angle="90">
+ </gradient>
+</shape>
diff --git a/java/com/android/dialer/voicemail/listui/error/res/drawable/voicemail_tos_image.png b/java/com/android/dialer/voicemail/listui/error/res/drawable/voicemail_tos_image.png
new file mode 100644
index 000000000..48ab3c3c3
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/drawable/voicemail_tos_image.png
Binary files differ
diff --git a/java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_error_message_fragment.xml b/java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_error_message_fragment.xml
new file mode 100644
index 000000000..4bea8b152
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_error_message_fragment.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<android.support.v7.widget.CardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/error_card"
+ style="@style/CallLogCardStyle"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/error_card_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/alert_main_padding"
+ android:paddingBottom="@dimen/alert_main_padding"
+ android:paddingStart="@dimen/alert_main_padding"
+ android:paddingEnd="@dimen/alert_main_padding"
+ android:gravity="top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/voicemail_promo_card_icon"
+ android:layout_width="@dimen/voicemail_promo_card_icon_size"
+ android:layout_height="@dimen/voicemail_promo_card_icon_size"
+ android:layout_gravity="top"
+ android:src="@drawable/ic_voicemail_error_24px"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/voicemail_promo_card_main_padding"
+ android:gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/error_card_header"
+ android:textStyle="bold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/voicemail_promo_card_title_padding"
+ android:layout_gravity="center_vertical"
+ android:singleLine="false"
+ android:textSize="@dimen/voicemail_promo_card_title_text_size"/>
+
+ <TextView
+ android:id="@+id/error_card_details"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:lineSpacingExtra="@dimen/voicemail_promo_card_line_spacing"
+ android:singleLine="false"
+ android:textSize="@dimen/voicemail_promo_card_message_size"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/voicemail_promo_card_action_vertical_padding"
+ android:paddingBottom="@dimen/voicemail_promo_card_action_vertical_padding"
+ android:paddingEnd="@dimen/voicemail_promo_card_action_end_padding"
+ android:gravity="end"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/secondary_action_raised"
+ style="@style/RaisedErrorActionStyle"
+ android:paddingEnd="@dimen/alert_action_between_padding"
+ android:layout_marginEnd="8dp"
+ android:nextFocusForward="@+id/secondary_action"
+ android:clickable="true"/>
+
+ <TextView
+ android:id="@+id/secondary_action"
+ style="@style/ErrorActionStyle"
+ android:paddingEnd="@dimen/voicemail_promo_card_action_between_padding"
+ android:background="?android:attr/selectableItemBackground"
+ android:nextFocusForward="@+id/primary_action"/>
+
+ <TextView
+ android:id="@+id/primary_action"
+ style="@style/ErrorActionStyle"
+ android:background="?android:attr/selectableItemBackground"
+ android:nextFocusForward="@+id/primary_action_raised"/>
+
+ <TextView
+ android:id="@+id/primary_action_raised"
+ style="@style/RaisedErrorActionStyle"
+ android:nextFocusForward="@+id/promo_card"
+ android:clickable="true"
+ />
+
+ </LinearLayout>
+ </LinearLayout>
+</android.support.v7.widget.CardView>
diff --git a/java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_tos_fragment.xml b/java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_tos_fragment.xml
new file mode 100644
index 000000000..4e143a59a
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/layout/voicemail_tos_fragment.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ScrollView
+ android:id="@+id/voicemail_tos_message"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="32dp"
+ android:orientation="vertical">
+ <ImageView
+ android:id="@+id/voicemail_image"
+ android:layout_width="@dimen/voicemail_tos_image_size"
+ android:layout_height="@dimen/voicemail_tos_image_size"
+ android:layout_gravity="center"
+ android:paddingBottom="32dp"
+ android:importantForAccessibility="no"/>
+ <TextView
+ android:id="@+id/tos_message_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal|top"
+ android:minHeight="20sp"
+ android:text="@string/verizon_terms_and_conditions_title"
+ style="@style/TosTitleStyle"/>
+ <TextView
+ android:id="@+id/tos_message_details"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:text="@string/verizon_terms_and_conditions_1.1_english"
+ style="@style/TosTextStyle"/>
+ </LinearLayout>
+ </ScrollView>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:elevation="1dp"
+ android:background="@drawable/shadow"/>
+
+ <LinearLayout
+ android:id="@+id/voicemail_tos_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="56dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:background="#ffffffff"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/voicemail_tos_button_decline"
+ style="@style/ErrorActionDeclineStyle"
+ android:background="?android:attr/selectableItemBackground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/verizon_terms_and_conditions_decline_english"/>
+ <android.support.v4.widget.Space
+ android:layout_width="8dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/voicemail_tos_button_accept"
+ style="@style/ErrorActionStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/verizon_terms_and_conditions_accept_english"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/java/com/android/dialer/voicemail/listui/error/res/values/dimens.xml b/java/com/android/dialer/voicemail/listui/error/res/values/dimens.xml
new file mode 100644
index 000000000..f40cc5f2b
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/values/dimens.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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>
+ <dimen name="alert_main_padding">24dp</dimen>
+ <dimen name="alert_action_between_padding">11dp</dimen>
+
+ <!-- Dimensions for promo card -->
+ <dimen name="voicemail_promo_card_icon_size">24dp</dimen>
+ <dimen name="voicemail_promo_card_main_padding">24dp</dimen>
+ <dimen name="voicemail_promo_card_title_padding">12dp</dimen>
+ <dimen name="voicemail_promo_card_action_vertical_padding">4dp</dimen>
+ <dimen name="voicemail_promo_card_action_end_padding">4dp</dimen>
+ <dimen name="voicemail_promo_card_action_between_padding">11dp</dimen>
+ <dimen name="voicemail_promo_card_line_spacing">4dp</dimen>
+ <dimen name="voicemail_promo_card_title_text_size">16sp</dimen>
+ <dimen name="voicemail_promo_card_message_size">14sp</dimen>
+
+ <dimen name="voicemail_tos_image_size">280dp</dimen>
+</resources>
diff --git a/java/com/android/dialer/voicemail/listui/error/res/values/strings.xml b/java/com/android/dialer/voicemail/listui/error/res/values/strings.xml
new file mode 100644
index 000000000..05082d8d9
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/values/strings.xml
@@ -0,0 +1,214 @@
+<!--
+ ~ 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
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="voicemail_error_activating_title">Activating visual voicemail</string>
+ <string name="voicemail_error_activating_message">You might not receive voicemail notifications until visual voicemail is fully activated. Call voicemail to retrieve new messages until voicemail is fully activated.</string>
+
+ <string name="voicemail_error_not_activate_no_signal_title">Can\'t activate visual voicemail</string>
+ <string name="voicemail_error_not_activate_no_signal_message">Make sure your phone has a mobile network connection and try again.</string>
+ <string name="voicemail_error_not_activate_no_signal_airplane_mode_message">Turn off airplane mode and try again.</string>
+
+ <string name="voicemail_error_no_signal_title">No connection</string>
+ <string name="voicemail_error_no_signal_message">You won\'t be notified for new voicemails. If you\'re on Wi\u2011Fi, you can check for voicemail by syncing now.</string>
+ <string name="voicemail_error_no_signal_airplane_mode_message">You won\'t be notified for new voicemails. Turn off airplane mode to sync your voicemail.</string>
+ <string name="voicemail_error_no_signal_cellular_required_message">Your phone needs a mobile data connection to check voicemail.</string>
+
+ <string name="voicemail_error_activation_failed_title">Can\'t activate visual voicemail</string>
+ <string name="voicemail_error_activation_failed_message">You can still call to check voicemail.</string>
+
+ <string name="voicemail_error_no_data_title">Can\'t update visual voicemail</string>
+ <string name="voicemail_error_no_data_message">Try again when your Wi\u2011Fi or mobile data connection is better. You can still call to check voicemail.</string>
+ <string name="voicemail_error_no_data_cellular_required_message">Try again when your mobile data connection is better. You can still call to check voicemail.</string>
+
+ <string name="voicemail_error_bad_config_title">Can\'t update visual voicemail</string>
+ <string name="voicemail_error_bad_config_message">You can still call to check voicemail.</string>
+
+ <string name="voicemail_error_communication_title">Can\'t update visual voicemail</string>
+ <string name="voicemail_error_communication_message">You can still call to check voicemail.</string>
+
+ <string name="voicemail_error_server_connection_title">Can\'t update visual voicemail</string>
+ <string name="voicemail_error_server_connection_message">You can still call to check voicemail.</string>
+
+ <string name="voicemail_error_server_title">Can\'t update visual voicemail</string>
+ <string name="voicemail_error_server_message">You can still call to check voicemail.</string>
+
+ <string name="voicemail_error_inbox_near_full_title">Inbox almost full</string>
+ <string name="voicemail_error_inbox_near_full_message">You won\'t be able to receive new voicemail if your inbox is full.</string>
+
+ <string name="voicemail_error_inbox_full_title">Can\'t receive new voicemails</string>
+ <string name="voicemail_error_inbox_full_message">Your inbox is full. Try deleting some messages to receive new voicemail.</string>
+
+ <string name="voicemail_error_inbox_full_turn_archive_on_title">Turn on extra storage and backup</string>
+ <string name="voicemail_error_inbox_full_turn_archive_on_message">Your mailbox is full. To free up space, turn on extra storage so Google can manage and backup your voicemail messages.</string>
+
+ <string name="voicemail_error_inbox_almost_full_turn_archive_on_title">Turn on extra storage and backup</string>
+ <string name="voicemail_error_inbox_almost_full_turn_archive_on_message">Your mailbox is almost full. To free up space, turn on extra storage so Google can manage and backup your voicemail messages.</string>
+
+ <string name="voicemail_error_pin_not_set_title">Set your voicemail PIN</string>
+ <string name="voicemail_error_pin_not_set_message">You\'ll need a voicemail PIN anytime you call to access your voicemail.</string>
+
+ <string name="voicemail_action_turn_off_airplane_mode">Airplane Mode Settings</string>
+ <string name="voicemail_action_set_pin">Set PIN</string>
+ <string name="voicemail_action_retry">Try Again</string>
+ <string name="voicemail_action_turn_archive_on">Turn on</string>
+ <string name="voicemail_action_dimiss">No Thanks</string>
+ <string name="voicemail_action_sync">Sync</string>
+ <string name="voicemail_action_call_voicemail">Call Voicemail</string>
+ <string name="voicemail_action_call_customer_support">Call Customer Support</string>
+
+ <string name="vvm3_error_vms_dns_failure_title">Something Went Wrong</string>
+ <string name="vvm3_error_vms_dns_failure_message">Sorry, we ran into a problem. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9001.</string>
+
+ <string name="vvm3_error_vmg_dns_failure_title">Something Went Wrong</string>
+ <string name="vvm3_error_vmg_dns_failure_message">Sorry, we ran into a problem. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9002.</string>
+
+ <string name="vvm3_error_spg_dns_failure_title">Something Went Wrong</string>
+ <string name="vvm3_error_spg_dns_failure_message">Sorry, we ran into a problem. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9003.</string>
+
+ <string name="vvm3_error_vms_no_cellular_title">Can\'t Connect to Your Voice Mailbox</string>
+ <string name="vvm3_error_vms_no_cellular_message">Sorry, we\'re having trouble connecting to your voice mailbox. If you\'re in an area with poor signal strength, wait until you have a strong signal and try again. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9004.</string>
+
+ <string name="vvm3_error_vmg_no_cellular_title">Can\'t Connect to Your Voice Mailbox</string>
+ <string name="vvm3_error_vmg_no_cellular_message">Sorry, we\'re having trouble connecting to your voice mailbox. If you\'re in an area with poor signal strength, wait until you have a strong signal and try again. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9005.</string>
+
+ <string name="vvm3_error_spg_no_cellular_title">Can\'t Connect to Your Voice Mailbox</string>
+ <string name="vvm3_error_spg_no_cellular_message">Sorry, we\'re having trouble connecting to your voice mailbox. If you\'re in an area with poor signal strength, wait until you have a strong signal and try again. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9006.</string>
+
+ <string name="vvm3_error_vms_timeout_title">Something Went Wrong</string>
+ <string name="vvm3_error_vms_timeout_message">Sorry, we ran into a problem. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9007.</string>
+
+ <string name="vvm3_error_vmg_timeout_title">Something Went Wrong</string>
+ <string name="vvm3_error_vmg_timeout_message">Sorry, we ran into a problem. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9008.</string>
+
+ <string name="vvm3_error_status_sms_timeout_title">Something Went Wrong</string>
+ <string name="vvm3_error_status_sms_timeout_message">Sorry, we\'re having trouble setting up your service. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9009.</string>
+
+ <string name="vvm3_error_subscriber_blocked_title">Can\'t Connect to Your Voice Mailbox</string>
+ <string name="vvm3_error_subscriber_blocked_message">Sorry, we\'re not able to connect to your voice mailbox at this time. Please try again later. If there is still a problem, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9990."</string>
+
+ <string name="vvm3_error_unknown_user_title">Set Up Voicemail</string>
+ <string name="vvm3_error_unknown_user_message">Voicemail is not set up on your account. Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9991.</string>
+
+ <string name="vvm3_error_unknown_device_title">Voicemail</string>
+ <string name="vvm3_error_unknown_device_message">Visual voicemail cannot be used on this device. Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9992.</string>
+
+ <string name="vvm3_error_invalid_password_title">Something Went Wrong</string>
+ <string name="vvm3_error_invalid_password_message">Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9993.</string>
+
+ <string name="vvm3_error_mailbox_not_initialized_title">Visual voicemail</string>
+ <string name="vvm3_error_mailbox_not_initialized_message">To complete visual voicemail setup, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9994.</string>
+
+ <string name="vvm3_error_service_not_provisioned_title">Visual voicemail</string>
+ <string name="vvm3_error_service_not_provisioned_message">To complete visual voicemail setup, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9995.</string>
+
+ <string name="vvm3_error_service_not_activated_title">Visual voicemail</string>
+ <string name="vvm3_error_service_not_activated_message">To activate visual voicemail, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9996.</string>
+
+ <string name="vvm3_error_user_blocked_title">Something Went Wrong</string>
+ <string name="vvm3_error_user_blocked_message">To complete visual voicemail setup, please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9998.</string>
+
+ <string name="vvm3_error_subscriber_unknown_title">Visual voicemail is disabled</string>
+ <string name="vvm3_error_subscriber_unknown_message">Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> to activate visual voicemail.</string>
+
+ <string name="vvm3_error_imap_getquota_error_title">Something Went Wrong</string>
+ <string name="vvm3_error_imap_getquota_error_message">Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9997.</string>
+
+ <string name="vvm3_error_imap_select_error_title">Something Went Wrong</string>
+ <string name="vvm3_error_imap_select_error_message">Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9989.</string>
+
+ <string name="vvm3_error_imap_error_title">Something Went Wrong</string>
+ <string name="vvm3_error_imap_error_message">Please contact Customer Service at <xliff:g example="(555) 555-5555" id="number">%1$s</xliff:g> and tell them the error code is 9999.</string>
+
+ <string translatable="false" name="verizon_domestic_customer_support_number">+18009220204</string>
+ <string translatable="false" name="verizon_domestic_customer_support_display_number">(800) 922–0204</string>
+
+ <string name="verizon_terms_and_conditions_title">Turn on visual voicemail</string>
+ <string name="verizon_terms_and_conditions_message"><xliff:g>%1$s</xliff:g> By turning on visual voicemail you agree to the Verizon Wireless terms and conditions:\n\n<xliff:g>%2$s</xliff:g></string>
+
+ <string name="dialer_terms_and_conditions_title">Turn on visual voicemail</string>
+
+ <string name="dialer_terms_and_conditions_existing_user_title">New! Read your voicemail</string>
+ <string name="dialer_terms_and_conditions_message"><xliff:g>%s</xliff:g></string>
+
+ <string translatable="false" name="verizon_terms_and_conditions_1.1_english">
+Visual Voice Mail (VVM) is a service that provides access to voice mail messages directly on the device, without the need to call *86. This service requires traditional Voice Mail but does not support all traditional Voice Mail features, which you can access by dialing *86 from your handset. Use of this feature will be billed on a per-megabyte basis, or according to any data package you have. Mobile to mobile minutes do not apply. Standard rates apply to any calls, emails or messages initiated from Visual Voice Mail.\n
+\n
+You may disable VVM in settings. This will revert you to basic voice mail. In some cases you may need to call customer care to cancel and if you cancel Visual Voice Mail you may lose all stored voice mails and information.\n
+\n
+For the Premium Visual Voice Mail service, some voice messages may not be completely transcribed; incomplete messages will end with [...]. Only the first 45 seconds of each voice message will be transcribed, so for longer messages, you will need to listen to the voice message itself. Any profane or offensive language also will not be transcribed and will appear as [...] in the transcription.\n
+\n
+Speech recordings may be collected and stored for a period of 30 days, solely for the purpose of testing and improving transcription technology and performance, subject to the Verizon Wireless Privacy Policy, which can be found at <xliff:g>%s</xliff:g>\n
+\n
+You understand that by selecting ACCEPT, your messages will be stored and anyone in possession of this device will have access to your voice mail. You further understand that your voice mail messages may be stored in electronic format on this device. To limit unauthorized access to your voice mail, you should consider locking your phone when not in use. Not available in all areas or over Wi\u2011Fi.\n
+\n
+If you do not accept all of these terms and conditions, do not use Visual Voice Mail. </string>
+
+ <string translatable="false" name="verizon_terms_and_conditions_1.1_spanish">
+El buzón de voz visual (VVM) es un servicio que permite acceder a los mensajes del buzón de voz directamente en el dispositivo, sin necesidad de llamar al *86. Este servicio requiere el buzón de voz tradicional, pero no admite todas las funciones del buzón de voz tradicional, a las que se puede acceder marcando *86 en el teléfono. El uso de esta función se factura por megabyte o conforme a cualquier paquete de datos que tenga. No se aplican los minutos de un dispositivo móvil a otro. Se aplican tarifas estándar a todos los correos electrónicos, las llamadas o los mensajes originados en el buzón de voz visual.\n
+\n
+Puede inhabilitar el VVM en la configuración. Esto le permite volver al buzón de voz básico. En algunos casos, es posible que deba llamar al servicio de atención al cliente para cancelar el buzón de voz visual. Si lo cancela, puede perder la información y los mensajes de voz almacenados.\n
+\n
+En el caso del servicio de buzón de voz visual premium, es posible que algunos mensajes no se transcriban totalmente; los mensajes incompletos finalizan con "[…]". Solo se transcriben los primeros 45 segundos de cada mensaje de voz, por lo que debe escuchar los mensajes de voz más largos. Tampoco se transcribe ninguna palabra ofensiva o profana; aparece como "[…]" en la transcripción.\n
+\n
+Es posible que reunamos y almacenemos grabaciones de voz durante 30 días, con el único fin de probar y mejorar el rendimiento y la tecnología de la transcripción, sujeto a la Política de privacidad de Verizon Wireless, disponible en <xliff:g>%s</xliff:g>\n
+\n
+Entiende que, al seleccionar ACEPTAR, sus mensajes se almacenarán, y cualquier persona que disponga de este dispositivo tendrá acceso al buzón de voz. Entiende, además, que los mensajes de voz pueden almacenarse en formato electrónico en este dispositivo. Para limitar el acceso no autorizado al buzón de voz, debe considerar el bloqueo del teléfono cuando no está en uso. No está disponible en todas las áreas ni mediante Wi\u2011Fi.\n
+\n
+Si no acepta todos estos términos y condiciones, no use el buzón de voz visual.
+ </string>
+
+ <string name="dialer_terms_and_conditions_1.0">
+ See and listen to your messages, without having to call voicemail. Transcripts of your voicemail are provided by Google’s free transcription service. <xliff:g>%s</xliff:g>
+ </string>
+
+ <string name="dialer_terms_and_conditions_existing_user">
+ Transcripts of your voicemail are now provided by Google’s free transcription service. %s
+ </string>
+
+ <string name="dialer_terms_and_conditions_for_verizon_1.0">
+ See and listen to your messages, without having to call voicemail.
+ </string>
+
+ <string name="dialer_terms_and_conditions_learn_more">Learn&#160;more</string>
+ <string translatable="false" name="dialer_terms_and_conditions_learn_more_url">https://support.google.com/phoneapp/answer/2811844?hl=en%26ref_topic=7539039</string>
+
+ <string translatable="false" name="verizon_terms_and_conditions_policy_url">http://www.verizon.com/about/privacy/policy/</string>
+
+ <string translatable="false" name="verizon_terms_and_conditions_accept_english">Turn On</string>
+ <string translatable="false" name="verizon_terms_and_conditions_accept_spanish">Aceptar</string>
+ <string translatable="false" name="verizon_terms_and_conditions_decline_english">No Thanks</string>
+ <string translatable="false" name="verizon_terms_and_conditions_decline_spanish">Rechazar</string>
+
+ <string translatable="false" name="dialer_terms_and_conditions_accept_english">Turn On</string>
+ <string translatable="false" name="dialer_terms_and_conditions_accept_spanish">Aceptar</string>
+ <string translatable="false" name="dialer_terms_and_conditions_decline_english">No Thanks</string>
+ <string translatable="false" name="dialer_terms_and_conditions_decline_spanish">Rechazar</string>
+
+ <string name="dialer_terms_and_conditions_existing_user_ack">Ok, got it</string>
+ <string name="dialer_terms_and_conditions_existing_user_setings">Settings</string>
+
+ <string name="terms_and_conditions_decline_dialog_title">Disable visual voicemail?</string>
+
+ <string name="verizon_terms_and_conditions_decline_dialog_message">Visual voicemail will be disabled if the terms and conditions are declined.</string>
+ <string name="verizon_terms_and_conditions_decline_dialog_downgrade">Disable</string>
+
+ <string name="dialer_terms_and_conditions_decline_dialog_message">Visual voicemail will be disabled if you turn off visual voicemail.</string>
+ <string name="dialer_terms_and_conditions_decline_dialog_downgrade">Disable</string>
+
+ <string name="verizon_terms_and_conditions_decline_set_pin_dialog_message">Voicemail will only be accessible by calling *86. Set a new voicemail PIN to proceed.</string>
+ <string name="verizon_terms_and_conditions_decline_set_pin_dialog_set_pin">Set PIN</string>
+</resources>
diff --git a/java/com/android/dialer/voicemail/listui/error/res/values/styles.xml b/java/com/android/dialer/voicemail/listui/error/res/values/styles.xml
new file mode 100644
index 000000000..bf70240b6
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/error/res/values/styles.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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>
+ <style name="ErrorActionStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:gravity">end|center_vertical</item>
+ <item name="android:layout_marginStart">8dp</item>
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:padding">8dp</item>
+ <item name="android:textColor">@color/dialer_theme_color</item>
+ <item name="android:fontFamily">"sans-serif-medium"</item>
+ <item name="android:focusable">true</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:minHeight">48dp</item>
+ </style>
+
+ <style name="ErrorActionDeclineStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">48dp</item>
+ <item name="android:gravity">end|center_vertical</item>
+ <item name="android:layout_marginStart">8dp</item>
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:padding">8dp</item>
+ <item name="android:textColor">#757575</item>
+ <item name="android:fontFamily">"sans-serif-medium"</item>
+ <item name="android:focusable">true</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:minHeight">48dp</item>
+ </style>
+
+ <style name="RaisedErrorActionStyle" parent="Widget.AppCompat.Button.Colored">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:colorButtonNormal">@color/dialer_theme_color</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:layout_height">@dimen/call_log_action_height</item>
+ </style>
+
+ <style name="TosTitleStyle">
+ <item name="android:textColor">@color/primary_text_color</item>
+ <item name="android:textSize">20sp</item>
+ <item name="android:fontFamily">"sans-serif-medium"</item>
+ </style>
+
+ <style name="TosLinkStyle">
+ <item name="android:textColor">@color/dialer_theme_color</item>
+ <item name="android:fontFamily">"sans-serif-medium"</item>
+ </style>
+
+ <style name="TosTextStyle">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/dialer_primary_text_color</item>
+ <item name="android:fontFamily">"sans-serif-regular"</item>
+ <item name="android:lineSpacingExtra">8sp</item>
+ </style>
+
+ <style name="TosDetailsTextStyle">
+ <item name="android:textSize">12sp</item>
+ <item name="android:textColor">@color/dialer_secondary_text_color</item>
+ <item name="android:fontFamily">"sans-serif-regular"</item>
+ <item name="android:lineSpacingExtra">12sp</item>
+ </style>
+</resources>