Delete voicemails when disabling visual voicemail

For privacy reasons we now delete all voicemails and transcriptions when
the user disables visual voicemail, (after confirming that they want to do this
via a dialog).

Note: we only delete voicemails whose source package is the google dialer.
Note: the voicemails will be re-downloaded and re-transcribed if the user re-enables visual voicemail

Bug: 69323147
Test: manual and unit test
PiperOrigin-RevId: 177315139
Change-Id: Ie04496dc5960b485794733fbf74f7f704e806023
diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java
index aaa1e15..5ae26f5 100644
--- a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java
+++ b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java
@@ -14,6 +14,8 @@
 package com.android.dialer.voicemail.settings;
 
 import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -226,16 +228,13 @@
     LogUtil.d(TAG, "onPreferenceChange: \"" + preference + "\" changed to \"" + objValue + "\"");
     if (preference.getKey().equals(voicemailVisualVoicemail.getKey())) {
       boolean isEnabled = (boolean) objValue;
-      voicemailClient.setVoicemailEnabled(getContext(), phoneAccountHandle, isEnabled);
-
-      if (isEnabled) {
-        Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_USER_ENABLED_IN_SETTINGS);
+      if (!isEnabled) {
+        showDisableConfirmationDialog();
+        // Don't let the preference setting proceed.
+        return false;
       } else {
-        Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_USER_DISABLED_IN_SETTINGS);
+        updateVoicemailEnabled(true);
       }
-
-      updateChangePin();
-      updateDonateVoicemail();
     } else if (preference.getKey().equals(autoArchiveSwitchPreference.getKey())) {
       logArchiveToggle((boolean) objValue);
       voicemailClient.setVoicemailArchiveEnabled(
@@ -246,10 +245,24 @@
           getContext(), phoneAccountHandle, (boolean) objValue);
     }
 
-    // Always let the preference setting proceed.
+    // Let the preference setting proceed.
     return true;
   }
 
+  private void updateVoicemailEnabled(boolean isEnabled) {
+    voicemailClient.setVoicemailEnabled(getContext(), phoneAccountHandle, isEnabled);
+    voicemailVisualVoicemail.setChecked(isEnabled);
+
+    if (isEnabled) {
+      Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_USER_ENABLED_IN_SETTINGS);
+    } else {
+      Logger.get(getContext()).logImpression(DialerImpression.Type.VVM_USER_DISABLED_IN_SETTINGS);
+    }
+
+    updateChangePin();
+    updateDonateVoicemail();
+  }
+
   private void updateChangePin() {
     if (!voicemailClient.isVoicemailEnabled(getContext(), phoneAccountHandle)) {
       voicemailChangePinPreference.setSummary(
@@ -305,4 +318,34 @@
         .putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
         .putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
   }
+
+  private void showDisableConfirmationDialog() {
+    LogUtil.i(TAG, "showDisableConfirmationDialog");
+    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+    builder.setTitle(R.string.confirm_disable_voicemail_dialog_title);
+    builder.setMessage(R.string.confirm_disable_voicemail_dialog_message);
+    builder.setPositiveButton(
+        R.string.confirm_disable_voicemail_accept_dialog_label,
+        new DialogInterface.OnClickListener() {
+          @Override
+          public void onClick(DialogInterface dialog, int which) {
+            LogUtil.i(TAG, "showDisableConfirmationDialog, confirmed");
+            updateVoicemailEnabled(false);
+            dialog.dismiss();
+          }
+        });
+
+    builder.setNegativeButton(
+        android.R.string.cancel,
+        new DialogInterface.OnClickListener() {
+          @Override
+          public void onClick(DialogInterface dialog, int which) {
+            LogUtil.i(TAG, "showDisableConfirmationDialog, cancelled");
+            dialog.dismiss();
+          }
+        });
+
+    builder.setCancelable(true);
+    builder.show();
+  }
 }
diff --git a/java/com/android/dialer/voicemail/settings/res/values/strings.xml b/java/com/android/dialer/voicemail/settings/res/values/strings.xml
index 4e502b4..10fa459 100644
--- a/java/com/android/dialer/voicemail/settings/res/values/strings.xml
+++ b/java/com/android/dialer/voicemail/settings/res/values/strings.xml
@@ -118,4 +118,11 @@
   <!-- Summary information for visual voicemail donation setting [CHAR LIMIT=NONE] -->
   <string name="voicemail_donate_preference_summary_info">Let Google review your voicemail messages to improve transcription quality</string>
 
+  <!-- Title for disable visual voicemail confirmation dialog [CHAR LIMIT=40] -->
+  <string name="confirm_disable_voicemail_dialog_title">Turn off visual voicemail</string>
+  <!-- Message explaining the implictions of disabling visual voicemail [CHAR LIMIT=NONE] -->
+  <string name="confirm_disable_voicemail_dialog_message">This will delete any voicemail and Google transcripts stored within this app. Your carrier may keep its own copies.</string>
+  <!-- The label for the confirm-disable-voicemail button [CHAR LIMIT=16] -->
+  <string name="confirm_disable_voicemail_accept_dialog_label">TURN OFF</string>
+
 </resources>
diff --git a/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java b/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java
index 61d7619..e42d569 100644
--- a/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java
+++ b/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java
@@ -19,11 +19,14 @@
 import android.support.annotation.VisibleForTesting;
 import android.telecom.PhoneAccountHandle;
 import com.android.dialer.common.Assert;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.voicemail.VoicemailComponent;
 import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper;
 import com.android.voicemail.impl.VisualVoicemailPreferences;
 import com.android.voicemail.impl.VvmLog;
 import com.android.voicemail.impl.sync.VvmAccountManager;
+import com.android.voicemail.impl.utils.VoicemailDatabaseUtil;
 
 /** Save whether or not a particular account is enabled in shared to be retrieved later. */
 public class VisualVoicemailSettingsUtil {
@@ -45,9 +48,40 @@
     } else {
       VvmAccountManager.removeAccount(context, phoneAccount);
       config.startDeactivation();
+      // Remove all voicemails from the database
+      DialerExecutorComponent.get(context)
+          .dialerExecutorFactory()
+          .createNonUiTaskBuilder(new VoicemailDeleteWorker(context))
+          .onSuccess(VisualVoicemailSettingsUtil::onSuccess)
+          .onFailure(VisualVoicemailSettingsUtil::onFailure)
+          .build()
+          .executeParallel(null);
     }
   }
 
