Home | History | Annotate | Download | only in error
      1 /*
      2  * Copyright (C) 2017 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.app.AlertDialog;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.DialogInterface;
     23 import android.content.Intent;
     24 import android.content.SharedPreferences;
     25 import android.net.Uri;
     26 import android.os.Build;
     27 import android.preference.PreferenceManager;
     28 import android.support.annotation.Nullable;
     29 import android.telecom.PhoneAccountHandle;
     30 import android.telephony.TelephonyManager;
     31 import android.text.Layout;
     32 import android.text.SpannableString;
     33 import android.text.Spanned;
     34 import android.text.TextUtils;
     35 import android.text.style.AlignmentSpan;
     36 import android.text.style.TextAppearanceSpan;
     37 import android.text.style.URLSpan;
     38 import android.view.View;
     39 import android.view.View.OnClickListener;
     40 import com.android.dialer.common.LogUtil;
     41 import com.android.dialer.compat.telephony.TelephonyManagerCompat;
     42 import com.android.dialer.configprovider.ConfigProviderBindings;
     43 import com.android.dialer.constants.Constants;
     44 import com.android.dialer.logging.DialerImpression;
     45 import com.android.dialer.logging.Logger;
     46 import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action;
     47 import com.android.dialer.voicemail.settings.VoicemailSettingsFragment;
     48 import com.android.voicemail.VisualVoicemailTypeExtensions;
     49 import com.android.voicemail.VoicemailClient;
     50 import com.android.voicemail.VoicemailComponent;
     51 import com.android.voicemail.VoicemailVersionConstants;
     52 import java.util.Locale;
     53 
     54 /**
     55  * Create error message from {@link VoicemailStatus} for voicemail. This is will show different
     56  * terms of service for Verizon and for other carriers.
     57  */
     58 public class VoicemailTosMessageCreator {
     59   private static final String ISO639_SPANISH = "es";
     60 
     61   private final Context context;
     62   private final VoicemailStatus status;
     63   private final VoicemailStatusReader statusReader;
     64   private final SharedPreferences preferences;
     65 
     66   VoicemailTosMessageCreator(
     67       final Context context,
     68       final VoicemailStatus status,
     69       final VoicemailStatusReader statusReader) {
     70     this.context = context;
     71     this.status = status;
     72     this.statusReader = statusReader;
     73     this.preferences =
     74         PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
     75   }
     76 
     77   @Nullable
     78   VoicemailErrorMessage maybeCreateTosMessage() {
     79     if (!canShowTos()) {
     80       return null;
     81     } else if (shouldShowTos()) {
     82       logTosCreatedImpression();
     83       return getTosMessage();
     84     } else if (shouldShowPromo()) {
     85       return getPromoMessage();
     86     } else {
     87       return null;
     88     }
     89   }
     90 
     91   private VoicemailErrorMessage getTosMessage() {
     92     return new VoicemailTosMessage(
     93             getNewUserTosTitle(),
     94             getNewUserTosMessageText(),
     95             new Action(
     96                 getDeclineText(),
     97                 new OnClickListener() {
     98                   @Override
     99                   public void onClick(View v) {
    100                     LogUtil.i("VoicemailTosMessageCreator.getTosMessage", "decline clicked");
    101                     PhoneAccountHandle handle =
    102                         new PhoneAccountHandle(
    103                             ComponentName.unflattenFromString(status.phoneAccountComponentName),
    104                             status.phoneAccountId);
    105                     logTosDeclinedImpression();
    106                     showDeclineTosDialog(handle);
    107                   }
    108                 }),
    109             new Action(
    110                 getAcceptText(),
    111                 new OnClickListener() {
    112                   @Override
    113                   public void onClick(View v) {
    114                     LogUtil.i("VoicemailTosMessageCreator.getTosMessage", "accept clicked");
    115                     recordTosAcceptance();
    116                     // Accepting the TOS also acknowledges the latest features
    117                     recordFeatureAcknowledgement();
    118                     logTosAcceptedImpression();
    119                     statusReader.refresh();
    120                   }
    121                 },
    122                 true /* raised */))
    123         .setModal(true)
    124         .setImageResourceId(R.drawable.voicemail_tos_image);
    125   }
    126 
    127   private VoicemailErrorMessage getPromoMessage() {
    128     return new VoicemailTosMessage(
    129             getExistingUserTosTitle(),
    130             getExistingUserTosMessageText(),
    131             new Action(
    132                 context.getString(R.string.dialer_terms_and_conditions_existing_user_setings),
    133                 new OnClickListener() {
    134                   @Override
    135                   public void onClick(View v) {
    136                     LogUtil.i("VoicemailTosMessageCreator.getPromoMessage", "open settings");
    137                     Intent intent =
    138                         new Intent(Intent.ACTION_VIEW)
    139                             .setComponent(
    140                                 new ComponentName(context, Constants.get().getSettingsActivity()))
    141                             .setData(
    142                                 Uri.fromParts(
    143                                     "header", VoicemailSettingsFragment.class.getName(), null));
    144                     context.startActivity(intent);
    145                   }
    146                 }),
    147             new Action(
    148                 context.getString(R.string.dialer_terms_and_conditions_existing_user_ack),
    149                 new OnClickListener() {
    150                   @Override
    151                   public void onClick(View v) {
    152                     LogUtil.i("VoicemailTosMessageCreator.getPromoMessage", "acknowledge clicked");
    153                     // Feature acknowledgement also means accepting TOS
    154                     recordTosAcceptance();
    155                     recordFeatureAcknowledgement();
    156                     statusReader.refresh();
    157                   }
    158                 },
    159                 true /* raised */))
    160         .setModal(true)
    161         .setImageResourceId(R.drawable.voicemail_tos_image);
    162   }
    163 
    164   private boolean canShowTos() {
    165     if (!isValidVoicemailType(status.type)) {
    166       LogUtil.i("VoicemailTosMessageCreator.canShowTos", "unsupported type: " + status.type);
    167       return false;
    168     }
    169 
    170     if (status.getPhoneAccountHandle() == null
    171         || status.getPhoneAccountHandle().getComponentName() == null) {
    172       LogUtil.i("VoicemailTosMessageCreator.canShowTos", "invalid phone account");
    173       return false;
    174     }
    175 
    176     return true;
    177   }
    178 
    179   private boolean shouldShowTos() {
    180     if (hasAcceptedTos()) {
    181       LogUtil.i("VoicemailTosMessageCreator.shouldShowTos", "already accepted TOS");
    182       return false;
    183     }
    184 
    185     if (isVvm3()) {
    186       LogUtil.i("VoicemailTosMessageCreator.shouldShowTos", "showing TOS for verizon");
    187       return true;
    188     }
    189 
    190     if (isVoicemailTranscriptionAvailable() && !isLegacyVoicemailUser()) {
    191       LogUtil.i(
    192           "VoicemailTosMessageCreator.shouldShowTos", "showing TOS for Google transcription users");
    193       return true;
    194     }
    195 
    196     return false;
    197   }
    198 
    199   private boolean shouldShowPromo() {
    200     if (hasAcknowledgedFeatures()) {
    201       LogUtil.i(
    202           "VoicemailTosMessageCreator.shouldShowPromo", "already acknowledeged latest features");
    203       return false;
    204     }
    205 
    206     if (isVoicemailTranscriptionAvailable()) {
    207       LogUtil.i(
    208           "VoicemailTosMessageCreator.shouldShowPromo",
    209           "showing promo for Google transcription users");
    210       return true;
    211     }
    212 
    213     return false;
    214   }
    215 
    216   private static boolean isValidVoicemailType(String type) {
    217     if (type == null) {
    218       return false;
    219     }
    220     switch (type) {
    221       case TelephonyManager.VVM_TYPE_OMTP:
    222       case TelephonyManager.VVM_TYPE_CVVM:
    223       case VisualVoicemailTypeExtensions.VVM_TYPE_VVM3:
    224         return true;
    225       default:
    226         return false;
    227     }
    228   }
    229 
    230   private boolean isVoicemailTranscriptionAvailable() {
    231     return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
    232         && ConfigProviderBindings.get(context)
    233             .getBoolean("voicemail_transcription_available", false);
    234   }
    235 
    236   private void showDeclineTosDialog(final PhoneAccountHandle handle) {
    237     if (isVvm3() && Vvm3VoicemailMessageCreator.PIN_NOT_SET == status.configurationState) {
    238       LogUtil.i(
    239           "VoicemailTosMessageCreator.showDeclineTosDialog", "PIN_NOT_SET, showing set PIN dialog");
    240       showSetPinBeforeDeclineDialog(handle);
    241       return;
    242     }
    243     LogUtil.i(
    244         "VoicemailTosMessageCreator.showDeclineVerizonTosDialog",
    245         "showing decline ToS dialog, status=" + status);
    246     final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
    247     AlertDialog.Builder builder = new AlertDialog.Builder(context);
    248     builder.setTitle(R.string.terms_and_conditions_decline_dialog_title);
    249     builder.setMessage(getTosDeclinedDialogMessageId());
    250     builder.setPositiveButton(
    251         getTosDeclinedDialogDowngradeId(),
    252         new DialogInterface.OnClickListener() {
    253           @Override
    254           public void onClick(DialogInterface dialog, int which) {
    255             Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINED);
    256             VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient();
    257             if (voicemailClient.isVoicemailModuleEnabled()) {
    258               voicemailClient.setVoicemailEnabled(context, status.getPhoneAccountHandle(), false);
    259             } else {
    260               TelephonyManagerCompat.setVisualVoicemailEnabled(telephonyManager, handle, false);
    261             }
    262           }
    263         });
    264 
    265     builder.setNegativeButton(
    266         android.R.string.cancel,
    267         new DialogInterface.OnClickListener() {
    268           @Override
    269           public void onClick(DialogInterface dialog, int which) {
    270             dialog.dismiss();
    271           }
    272         });
    273 
    274     builder.setCancelable(true);
    275     builder.show();
    276   }
    277 
    278   private void showSetPinBeforeDeclineDialog(PhoneAccountHandle phoneAccountHandle) {
    279     AlertDialog.Builder builder = new AlertDialog.Builder(context);
    280     builder.setMessage(R.string.verizon_terms_and_conditions_decline_set_pin_dialog_message);
    281     builder.setPositiveButton(
    282         R.string.verizon_terms_and_conditions_decline_set_pin_dialog_set_pin,
    283         new DialogInterface.OnClickListener() {
    284           @Override
    285           public void onClick(DialogInterface dialog, int which) {
    286             Logger.get(context)
    287                 .logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINE_CHANGE_PIN_SHOWN);
    288             Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
    289             intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
    290             context.startActivity(intent);
    291           }
    292         });
    293 
    294     builder.setNegativeButton(
    295         android.R.string.cancel,
    296         new DialogInterface.OnClickListener() {
    297           @Override
    298           public void onClick(DialogInterface dialog, int which) {
    299             dialog.dismiss();
    300           }
    301         });
    302 
    303     builder.setCancelable(true);
    304     builder.show();
    305   }
    306 
    307   private boolean isVvm3() {
    308     return VisualVoicemailTypeExtensions.VVM_TYPE_VVM3.equals(status.type);
    309   }
    310 
    311   private boolean useSpanish() {
    312     return Locale.getDefault().getLanguage().equals(new Locale(ISO639_SPANISH).getLanguage());
    313   }
    314 
    315   private boolean hasAcceptedTos() {
    316     if (isVvm3()) {
    317       return preferences.getInt(VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 0)
    318           >= VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION;
    319     } else {
    320       return preferences.getInt(VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 0)
    321           >= VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION;
    322     }
    323   }
    324 
    325   private void recordTosAcceptance() {
    326     if (isVvm3()) {
    327       preferences
    328           .edit()
    329           .putInt(
    330               VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY,
    331               VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION)
    332           .apply();
    333     } else {
    334       preferences
    335           .edit()
    336           .putInt(
    337               VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY,
    338               VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION)
    339           .apply();
    340     }
    341 
    342     PhoneAccountHandle handle =
    343         new PhoneAccountHandle(
    344             ComponentName.unflattenFromString(status.phoneAccountComponentName),
    345             status.phoneAccountId);
    346     VoicemailComponent.get(context).getVoicemailClient().onTosAccepted(context, handle);
    347   }
    348 
    349   private boolean hasAcknowledgedFeatures() {
    350     if (isVvm3()) {
    351       return true;
    352     }
    353 
    354     return preferences.getInt(
    355             VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
    356         >= VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION;
    357   }
    358 
    359   private void recordFeatureAcknowledgement() {
    360     preferences
    361         .edit()
    362         .putInt(
    363             VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY,
    364             VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION)
    365         .apply();
    366   }
    367 
    368   private boolean isLegacyVoicemailUser() {
    369     return preferences.getInt(
    370             VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0)
    371         == VoicemailVersionConstants.LEGACY_VOICEMAIL_FEATURE_VERSION;
    372   }
    373 
    374   private void logTosCreatedImpression() {
    375     if (isVvm3()) {
    376       Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_CREATED);
    377     } else {
    378       Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_CREATED);
    379     }
    380   }
    381 
    382   private void logTosDeclinedImpression() {
    383     if (isVvm3()) {
    384       Logger.get(context)
    385           .logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_DECLINE_CLICKED);
    386     } else {
    387       Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_DECLINE_CLICKED);
    388     }
    389   }
    390 
    391   private void logTosAcceptedImpression() {
    392     if (isVvm3()) {
    393       Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_ACCEPTED);
    394     } else {
    395       Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_ACCEPTED);
    396     }
    397   }
    398 
    399   private CharSequence getVvm3Tos() {
    400     String policyUrl = context.getString(R.string.verizon_terms_and_conditions_policy_url);
    401     return useSpanish()
    402         ? context.getString(R.string.verizon_terms_and_conditions_1_1_spanish, policyUrl)
    403         : context.getString(R.string.verizon_terms_and_conditions_1_1_english, policyUrl);
    404   }
    405 
    406   private CharSequence getVvmDialerTos() {
    407     return context.getString(R.string.dialer_terms_and_conditions_for_verizon_1_0);
    408   }
    409 
    410   private CharSequence getNewUserDialerTos() {
    411     if (!isVoicemailTranscriptionAvailable()) {
    412       return "";
    413     }
    414 
    415     String learnMoreText = context.getString(R.string.dialer_terms_and_conditions_learn_more);
    416     return context.getString(R.string.dialer_terms_and_conditions_1_0, learnMoreText);
    417   }
    418 
    419   private CharSequence getExistingUserDialerTos() {
    420     if (!isVoicemailTranscriptionAvailable()) {
    421       return "";
    422     }
    423 
    424     String learnMoreText = context.getString(R.string.dialer_terms_and_conditions_learn_more);
    425     return context.getString(R.string.dialer_terms_and_conditions_existing_user, learnMoreText);
    426   }
    427 
    428   private CharSequence getAcceptText() {
    429     if (isVvm3()) {
    430       return useSpanish()
    431           ? context.getString(R.string.verizon_terms_and_conditions_accept_spanish)
    432           : context.getString(R.string.verizon_terms_and_conditions_accept_english);
    433     } else {
    434       return useSpanish()
    435           ? context.getString(R.string.dialer_terms_and_conditions_accept_spanish)
    436           : context.getString(R.string.dialer_terms_and_conditions_accept_english);
    437     }
    438   }
    439 
    440   private CharSequence getDeclineText() {
    441     if (isVvm3()) {
    442       return useSpanish()
    443           ? context.getString(R.string.verizon_terms_and_conditions_decline_spanish)
    444           : context.getString(R.string.verizon_terms_and_conditions_decline_english);
    445     } else {
    446       return useSpanish()
    447           ? context.getString(R.string.dialer_terms_and_conditions_decline_spanish)
    448           : context.getString(R.string.dialer_terms_and_conditions_decline_english);
    449     }
    450   }
    451 
    452   private CharSequence getNewUserTosTitle() {
    453     return isVvm3()
    454         ? context.getString(R.string.verizon_terms_and_conditions_title)
    455         : context.getString(R.string.dialer_terms_and_conditions_title);
    456   }
    457 
    458   private CharSequence getExistingUserTosTitle() {
    459     return isVvm3()
    460         ? context.getString(R.string.verizon_terms_and_conditions_title)
    461         : context.getString(R.string.dialer_terms_and_conditions_existing_user_title);
    462   }
    463 
    464   private CharSequence getNewUserTosMessageText() {
    465     SpannableString spannableTos;
    466     if (isVvm3()) {
    467       // For verizon the TOS consist of three pieces: google dialer TOS, Verizon TOS message and
    468       // Verizon TOS details.
    469       CharSequence vvm3Details = getVvm3Tos();
    470       CharSequence tos =
    471           context.getString(
    472               R.string.verizon_terms_and_conditions_message, getVvmDialerTos(), vvm3Details);
    473       spannableTos = new SpannableString(tos);
    474       // Set the text style for the details part of the TOS
    475       int start = spannableTos.length() - vvm3Details.length();
    476       spannableTos.setSpan(
    477           new TextAppearanceSpan(context, R.style.TosDetailsTextStyle),
    478           start,
    479           start + vvm3Details.length(),
    480           Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    481       // Add verizon policy link
    482       String linkUrl = context.getString(R.string.verizon_terms_and_conditions_policy_url);
    483       return addLink(spannableTos, linkUrl, linkUrl);
    484     } else {
    485       // The TOS for everyone else, there are no details, but change to center alignment.
    486       CharSequence tos =
    487           context.getString(R.string.dialer_terms_and_conditions_message, getNewUserDialerTos());
    488       spannableTos = new SpannableString(tos);
    489       spannableTos.setSpan(
    490           new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
    491           0,
    492           tos.length(),
    493           Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    494 
    495       // Add 'Learn more' link for dialer TOS
    496       String learnMore = context.getString(R.string.dialer_terms_and_conditions_learn_more);
    497       return addLink(spannableTos, learnMore, getLearnMoreUrl());
    498     }
    499   }
    500 
    501   private CharSequence getExistingUserTosMessageText() {
    502     SpannableString spannableTos;
    503     // Change to center alignment.
    504     CharSequence tos =
    505         context.getString(R.string.dialer_terms_and_conditions_message, getExistingUserDialerTos());
    506     spannableTos = new SpannableString(tos);
    507     spannableTos.setSpan(
    508         new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
    509         0,
    510         tos.length(),
    511         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    512 
    513     // Add 'Learn more' link for dialer TOS
    514     String learnMore = context.getString(R.string.dialer_terms_and_conditions_learn_more);
    515     return addLink(spannableTos, learnMore, getLearnMoreUrl());
    516   }
    517 
    518   private SpannableString addLink(SpannableString spannable, String linkText, String linkUrl) {
    519     if (TextUtils.isEmpty(linkUrl) || TextUtils.isEmpty(linkText)) {
    520       return spannable;
    521     }
    522 
    523     int start = spannable.toString().indexOf(linkText);
    524     if (start != -1) {
    525       int end = start + linkText.length();
    526       spannable.setSpan(new URLSpan(linkUrl), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    527       spannable.setSpan(
    528           new TextAppearanceSpan(context, R.style.TosLinkStyle),
    529           start,
    530           end,
    531           Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    532     }
    533     return spannable;
    534   }
    535 
    536   private String getLearnMoreUrl() {
    537     return ConfigProviderBindings.get(context)
    538         .getString(
    539             "voicemail_transcription_learn_more_url",
    540             context.getString(R.string.dialer_terms_and_conditions_learn_more_url));
    541   }
    542 
    543   private int getTosDeclinedDialogMessageId() {
    544     return isVvm3()
    545         ? R.string.verizon_terms_and_conditions_decline_dialog_message
    546         : R.string.dialer_terms_and_conditions_decline_dialog_message;
    547   }
    548 
    549   private int getTosDeclinedDialogDowngradeId() {
    550     return isVvm3()
    551         ? R.string.verizon_terms_and_conditions_decline_dialog_downgrade
    552         : R.string.dialer_terms_and_conditions_decline_dialog_downgrade;
    553   }
    554 }
    555