Home | History | Annotate | Download | only in error
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.dialer.voicemail.listui.error;
     18 
     19 import android.content.Context;
     20 import android.preference.PreferenceManager;
     21 import android.provider.VoicemailContract.Status;
     22 import android.support.annotation.Nullable;
     23 import android.telecom.PhoneAccountHandle;
     24 import com.android.dialer.common.LogUtil;
     25 import com.android.dialer.common.PerAccountSharedPreferences;
     26 import com.android.dialer.logging.DialerImpression;
     27 import com.android.dialer.logging.Logger;
     28 import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action;
     29 import com.android.voicemail.VoicemailClient;
     30 import com.android.voicemail.VoicemailComponent;
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 
     34 /**
     35  * Create error message from {@link VoicemailStatus} for OMTP visual voicemail. This is also the
     36  * default behavior if other message creator does not handle the status.
     37  */
     38 public class OmtpVoicemailMessageCreator {
     39 
     40   private static final float QUOTA_NEAR_FULL_THRESHOLD = 0.9f;
     41   private static final float QUOTA_FULL_THRESHOLD = 0.99f;
     42   protected static final String VOICEMAIL_PROMO_DISMISSED_KEY =
     43       "voicemail_archive_promo_was_dismissed";
     44   protected static final String VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY =
     45       "voicemail_archive_almost_full_promo_was_dismissed";
     46 
     47   @Nullable
     48   public static VoicemailErrorMessage create(
     49       Context context, VoicemailStatus status, final VoicemailStatusReader statusReader) {
     50     VoicemailErrorMessage tosMessage =
     51         new VoicemailTosMessageCreator(context, status, statusReader).maybeCreateTosMessage();
     52     if (tosMessage != null) {
     53       return tosMessage;
     54     }
     55 
     56     if (Status.CONFIGURATION_STATE_OK == status.configurationState
     57         && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState
     58         && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) {
     59       return checkQuota(context, status, statusReader);
     60     }
     61     // Initial state when the source is activating. Other error might be written into data and
     62     // notification channel during activation.
     63     if (Status.CONFIGURATION_STATE_CONFIGURING == status.configurationState
     64         && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState
     65         && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) {
     66       return new VoicemailErrorMessage(
     67           context.getString(R.string.voicemail_error_activating_title),
     68           context.getString(R.string.voicemail_error_activating_message),
     69           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()));
     70     }
     71 
     72     if (Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION == status.notificationChannelState) {
     73       return createNoSignalMessage(context, status);
     74     }
     75 
     76     if (Status.CONFIGURATION_STATE_FAILED == status.configurationState) {
     77       return new VoicemailErrorMessage(
     78           context.getString(R.string.voicemail_error_activation_failed_title),
     79           context.getString(R.string.voicemail_error_activation_failed_message),
     80           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
     81           VoicemailErrorMessage.createRetryAction(context, status));
     82     }
     83 
     84     if (Status.DATA_CHANNEL_STATE_NO_CONNECTION == status.dataChannelState) {
     85       return new VoicemailErrorMessage(
     86           context.getString(R.string.voicemail_error_no_data_title),
     87           context.getString(R.string.voicemail_error_no_data_message),
     88           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
     89           VoicemailErrorMessage.createRetryAction(context, status));
     90     }
     91 
     92     if (Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED == status.dataChannelState) {
     93       return new VoicemailErrorMessage(
     94           context.getString(R.string.voicemail_error_no_data_title),
     95           context.getString(R.string.voicemail_error_no_data_cellular_required_message),
     96           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
     97           VoicemailErrorMessage.createRetryAction(context, status));
     98     }
     99 
    100     if (Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION == status.dataChannelState) {
    101       return new VoicemailErrorMessage(
    102           context.getString(R.string.voicemail_error_bad_config_title),
    103           context.getString(R.string.voicemail_error_bad_config_message),
    104           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
    105           VoicemailErrorMessage.createRetryAction(context, status));
    106     }
    107 
    108     if (Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR == status.dataChannelState) {
    109       return new VoicemailErrorMessage(
    110           context.getString(R.string.voicemail_error_communication_title),
    111           context.getString(R.string.voicemail_error_communication_message),
    112           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
    113           VoicemailErrorMessage.createRetryAction(context, status));
    114     }
    115 
    116     if (Status.DATA_CHANNEL_STATE_SERVER_ERROR == status.dataChannelState) {
    117       return new VoicemailErrorMessage(
    118           context.getString(R.string.voicemail_error_server_title),
    119           context.getString(R.string.voicemail_error_server_message),
    120           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
    121           VoicemailErrorMessage.createRetryAction(context, status));
    122     }
    123 
    124     if (Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR == status.dataChannelState) {
    125       return new VoicemailErrorMessage(
    126           context.getString(R.string.voicemail_error_server_connection_title),
    127           context.getString(R.string.voicemail_error_server_connection_message),
    128           VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()),
    129           VoicemailErrorMessage.createRetryAction(context, status));
    130     }
    131 
    132     // This should be an assertion error, but there's a bug in NYC-DR (a bug) that will
    133     // sometimes give status mixed from multiple SIMs. There's no meaningful message to be displayed
    134     // from it, so just suppress the message.
    135     LogUtil.e("OmtpVoicemailMessageCreator.create", "Unhandled status: " + status);
    136     return null;
    137   }
    138 
    139   public static boolean isSyncBlockingError(VoicemailStatus status) {
    140     if (status.notificationChannelState != Status.NOTIFICATION_CHANNEL_STATE_OK) {
    141       return true;
    142     }
    143 
    144     if (status.dataChannelState != Status.DATA_CHANNEL_STATE_OK) {
    145       return true;
    146     }
    147 
    148     switch (status.configurationState) {
    149       case Status.CONFIGURATION_STATE_OK:
    150         // allow activation to be queued again in case it is interrupted
    151       case Status.CONFIGURATION_STATE_CONFIGURING:
    152         return false;
    153       default:
    154         return true;
    155     }
    156   }
    157 
    158   @Nullable
    159   private static VoicemailErrorMessage checkQuota(
    160       Context context, VoicemailStatus status, VoicemailStatusReader statusReader) {
    161     if (status.quotaOccupied != Status.QUOTA_UNAVAILABLE
    162         && status.quotaTotal != Status.QUOTA_UNAVAILABLE) {
    163       return createInboxErrorMessage(context, status, statusReader);
    164     }
    165     Logger.get(context).logImpression(DialerImpression.Type.VVM_QUOTA_CHECK_UNAVAILABLE);
    166     return null;
    167   }
    168 
    169   @Nullable
    170   private static VoicemailErrorMessage createInboxErrorMessage(
    171       Context context, VoicemailStatus status, VoicemailStatusReader statusReader) {
    172 
    173     float voicemailOccupiedFraction = (float) status.quotaOccupied / (float) status.quotaTotal;
    174 
    175     if (voicemailOccupiedFraction < QUOTA_NEAR_FULL_THRESHOLD) {
    176       return null;
    177     }
    178 
    179     boolean isFull = voicemailOccupiedFraction >= QUOTA_FULL_THRESHOLD;
    180 
    181     PhoneAccountHandle phoneAccountHandle = status.getPhoneAccountHandle();
    182 
    183     PerAccountSharedPreferences sharedPreferenceForAccount =
    184         new PerAccountSharedPreferences(
    185             context, phoneAccountHandle, PreferenceManager.getDefaultSharedPreferences(context));
    186 
    187     VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient();
    188 
    189     boolean shouldShowPromoForArchive =
    190         !isPromoForArchiveDismissed(sharedPreferenceForAccount, isFull)
    191             && !voicemailClient.isVoicemailArchiveEnabled(context, phoneAccountHandle)
    192             && voicemailClient.isVoicemailArchiveAvailable(context);
    193 
    194     if (!shouldShowPromoForArchive) {
    195       if (isFull) {
    196         Logger.get(context)
    197             .logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_ERROR_MESSAGE);
    198         return new VoicemailErrorMessage(
    199             context.getString(R.string.voicemail_error_inbox_full_title),
    200             context.getString(R.string.voicemail_error_inbox_full_message));
    201       } else {
    202         Logger.get(context)
    203             .logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_ERROR_MESSAGE);
    204         return new VoicemailErrorMessage(
    205             context.getString(R.string.voicemail_error_inbox_near_full_title),
    206             context.getString(R.string.voicemail_error_inbox_near_full_message));
    207       }
    208     }
    209 
    210     String title;
    211     CharSequence message;
    212     DialerImpression.Type enabledImpression;
    213     DialerImpression.Type dismissedImpression;
    214     String dismissedKey;
    215 
    216     if (isFull) {
    217       Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_PROMO);
    218       title = context.getString(R.string.voicemail_error_inbox_full_turn_archive_on_title);
    219       message = context.getText(R.string.voicemail_error_inbox_full_turn_archive_on_message);
    220       enabledImpression = DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_FULL_PROMO;
    221       dismissedImpression = DialerImpression.Type.VVM_USER_DISMISSED_VM_FULL_PROMO;
    222       dismissedKey = VOICEMAIL_PROMO_DISMISSED_KEY;
    223     } else {
    224       Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_PROMO);
    225       title = context.getString(R.string.voicemail_error_inbox_almost_full_turn_archive_on_title);
    226       message = context.getText(R.string.voicemail_error_inbox_almost_full_turn_archive_on_message);
    227       enabledImpression = DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_ALMOST_FULL_PROMO;
    228       dismissedImpression = DialerImpression.Type.VVM_USER_DISMISSED_VM_ALMOST_FULL_PROMO;
    229       dismissedKey = VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY;
    230     }
    231 
    232     return createVMQuotaPromo(
    233         context,
    234         phoneAccountHandle,
    235         status,
    236         statusReader,
    237         voicemailClient,
    238         sharedPreferenceForAccount,
    239         title,
    240         message,
    241         enabledImpression,
    242         dismissedImpression,
    243         dismissedKey);
    244   }
    245 
    246   private static boolean isPromoForArchiveDismissed(
    247       PerAccountSharedPreferences sharedPreferenceForAccount, boolean isFull) {
    248     if (isFull) {
    249       return sharedPreferenceForAccount.getBoolean(VOICEMAIL_PROMO_DISMISSED_KEY, false);
    250     } else {
    251       return sharedPreferenceForAccount.getBoolean(
    252           VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY, false);
    253     }
    254   }
    255 
    256   private static VoicemailErrorMessage createVMQuotaPromo(
    257       Context context,
    258       PhoneAccountHandle phoneAccountHandle,
    259       VoicemailStatus status,
    260       VoicemailStatusReader statusReader,
    261       VoicemailClient voicemailClient,
    262       PerAccountSharedPreferences sharedPreferenceForAccount,
    263       String title,
    264       CharSequence message,
    265       DialerImpression.Type impressionToLogOnEnable,
    266       DialerImpression.Type impressionToLogOnDismiss,
    267       String preferenceKeyToUpdate) {
    268     return new VoicemailErrorMessage(
    269         title,
    270         message,
    271         VoicemailErrorMessage.createTurnArchiveOnAction(
    272             context,
    273             impressionToLogOnEnable,
    274             status,
    275             statusReader,
    276             voicemailClient,
    277             phoneAccountHandle),
    278         VoicemailErrorMessage.createDismissTurnArchiveOnAction(
    279             context,
    280             impressionToLogOnDismiss,
    281             statusReader,
    282             sharedPreferenceForAccount,
    283             preferenceKeyToUpdate));
    284   }
    285 
    286   @Nullable
    287   private static VoicemailErrorMessage createNoSignalMessage(
    288       Context context, VoicemailStatus status) {
    289     CharSequence title;
    290     CharSequence description;
    291     List<Action> actions = new ArrayList<>();
    292     if (Status.CONFIGURATION_STATE_OK == status.configurationState) {
    293       if (Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED == status.dataChannelState) {
    294         title = context.getString(R.string.voicemail_error_no_signal_title);
    295         description =
    296             context.getString(R.string.voicemail_error_no_signal_cellular_required_message);
    297       } else {
    298         title = context.getString(R.string.voicemail_error_no_signal_title);
    299         if (status.isAirplaneMode) {
    300           description = context.getString(R.string.voicemail_error_no_signal_airplane_mode_message);
    301         } else {
    302           description = context.getString(R.string.voicemail_error_no_signal_message);
    303         }
    304         actions.add(VoicemailErrorMessage.createSyncAction(context, status));
    305       }
    306     } else {
    307       title = context.getString(R.string.voicemail_error_not_activate_no_signal_title);
    308       if (status.isAirplaneMode) {
    309         description =
    310             context.getString(
    311                 R.string.voicemail_error_not_activate_no_signal_airplane_mode_message);
    312       } else {
    313         description = context.getString(R.string.voicemail_error_not_activate_no_signal_message);
    314         actions.add(VoicemailErrorMessage.createRetryAction(context, status));
    315       }
    316     }
    317     if (status.isAirplaneMode) {
    318       actions.add(VoicemailErrorMessage.createChangeAirplaneModeAction(context));
    319     }
    320     return new VoicemailErrorMessage(title, description, actions);
    321   }
    322 }
    323