+  private static class VoicemailDeleteWorker implements Worker<Void, Void> {
+    private final Context context;
+
+    VoicemailDeleteWorker(Context context) {
+      this.context = context;
+    }
+
+    @Override
+    public Void doInBackground(Void unused) {
+      int deleted = VoicemailDatabaseUtil.deleteAll(context);
+      VvmLog.i("VisualVoicemailSettingsUtil.doInBackground", "deleted " + deleted + " voicemails");
+      return null;
+    }
+  }
+
+  private static void onSuccess(Void unused) {
+    VvmLog.i("VisualVoicemailSettingsUtil.onSuccess", "delete voicemails");
+  }
+
+  private static void onFailure(Throwable t) {
+    VvmLog.e("VisualVoicemailSettingsUtil.onFailure", "delete voicemails", t);
+  }
+
   public static void setArchiveEnabled(
       Context context, PhoneAccountHandle phoneAccount, boolean isEnabled) {
     Assert.checkArgument(
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java
index 1caface..b721ba5 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java
@@ -85,7 +85,8 @@
 
     private SendTranscriptionFeedbackRequest getFeedbackRequest() {
       ByteString audioData = TranscriptionUtils.getAudioData(context, voicemailUri);
-      String voicemailId = TranscriptionUtils.getFingerprintFor(audioData);
+      String salt = voicemailUri.toString();
+      String voicemailId = TranscriptionUtils.getFingerprintFor(audioData, salt);
       TranscriptionRating rating =
           TranscriptionRating.newBuilder()
               .setTranscriptionId(voicemailId)
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
index d483f6f..ca3320f 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
@@ -55,10 +55,10 @@
 public abstract class TranscriptionTask implements Runnable {
   private static final String TAG = "TranscriptionTask";
 
-  protected final Context context;
   private final JobCallback callback;
   private final JobWorkItem workItem;
   private final TranscriptionClientFactory clientFactory;
+  protected final Context context;
   protected final Uri voicemailUri;
   protected final PhoneAccountHandle phoneAccountHandle;
   protected final TranscriptionConfigProvider configProvider;
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
index 7abf248..bb7aa5f 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
@@ -68,6 +68,9 @@
     } else if (uploadResponse == null) {
       VvmLog.i(TAG, "getTranscription, failed to upload voicemail.");
       return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+    } else if (uploadResponse.getTranscriptionId() == null) {
+      VvmLog.i(TAG, "getTranscription, upload error: " + uploadResponse.status);
+      return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
     } else {
       VvmLog.i(TAG, "getTranscription, begin polling for result.");
       GetTranscriptReceiver.beginPolling(
@@ -98,7 +101,12 @@
     // available (because rating donating voicemails requires locally generated voicemail ids).
     if (configProvider.useClientGeneratedVoicemailIds()
         || configProvider.isVoicemailDonationAvailable()) {
-      builder.setTranscriptionId(TranscriptionUtils.getFingerprintFor(audioData));
+      // The server currently can't handle repeated transcription id's so if we add the Uri to the
+      // fingerprint (which contains the voicemail id) which is different each time a voicemail is
+      // downloaded.  If this becomes a problem then it should be possible to change the server
+      // behavior to allow id's to be re-used, a bug
+      String salt = voicemailUri.toString();
+      builder.setTranscriptionId(TranscriptionUtils.getFingerprintFor(audioData, salt));
     }
     return builder.build();
   }
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java
index 36b1400..3bd1473 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java
@@ -18,6 +18,8 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.Nullable;
 import android.util.Base64;
 import com.android.dialer.common.Assert;
 import com.google.internal.communications.voicemailtranscription.v1.AudioFormat;
@@ -47,11 +49,14 @@
         : AudioFormat.AUDIO_FORMAT_UNSPECIFIED;
   }
 
-  @TargetApi(android.os.Build.VERSION_CODES.O)
-  static String getFingerprintFor(ByteString data) {
+  @TargetApi(VERSION_CODES.O)
+  static String getFingerprintFor(ByteString data, @Nullable String salt) {
     Assert.checkArgument(data != null);
     try {
       MessageDigest md = MessageDigest.getInstance("MD5");
+      if (salt != null) {
+        md.update(salt.getBytes());
+      }
       byte[] md5Bytes = md.digest(data.toByteArray());
       return Base64.encodeToString(md5Bytes, Base64.DEFAULT);
     } catch (NoSuchAlgorithmException e) {
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
index f0823de..ae4796d 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponse.java
@@ -50,4 +50,9 @@
 
     return false;
   }
+
+  @Override
+  public String toString() {
+    return "status: " + status;
+  }
 }
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java
index 38b4630..bd56794 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseAsync.java
@@ -50,4 +50,9 @@
     }
     return 0;
   }
+
+  @Override
+  public String toString() {
+    return super.toString() + ", response: " + response;
+  }
 }
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java
index d2e2e21..382bd1a 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionResponseSync.java
@@ -40,4 +40,9 @@
   public @Nullable String getTranscript() {
     return (response != null) ? response.getTranscript() : null;
   }
+
+  @Override
+  public String toString() {
+    return super.toString() + ", response: " + response;
+  }
 }
diff --git a/java/com/android/voicemail/impl/utils/VoicemailDatabaseUtil.java b/java/com/android/voicemail/impl/utils/VoicemailDatabaseUtil.java
index 711d6a8..ef5447d 100644
--- a/java/com/android/voicemail/impl/utils/VoicemailDatabaseUtil.java
+++ b/java/com/android/voicemail/impl/utils/VoicemailDatabaseUtil.java
@@ -57,6 +57,16 @@
     return voicemails.size();
   }
 
+  /**
+   * Delete all the voicemails whose source_package field matches this package
+   *
+   * @return the number of voicemails deleted
+   */
+  public static int deleteAll(Context context) {
+    ContentResolver contentResolver = context.getContentResolver();
+    return contentResolver.delete(Voicemails.buildSourceUri(context.getPackageName()), null, null);
+  }
+
   /** Maps structured {@link Voicemail} to {@link ContentValues} in content provider. */
   private static ContentValues getContentValues(Voicemail voicemail) {
     ContentValues contentValues = new ContentValues();