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