diff options
17 files changed, 683 insertions, 133 deletions
diff --git a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java index e41a75b22..07891a069 100644 --- a/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java +++ b/java/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java @@ -25,11 +25,13 @@ import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; +import android.telephony.SubscriptionInfo; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -43,7 +45,10 @@ import android.widget.ListAdapter; import android.widget.TextView; import com.android.contacts.common.R; import com.android.contacts.common.compat.PhoneAccountCompat; -import com.android.contacts.common.compat.PhoneNumberUtilsCompat; +import com.android.dialer.location.GeoUtil; +import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import com.android.dialer.telecom.TelecomUtil; +import com.google.common.base.Optional; import java.util.ArrayList; import java.util.List; @@ -319,8 +324,9 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { } else { holder.numberTextView.setVisibility(View.VISIBLE); holder.numberTextView.setText( - PhoneNumberUtilsCompat.createTtsSpannable( - account.getAddress().getSchemeSpecificPart())); + PhoneNumberHelper.formatNumberForDisplay( + account.getAddress().getSchemeSpecificPart(), + getCountryIso(getContext(), accountHandle))); } holder.imageView.setImageDrawable( PhoneAccountCompat.createIconDrawable(account, getContext())); @@ -339,6 +345,16 @@ public class SelectPhoneAccountDialogFragment extends DialogFragment { return rowView; } + private static String getCountryIso( + Context context, @NonNull PhoneAccountHandle phoneAccountHandle) { + Optional<SubscriptionInfo> info = + TelecomUtil.getSubscriptionInfo(context, phoneAccountHandle); + if (!info.isPresent()) { + return GeoUtil.getCurrentCountryIso(context); + } + return info.get().getCountryIso().toUpperCase(); + } + private static final class ViewHolder { TextView labelTextView; diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index 4126443bc..69186d87c 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -89,8 +89,11 @@ import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.PermissionsUtil; import com.android.dialer.widget.FloatingActionButtonController; +import com.google.common.base.Optional; import java.util.HashSet; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** Fragment that displays a twelve-key phone dialpad. */ public class DialpadFragment extends Fragment @@ -129,6 +132,9 @@ public class DialpadFragment extends Fragment private static final String EXTRA_SEND_EMPTY_FLASH = "com.android.phone.extra.SEND_EMPTY_FLASH"; private static final String PREF_DIGITS_FILLED_BY_INTENT = "pref_digits_filled_by_intent"; + + private static Optional<String> currentCountryIsoForTesting = Optional.absent(); + private final Object mToneGeneratorLock = new Object(); /** Set of dialpad keys that are currently being pressed */ private final HashSet<View> mPressedDialpadKeys = new HashSet<>(12); @@ -156,7 +162,6 @@ public class DialpadFragment extends Fragment // determines if we want to playback local DTMF tones. private boolean mDTMFToneEnabled; - private String mCurrentCountryIso; private CallStateReceiver mCallStateReceiver; private boolean mWasEmptyBeforeTextChange; /** @@ -324,8 +329,6 @@ public class DialpadFragment extends Fragment mFirstLaunch = state == null; - mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); - mProhibitedPhoneNumberRegexp = getResources().getString(R.string.config_prohibited_phone_number_regexp); @@ -377,8 +380,7 @@ public class DialpadFragment extends Fragment mDigits.addTextChangedListener(this); mDigits.setElegantTextHeight(false); - initPhoneNumberFormattingTextWatcherExecutor.executeSerial( - GeoUtil.getCurrentCountryIso(getActivity())); + initPhoneNumberFormattingTextWatcherExecutor.executeSerial(getCurrentCountryIso()); // Check for the presence of the keypad View oneButton = fragmentView.findViewById(R.id.one); @@ -422,6 +424,19 @@ public class DialpadFragment extends Fragment return fragmentView; } + private String getCurrentCountryIso() { + if (currentCountryIsoForTesting.isPresent()) { + return currentCountryIsoForTesting.get(); + } + + return GeoUtil.getCurrentCountryIso(getActivity()); + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public static void setCurrentCountryIsoForTesting(String countryCode) { + currentCountryIsoForTesting = Optional.of(countryCode); + } + private boolean isLayoutReady() { return mDigits != null; } @@ -561,7 +576,7 @@ public class DialpadFragment extends Fragment /** Sets formatted digits to digits field. */ private void setFormattedDigits(String data, String normalizedNumber) { - final String formatted = getFormattedDigits(data, normalizedNumber, mCurrentCountryIso); + final String formatted = getFormattedDigits(data, normalizedNumber, getCurrentCountryIso()); if (!TextUtils.isEmpty(formatted)) { Editable digits = mDigits.getText(); digits.replace(0, digits.length(), formatted); @@ -1717,19 +1732,189 @@ public class DialpadFragment extends Fragment } /** - * Input: the ISO 3166-1 two letters country code of the country the user is in + * A worker that helps formatting the phone number as the user types it in. * - * <p>Output: PhoneNumberFormattingTextWatcher. Note: It is unusual to return a non-data value - * from a worker, but it is a limitation in libphonenumber API that the watcher cannot be - * initialized on the main thread. + * <p>Input: the ISO 3166-1 two-letter country code of the country the user is in. + * + * <p>Output: an instance of {@link DialerPhoneNumberFormattingTextWatcher}. Note: It is unusual + * to return a non-data value from a worker. But {@link DialerPhoneNumberFormattingTextWatcher} + * depends on libphonenumber API, which cannot be initialized on the main thread. */ private static class InitPhoneNumberFormattingTextWatcherWorker - implements Worker<String, PhoneNumberFormattingTextWatcher> { + implements Worker<String, DialerPhoneNumberFormattingTextWatcher> { @Nullable @Override - public PhoneNumberFormattingTextWatcher doInBackground(@Nullable String countryCode) { - return new PhoneNumberFormattingTextWatcher(countryCode); + public DialerPhoneNumberFormattingTextWatcher doInBackground(@Nullable String countryCode) { + return new DialerPhoneNumberFormattingTextWatcher(countryCode); + } + } + + /** + * An extension of Android telephony's {@link PhoneNumberFormattingTextWatcher}. This watcher + * skips formatting Argentina mobile numbers for domestic calls. + * + * <p>As of Nov. 28, 2017, the as-you-type-formatting provided by libphonenumber's + * AsYouTypeFormatter (which {@link PhoneNumberFormattingTextWatcher} depends on) can't correctly + * format Argentina mobile numbers for domestic calls (a bug). We temporarily disable the + * formatting for such numbers until libphonenumber is fixed (which will come as early as the next + * Android release). + */ + @VisibleForTesting + public static class DialerPhoneNumberFormattingTextWatcher + extends PhoneNumberFormattingTextWatcher { + private static final Pattern AR_DOMESTIC_CALL_MOBILE_NUMBER_PATTERN; + + // This static initialization block builds a pattern for domestic calls to Argentina mobile + // numbers: + // (1) Local calls: 15 <local number> + // (2) Long distance calls: <area code> 15 <local number> + // See https://en.wikipedia.org/wiki/Telephone_numbers_in_Argentina for detailed explanations. + static { + String regex = + "0?(" + + " (" + + " 11|" + + " 2(" + + " 2(" + + " 02?|" + + " [13]|" + + " 2[13-79]|" + + " 4[1-6]|" + + " 5[2457]|" + + " 6[124-8]|" + + " 7[1-4]|" + + " 8[13-6]|" + + " 9[1267]" + + " )|" + + " 3(" + + " 02?|" + + " 1[467]|" + + " 2[03-6]|" + + " 3[13-8]|" + + " [49][2-6]|" + + " 5[2-8]|" + + " [67]" + + " )|" + + " 4(" + + " 7[3-578]|" + + " 9" + + " )|" + + " 6(" + + " [0136]|" + + " 2[24-6]|" + + " 4[6-8]?|" + + " 5[15-8]" + + " )|" + + " 80|" + + " 9(" + + " 0[1-3]|" + + " [19]|" + + " 2\\d|" + + " 3[1-6]|" + + " 4[02568]?|" + + " 5[2-4]|" + + " 6[2-46]|" + + " 72?|" + + " 8[23]?" + + " )" + + " )|" + + " 3(" + + " 3(" + + " 2[79]|" + + " 6|" + + " 8[2578]" + + " )|" + + " 4(" + + " 0[0-24-9]|" + + " [12]|" + + " 3[5-8]?|" + + " 4[24-7]|" + + " 5[4-68]?|" + + " 6[02-9]|" + + " 7[126]|" + + " 8[2379]?|" + + " 9[1-36-8]" + + " )|" + + " 5(" + + " 1|" + + " 2[1245]|" + + " 3[237]?|" + + " 4[1-46-9]|" + + " 6[2-4]|" + + " 7[1-6]|" + + " 8[2-5]?" + + " )|" + + " 6[24]|" + + " 7(" + + " [069]|" + + " 1[1568]|" + + " 2[15]|" + + " 3[145]|" + + " 4[13]|" + + " 5[14-8]|" + + " 7[2-57]|" + + " 8[126]" + + " )|" + + " 8(" + + " [01]|" + + " 2[15-7]|" + + " 3[2578]?|" + + " 4[13-6]|" + + " 5[4-8]?|" + + " 6[1-357-9]|" + + " 7[36-8]?|" + + " 8[5-8]?|" + + " 9[124]" + + " )" + + " )" + + " )?15" + + ").*"; + AR_DOMESTIC_CALL_MOBILE_NUMBER_PATTERN = Pattern.compile(regex.replaceAll("\\s+", "")); + } + + private final String countryCode; + + DialerPhoneNumberFormattingTextWatcher(String countryCode) { + super(countryCode); + this.countryCode = countryCode; + } + + @Override + public synchronized void afterTextChanged(Editable s) { + super.afterTextChanged(s); + + if (!"AR".equals(countryCode)) { + return; + } + + String rawNumber = getRawNumber(s); + + // As modifying the input will trigger another call to afterTextChanged(Editable), we must + // check whether the input's format has already been removed and return if it has + // been to avoid infinite recursion. + if (rawNumber.contentEquals(s)) { + return; + } + + Matcher matcher = AR_DOMESTIC_CALL_MOBILE_NUMBER_PATTERN.matcher(rawNumber); + if (matcher.matches()) { + s.replace(0, s.length(), rawNumber); + PhoneNumberUtils.addTtsSpan(s, 0 /* start */, s.length() /* endExclusive */); + } + } + + private static String getRawNumber(Editable s) { + StringBuilder rawNumberBuilder = new StringBuilder(); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (PhoneNumberUtils.isNonSeparator(c)) { + rawNumberBuilder.append(c); + } + } + + return rawNumberBuilder.toString(); } } } diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java index 40a338588..cdc06dead 100644 --- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java +++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java @@ -26,6 +26,8 @@ import android.support.annotation.Nullable; import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.util.SparseIntArray; import com.android.dialer.common.Assert; @@ -337,6 +339,18 @@ public class PhoneNumberHelper { return formattedNumber != null ? formattedNumber : number; } + @Nullable + public static CharSequence formatNumberForDisplay( + @Nullable String number, @NonNull String countryIso) { + if (number == null) { + return null; + } + + return PhoneNumberUtils.createTtsSpannable( + BidiFormatter.getInstance() + .unicodeWrap(formatNumber(number, countryIso), TextDirectionHeuristics.LTR)); + } + /** * Determines if the specified number is actually a URI (i.e. a SIP address) rather than a regular * PSTN phone number, based on whether or not the number contains an "@" character. diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java index aaa1e150d..5ae26f5f7 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 @@ public class VoicemailSettingsFragment extends PreferenceFragment 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 @@ public class VoicemailSettingsFragment extends PreferenceFragment 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 @@ public class VoicemailSettingsFragment extends PreferenceFragment .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 4e502b488..10fa459ff 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/AndroidManifest.xml b/java/com/android/voicemail/impl/AndroidManifest.xml index 53636092a..e7ab5818e 100644 --- a/java/com/android/voicemail/impl/AndroidManifest.xml +++ b/java/com/android/voicemail/impl/AndroidManifest.xml @@ -138,5 +138,9 @@ android:name="com.android.internal.telephony.CARRIER_VVM_PACKAGE_INSTALLED" /> </intent-filter> </receiver> + + <receiver android:name="com.android.voicemail.impl.transcribe.GetTranscriptReceiver" + android:exported="false"> + </receiver> </application> </manifest> diff --git a/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java b/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java index 61d76194c..e42d56938 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.content.Context; 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 @@ public class VisualVoicemailSettingsUtil { } 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/GetTranscriptReceiver.java b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java new file mode 100644 index 000000000..cc204ff53 --- /dev/null +++ b/java/com/android/voicemail/impl/transcribe/GetTranscriptReceiver.java @@ -0,0 +1,233 @@ +/* + * 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.voicemail.impl.transcribe; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.SystemClock; +import android.support.annotation.Nullable; +import android.util.Pair; +import com.android.dialer.common.Assert; +import com.android.dialer.common.backoff.ExponentialBaseCalculator; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; +import com.android.voicemail.impl.VvmLog; +import com.android.voicemail.impl.transcribe.grpc.GetTranscriptResponseAsync; +import com.android.voicemail.impl.transcribe.grpc.TranscriptionClient; +import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory; +import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest; +import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus; + +/** + * This class uses the AlarmManager to poll for the result of a voicemail transcription request. + * Initially it waits for the estimated transcription time, and if the result is not available then + * it polls using an exponential backoff scheme. + */ +public class GetTranscriptReceiver extends BroadcastReceiver { + private static final String TAG = "GetTranscriptReceiver"; + static final String EXTRA_IS_INITIAL_ESTIMATED_WAIT = "extra_is_initial_estimated_wait"; + static final String EXTRA_VOICEMAIL_URI = "extra_voicemail_uri"; + static final String EXTRA_TRANSCRIPT_ID = "extra_transcript_id"; + static final String EXTRA_DELAY_MILLIS = "extra_delay_millis"; + static final String EXTRA_BASE_MULTIPLIER = "extra_base_multiplier"; + static final String EXTRA_REMAINING_ATTEMPTS = "extra_remaining_attempts"; + + // Schedule an initial alarm to begin checking for a voicemail transcription result. + static void beginPolling( + Context context, + Uri voicemailUri, + String transcriptId, + long estimatedTranscriptionTimeMillis, + TranscriptionConfigProvider configProvider) { + long initialDelayMillis = configProvider.getInitialGetTranscriptPollDelayMillis(); + long maxBackoffMillis = configProvider.getMaxGetTranscriptPollTimeMillis(); + int maxAttempts = configProvider.getMaxGetTranscriptPolls(); + double baseMultiplier = + ExponentialBaseCalculator.findBase(initialDelayMillis, maxBackoffMillis, maxAttempts); + Intent intent = + makeAlarmIntent( + context, voicemailUri, transcriptId, initialDelayMillis, baseMultiplier, maxAttempts); + // Add an extra to distinguish this initial estimated transcription wait from subsequent backoff + // waits + intent.putExtra(EXTRA_IS_INITIAL_ESTIMATED_WAIT, true); + VvmLog.i( + TAG, + String.format( + "beginPolling, check in %d millis, for: %s", + estimatedTranscriptionTimeMillis, transcriptId)); + scheduleAlarm(context, estimatedTranscriptionTimeMillis, intent); + } + + // Alarm fired, poll for transcription result on a background thread + @Override + public void onReceive(Context context, Intent intent) { + String transcriptId = intent.getStringExtra(EXTRA_TRANSCRIPT_ID); + VvmLog.i(TAG, "onReceive, for transcript id: " + transcriptId); + DialerExecutorComponent.get(context) + .dialerExecutorFactory() + .createNonUiTaskBuilder(new PollWorker(context)) + .onSuccess(this::onSuccess) + .onFailure(this::onFailure) + .build() + .executeParallel(intent); + } + + private void onSuccess(Void unused) { + VvmLog.i(TAG, "onSuccess"); + } + + private void onFailure(Throwable t) { + VvmLog.e(TAG, "onFailure", t); + } + + private static void scheduleAlarm(Context context, long delayMillis, Intent intent) { + PendingIntent alarmIntent = + PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + alarmMgr.set( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + delayMillis, + alarmIntent); + } + + private static Intent makeAlarmIntent( + Context context, + Uri voicemailUri, + String transcriptId, + long delayMillis, + double baseMultiplier, + int remainingAttempts) { + Intent intent = new Intent(context, GetTranscriptReceiver.class); + intent.putExtra(EXTRA_VOICEMAIL_URI, voicemailUri); + intent.putExtra(EXTRA_TRANSCRIPT_ID, transcriptId); + intent.putExtra(EXTRA_DELAY_MILLIS, delayMillis); + intent.putExtra(EXTRA_BASE_MULTIPLIER, baseMultiplier); + intent.putExtra(EXTRA_REMAINING_ATTEMPTS, remainingAttempts); + return intent; + } + + private static class PollWorker implements Worker<Intent, Void> { + private final Context context; + + PollWorker(Context context) { + this.context = context; + } + + @Override + public Void doInBackground(Intent intent) { + String transcriptId = intent.getStringExtra(EXTRA_TRANSCRIPT_ID); + VvmLog.i(TAG, "doInBackground, for transcript id: " + transcriptId); + Pair<String, TranscriptionStatus> result = pollForTranscription(transcriptId); + if (result.first == null && result.second == null) { + // No result, try again if possible + Intent nextIntent = getNextAlarmIntent(intent); + if (nextIntent == null) { + VvmLog.i(TAG, "doInBackground, too many failures for: " + transcriptId); + result = new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY); + } else { + long nextDelayMillis = nextIntent.getLongExtra(EXTRA_DELAY_MILLIS, 0L); + VvmLog.i( + TAG, + String.format( + "doInBackground, check again in %d, for: %s", nextDelayMillis, transcriptId)); + scheduleAlarm(context, nextDelayMillis, nextIntent); + return null; + } + } + + // Got transcript or failed too many times + Uri voicemailUri = intent.getParcelableExtra(EXTRA_VOICEMAIL_URI); + TranscriptionDbHelper dbHelper = new TranscriptionDbHelper(context, voicemailUri); + TranscriptionTask.recordResult(context, result, dbHelper); + return null; + } + + private Pair<String, TranscriptionStatus> pollForTranscription(String transcriptId) { + VvmLog.i(TAG, "pollForTranscription, transcript id: " + transcriptId); + GetTranscriptRequest request = getGetTranscriptRequest(transcriptId); + TranscriptionClientFactory factory = null; + try { + factory = getTranscriptionClientFactory(context); + TranscriptionClient client = factory.getClient(); + Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_POLL_REQUEST); + GetTranscriptResponseAsync response = client.sendGetTranscriptRequest(request); + if (response == null) { + VvmLog.i(TAG, "pollForTranscription, no transcription result."); + return new Pair<>(null, null); + } else if (response.isTranscribing()) { + VvmLog.i(TAG, "pollForTranscription, transcribing"); + return new Pair<>(null, null); + } else if (response.hasFatalError()) { + VvmLog.i(TAG, "pollForTranscription, fail. " + response.getErrorDescription()); + return new Pair<>(null, response.getTranscriptionStatus()); + } else { + VvmLog.i(TAG, "pollForTranscription, got transcription"); + return new Pair<>(response.getTranscript(), TranscriptionStatus.SUCCESS); + } + } finally { + if (factory != null) { + factory.shutdown(); + } + } + } + + private GetTranscriptRequest getGetTranscriptRequest(String transcriptionId) { + Assert.checkArgument(transcriptionId != null); + return GetTranscriptRequest.newBuilder().setTranscriptionId(transcriptionId).build(); + } + + private @Nullable Intent getNextAlarmIntent(Intent previous) { + int remainingAttempts = previous.getIntExtra(EXTRA_REMAINING_ATTEMPTS, 0); + double baseMultiplier = previous.getDoubleExtra(EXTRA_BASE_MULTIPLIER, 0); + long nextDelay = previous.getLongExtra(EXTRA_DELAY_MILLIS, 0); + if (!previous.getBooleanExtra(EXTRA_IS_INITIAL_ESTIMATED_WAIT, false)) { + // After waiting the estimated transcription time, start decrementing the remaining attempts + // and incrementing the backoff time delay + remainingAttempts--; + if (remainingAttempts <= 0) { + return null; + } + nextDelay = (long) (nextDelay * baseMultiplier); + } + return makeAlarmIntent( + context, + previous.getParcelableExtra(EXTRA_VOICEMAIL_URI), + previous.getStringExtra(EXTRA_TRANSCRIPT_ID), + nextDelay, + baseMultiplier, + remainingAttempts); + } + } + + private static TranscriptionClientFactory transcriptionClientFactoryForTesting; + + static void setTranscriptionClientFactoryForTesting(TranscriptionClientFactory factory) { + transcriptionClientFactoryForTesting = factory; + } + + private static TranscriptionClientFactory getTranscriptionClientFactory(Context context) { + if (transcriptionClientFactoryForTesting != null) { + return transcriptionClientFactoryForTesting; + } + return new TranscriptionClientFactory(context, new TranscriptionConfigProvider(context)); + } +} diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java index 98c8461f5..3d1755b64 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionConfigProvider.java @@ -18,6 +18,7 @@ package com.android.voicemail.impl.transcribe; import android.content.Context; import android.os.Build; import com.android.dialer.configprovider.ConfigProviderBindings; +import java.util.concurrent.TimeUnit; /** Provides configuration values needed to connect to the transcription server. */ public class TranscriptionConfigProvider { @@ -65,14 +66,24 @@ public class TranscriptionConfigProvider { .getLong("voicemail_transcription_max_transcription_retries", 2L); } - public long getMaxGetTranscriptPolls() { + public int getMaxGetTranscriptPolls() { + return (int) + ConfigProviderBindings.get(context) + .getLong("voicemail_transcription_max_get_transcript_polls", 20L); + } + + public long getInitialGetTranscriptPollDelayMillis() { return ConfigProviderBindings.get(context) - .getLong("voicemail_transcription_max_get_transcript_polls", 20L); + .getLong( + "voicemail_transcription_get_initial_transcript_poll_delay_millis", + TimeUnit.SECONDS.toMillis(1)); } - public long getGetTranscriptPollIntervalMillis() { + public long getMaxGetTranscriptPollTimeMillis() { return ConfigProviderBindings.get(context) - .getLong("voicemail_transcription_get_transcript_poll_interval_millis", 1000L); + .getLong( + "voicemail_transcription_get_max_transcript_poll_time_millis", + TimeUnit.MINUTES.toMillis(20)); } public boolean isVoicemailDonationAvailable() { @@ -97,6 +108,6 @@ public class TranscriptionConfigProvider { shouldUseSyncApi(), getMaxTranscriptionRetries(), getMaxGetTranscriptPolls(), - getGetTranscriptPollIntervalMillis()); + getMaxGetTranscriptPollTimeMillis()); } } diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java index 1cafacecf..b721ba5b0 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java @@ -85,7 +85,8 @@ public class TranscriptionRatingHelper { 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 97cf89eef..ca3320f28 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java @@ -21,7 +21,6 @@ import android.net.Uri; import android.support.annotation.MainThread; import android.support.annotation.VisibleForTesting; import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; import android.util.Pair; import com.android.dialer.common.Assert; import com.android.dialer.common.concurrent.ThreadUtil; @@ -56,14 +55,14 @@ import com.google.protobuf.ByteString; 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; - private final Uri voicemailUri; + protected final Context context; + protected final Uri voicemailUri; protected final PhoneAccountHandle phoneAccountHandle; - private final TranscriptionDbHelper databaseHelper; protected final TranscriptionConfigProvider configProvider; + protected final TranscriptionDbHelper dbHelper; protected ByteString audioData; protected AudioFormat encoding; protected volatile boolean cancelled; @@ -86,7 +85,7 @@ public abstract class TranscriptionTask implements Runnable { this.voicemailUri = TranscriptionService.getVoicemailUri(workItem); this.phoneAccountHandle = TranscriptionService.getPhoneAccountHandle(workItem); this.configProvider = configProvider; - databaseHelper = new TranscriptionDbHelper(context, voicemailUri); + dbHelper = new TranscriptionDbHelper(context, voicemailUri); } @MainThread @@ -124,44 +123,7 @@ public abstract class TranscriptionTask implements Runnable { private void transcribeVoicemail() { VvmLog.i(TAG, "transcribeVoicemail"); - Pair<String, TranscriptionStatus> pair = getTranscription(); - String transcript = pair.first; - TranscriptionStatus status = pair.second; - if (!TextUtils.isEmpty(transcript)) { - updateTranscriptionAndState(transcript, VoicemailCompat.TRANSCRIPTION_AVAILABLE); - VvmLog.i(TAG, "transcribeVoicemail, got response"); - Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_SUCCESS); - } else { - VvmLog.i(TAG, "transcribeVoicemail, transcription unsuccessful, " + status); - switch (status) { - case FAILED_NO_SPEECH_DETECTED: - updateTranscriptionAndState( - transcript, VoicemailCompat.TRANSCRIPTION_FAILED_NO_SPEECH_DETECTED); - Logger.get(context) - .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_NO_SPEECH_DETECTED); - break; - case FAILED_LANGUAGE_NOT_SUPPORTED: - updateTranscriptionAndState( - transcript, VoicemailCompat.TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED); - Logger.get(context) - .logImpression( - DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_LANGUAGE_NOT_SUPPORTED); - break; - case EXPIRED: - updateTranscriptionAndState(transcript, VoicemailCompat.TRANSCRIPTION_FAILED); - Logger.get(context) - .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EXPIRED); - break; - default: - updateTranscriptionAndState( - transcript, - cancelled - ? VoicemailCompat.TRANSCRIPTION_NOT_STARTED - : VoicemailCompat.TRANSCRIPTION_FAILED); - Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EMPTY); - break; - } - } + recordResult(context, getTranscription(), dbHelper, cancelled); } protected TranscriptionResponse sendRequest(Request request) { @@ -213,12 +175,57 @@ public abstract class TranscriptionTask implements Runnable { } } - private void updateTranscriptionAndState(String transcript, int newState) { - databaseHelper.setTranscriptionAndState(transcript, newState); + protected void updateTranscriptionState(int newState) { + dbHelper.setTranscriptionState(newState); + } + + protected void updateTranscriptionAndState(String transcript, int newState) { + dbHelper.setTranscriptionAndState(transcript, newState); + } + + static void recordResult( + Context context, Pair<String, TranscriptionStatus> result, TranscriptionDbHelper dbHelper) { + recordResult(context, result, dbHelper, false); } - private void updateTranscriptionState(int newState) { - databaseHelper.setTranscriptionState(newState); + static void recordResult( + Context context, + Pair<String, TranscriptionStatus> result, + TranscriptionDbHelper dbHelper, + boolean cancelled) { + if (result.first != null) { + VvmLog.i(TAG, "recordResult, got transcription"); + dbHelper.setTranscriptionAndState(result.first, VoicemailCompat.TRANSCRIPTION_AVAILABLE); + Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_SUCCESS); + } else if (result.second != null) { + VvmLog.i(TAG, "recordResult, failed to transcribe, reason: " + result.second); + switch (result.second) { + case FAILED_NO_SPEECH_DETECTED: + dbHelper.setTranscriptionState(VoicemailCompat.TRANSCRIPTION_FAILED_NO_SPEECH_DETECTED); + Logger.get(context) + .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_NO_SPEECH_DETECTED); + break; + case FAILED_LANGUAGE_NOT_SUPPORTED: + dbHelper.setTranscriptionState( + VoicemailCompat.TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED); + Logger.get(context) + .logImpression( + DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_LANGUAGE_NOT_SUPPORTED); + break; + case EXPIRED: + dbHelper.setTranscriptionState(VoicemailCompat.TRANSCRIPTION_FAILED); + Logger.get(context) + .logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EXPIRED); + break; + default: + dbHelper.setTranscriptionState( + cancelled + ? VoicemailCompat.TRANSCRIPTION_NOT_STARTED + : VoicemailCompat.TRANSCRIPTION_FAILED); + Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EMPTY); + break; + } + } } private boolean readAndValidateAudioFile() { diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java index 808bf0f87..bb7aa5f38 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java @@ -19,17 +19,13 @@ import android.app.job.JobWorkItem; import android.content.Context; import android.support.annotation.VisibleForTesting; import android.util.Pair; -import com.android.dialer.common.Assert; import com.android.dialer.logging.DialerImpression; -import com.android.dialer.logging.Logger; import com.android.voicemail.VoicemailComponent; import com.android.voicemail.impl.VvmLog; import com.android.voicemail.impl.transcribe.TranscriptionService.JobCallback; -import com.android.voicemail.impl.transcribe.grpc.GetTranscriptResponseAsync; import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory; import com.android.voicemail.impl.transcribe.grpc.TranscriptionResponseAsync; import com.google.internal.communications.voicemailtranscription.v1.DonationPreference; -import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest; import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest; import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus; @@ -72,9 +68,19 @@ public class TranscriptionTaskAsync extends TranscriptionTask { } 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 { - waitForTranscription(uploadResponse); - return pollForTranscription(uploadResponse); + VvmLog.i(TAG, "getTranscription, begin polling for result."); + GetTranscriptReceiver.beginPolling( + context, + voicemailUri, + uploadResponse.getTranscriptionId(), + uploadResponse.getEstimatedWaitMillis(), + configProvider); + // This indicates that the result is not available yet + return new Pair<>(null, null); } } @@ -83,45 +89,6 @@ public class TranscriptionTaskAsync extends TranscriptionTask { return DialerImpression.Type.VVM_TRANSCRIPTION_REQUEST_SENT_ASYNC; } - private static void waitForTranscription(TranscriptionResponseAsync uploadResponse) { - long millis = uploadResponse.getEstimatedWaitMillis(); - VvmLog.i(TAG, "waitForTranscription, " + millis + " millis"); - sleep(millis); - } - - private Pair<String, TranscriptionStatus> pollForTranscription( - TranscriptionResponseAsync uploadResponse) { - VvmLog.i(TAG, "pollForTranscription"); - GetTranscriptRequest request = getGetTranscriptRequest(uploadResponse); - for (int i = 0; i < configProvider.getMaxGetTranscriptPolls(); i++) { - if (cancelled) { - VvmLog.i(TAG, "pollForTranscription, cancelled."); - return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY); - } - Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_POLL_REQUEST); - GetTranscriptResponseAsync response = - (GetTranscriptResponseAsync) - sendRequest((client) -> client.sendGetTranscriptRequest(request)); - if (cancelled) { - VvmLog.i(TAG, "pollForTranscription, cancelled."); - return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY); - } else if (response == null) { - VvmLog.i(TAG, "pollForTranscription, no transcription result."); - } else if (response.isTranscribing()) { - VvmLog.i(TAG, "pollForTranscription, poll count: " + (i + 1)); - } else if (response.hasFatalError()) { - VvmLog.i(TAG, "pollForTranscription, fail. " + response.getErrorDescription()); - return new Pair<>(null, response.getTranscriptionStatus()); - } else { - VvmLog.i(TAG, "pollForTranscription, got transcription"); - return new Pair<>(response.getTranscript(), TranscriptionStatus.SUCCESS); - } - sleep(configProvider.getGetTranscriptPollIntervalMillis()); - } - VvmLog.i(TAG, "pollForTranscription, timed out."); - return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY); - } - @VisibleForTesting TranscribeVoicemailAsyncRequest getUploadRequest() { TranscribeVoicemailAsyncRequest.Builder builder = @@ -134,7 +101,12 @@ public class TranscriptionTaskAsync extends TranscriptionTask { // 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(); } @@ -145,11 +117,4 @@ public class TranscriptionTaskAsync extends TranscriptionTask { .getVoicemailClient() .isVoicemailDonationEnabled(context, phoneAccountHandle); } - - private GetTranscriptRequest getGetTranscriptRequest(TranscriptionResponseAsync uploadResponse) { - Assert.checkArgument(uploadResponse.getTranscriptionId() != null); - return GetTranscriptRequest.newBuilder() - .setTranscriptionId(uploadResponse.getTranscriptionId()) - .build(); - } } diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java index 36b1400be..3bd14731f 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java @@ -18,6 +18,8 @@ package com.android.voicemail.impl.transcribe; 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 @@ public class TranscriptionUtils { : 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 f0823de32..ae4796dea 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 @@ public abstract class TranscriptionResponse { 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 38b463053..bd5679407 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 @@ public class TranscriptionResponseAsync extends TranscriptionResponse { } 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 d2e2e218c..382bd1a97 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 class TranscriptionResponseSync extends TranscriptionResponse { 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 711d6a8a4..ef5447d32 100644 --- a/java/com/android/voicemail/impl/utils/VoicemailDatabaseUtil.java +++ b/java/com/android/voicemail/impl/utils/VoicemailDatabaseUtil.java @@ -57,6 +57,16 @@ public class VoicemailDatabaseUtil { 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(); |