summaryrefslogtreecommitdiff
path: root/java/com
diff options
context:
space:
mode:
authormdooley <mdooley@google.com>2017-11-23 08:31:05 -0800
committerEric Erfanian <erfanian@google.com>2017-11-28 14:34:47 -0800
commit70fedf8d6caee1177ee891bbfff404dc48867c16 (patch)
treec59375133ee2f7236c17758706e88a492db81259 /java/com
parent5b6d823a45fe56cf9c36e5b00908831049c1d827 (diff)
Adding transcription rating feedback
Allow users who have agreed to donate their voicemails to also provide transcription quality feedback. screenshot: https://drive.google.com/open?id=0B9o_KvtLkcuIajVtdFN3Y0Qydmx2NXJYN2N3OVA3N0h5UEdR Bug: 68712148 Test: manual and new unit tests PiperOrigin-RevId: 176774942 Change-Id: I08b9afbbefaedfb0de5199038a1d2769bd983855
Diffstat (limited to 'java/com')
-rw-r--r--java/com/android/dialer/app/calllog/CallLogAdapter.java1
-rw-r--r--java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java81
-rw-r--r--java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java5
-rw-r--r--java/com/android/dialer/app/res/layout/call_log_list_item.xml48
-rw-r--r--java/com/android/dialer/app/res/values/colors.xml2
-rw-r--r--java/com/android/dialer/app/res/values/strings.xml31
-rw-r--r--java/com/android/dialer/app/res/values/styles.xml15
-rw-r--r--java/com/android/dialer/calllogutils/PhoneCallDetails.java3
-rw-r--r--java/com/android/dialer/compat/android/provider/VoicemailCompat.java17
-rw-r--r--java/com/android/dialer/constants/ScheduledJobIds.java1
-rw-r--r--java/com/android/voicemail/impl/AndroidManifest.xml5
-rw-r--r--java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java70
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java97
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionRatingService.java90
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionTask.java27
-rw-r--r--java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java25
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java11
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/TranscriptionFeedbackResponseAsync.java35
-rw-r--r--java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto2
19 files changed, 534 insertions, 32 deletions
diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java
index 47ef32d42..4f78bc97f 100644
--- a/java/com/android/dialer/app/calllog/CallLogAdapter.java
+++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java
@@ -960,6 +960,7 @@ public class CallLogAdapter extends GroupingListAdapter
}
views.callType = cursor.getInt(CallLogQuery.CALL_TYPE);
views.voicemailUri = cursor.getString(CallLogQuery.VOICEMAIL_URI);
+ details.voicemailUri = views.voicemailUri;
return details;
}
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
index ad931e87a..3898d1f24 100644
--- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
+++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
@@ -19,30 +19,39 @@ package com.android.dialer.app.calllog;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
+import android.net.Uri;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.support.v4.content.ContextCompat;
import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.util.Linkify;
+import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.dialer.app.R;
import com.android.dialer.app.calllog.calllogcache.CallLogCache;
import com.android.dialer.calllogutils.PhoneCallDetails;
+import com.android.dialer.common.LogUtil;
import com.android.dialer.compat.android.provider.VoicemailCompat;
import com.android.dialer.logging.ContactSource;
import com.android.dialer.oem.MotorolaUtils;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
import com.android.dialer.util.DialerUtils;
+import com.android.voicemail.VoicemailComponent;
+import com.android.voicemail.impl.transcribe.TranscriptionRatingHelper;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionRatingValue;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
/** Helper class to fill in the views in {@link PhoneCallDetailsViews}. */
-public class PhoneCallDetailsHelper {
-
+public class PhoneCallDetailsHelper
+ implements TranscriptionRatingHelper.SuccessListener,
+ TranscriptionRatingHelper.FailureListener {
/** The maximum number of icons will be shown to represent the call types in a group. */
private static final int MAX_CALL_TYPE_ICONS = 3;
@@ -152,13 +161,16 @@ public class PhoneCallDetailsHelper {
String transcript = "";
String branding = "";
+ boolean showRatingPrompt = false;
if (!TextUtils.isEmpty(details.transcription)) {
transcript = details.transcription;
- // Set the branding text if the voicemail was transcribed by google
- // TODO(mdooley): the transcription state is only set by the google transcription code,
- // but a better solution would be to check the SOURCE_PACKAGE
- if (details.transcriptionState == VoicemailCompat.TRANSCRIPTION_AVAILABLE) {
+ // Show a transcription quality rating prompt or set the branding text if the voicemail was
+ // transcribed by google
+ if (shouldShowTranscriptionRating(details.transcriptionState, details.accountHandle)) {
+ showRatingPrompt = true;
+ } else if (details.transcriptionState == VoicemailCompat.TRANSCRIPTION_AVAILABLE
+ || details.transcriptionState == VoicemailCompat.TRANSCRIPTION_AVAILABLE_AND_RATED) {
branding = mResources.getString(R.string.voicemail_transcription_branding_text);
}
} else {
@@ -183,7 +195,28 @@ public class PhoneCallDetailsHelper {
}
views.voicemailTranscriptionView.setText(transcript);
- views.voicemailTranscriptionBrandingView.setText(branding);
+ if (showRatingPrompt) {
+ views.voicemailTranscriptionBrandingView.setVisibility(View.GONE);
+
+ View ratingView = views.voicemailTranscriptionRatingView;
+ ratingView.setVisibility(View.VISIBLE);
+ ratingView
+ .findViewById(R.id.voicemail_transcription_rating_good)
+ .setOnClickListener(
+ view ->
+ recordTranscriptionRating(
+ TranscriptionRatingValue.GOOD_TRANSCRIPTION, details));
+ ratingView
+ .findViewById(R.id.voicemail_transcription_rating_bad)
+ .setOnClickListener(
+ view ->
+ recordTranscriptionRating(TranscriptionRatingValue.BAD_TRANSCRIPTION, details));
+ } else {
+ views.voicemailTranscriptionRatingView.setVisibility(View.GONE);
+
+ views.voicemailTranscriptionBrandingView.setVisibility(View.VISIBLE);
+ views.voicemailTranscriptionBrandingView.setText(branding);
+ }
}
// Bold if not read
@@ -198,6 +231,40 @@ public class PhoneCallDetailsHelper {
details.isRead ? R.color.call_log_detail_color : R.color.call_log_unread_text_color));
}
+ private boolean shouldShowTranscriptionRating(
+ int transcriptionState, PhoneAccountHandle account) {
+ // TODO(mdooley): add a configurable random element here?
+ return transcriptionState == VoicemailCompat.TRANSCRIPTION_AVAILABLE
+ && VoicemailComponent.get(mContext)
+ .getVoicemailClient()
+ .isVoicemailDonationEnabled(mContext, account);
+ }
+
+ private void recordTranscriptionRating(
+ TranscriptionRatingValue ratingValue, PhoneCallDetails details) {
+ LogUtil.enterBlock("PhoneCallDetailsHelper.recordTranscriptionRating");
+ TranscriptionRatingHelper.sendRating(
+ mContext,
+ ratingValue,
+ Uri.parse(details.voicemailUri),
+ this::onRatingSuccess,
+ this::onRatingFailure);
+ }
+
+ @Override
+ public void onRatingSuccess(Uri voicemailUri) {
+ LogUtil.enterBlock("PhoneCallDetailsHelper.onRatingSuccess");
+ Toast toast =
+ Toast.makeText(mContext, R.string.voicemail_transcription_rating_thanks, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 50);
+ toast.show();
+ }
+
+ @Override
+ public void onRatingFailure(Throwable t) {
+ LogUtil.e("PhoneCallDetailsHelper.onRatingFailure", "failed to send rating", t);
+ }
+
/**
* Builds a string containing the call location and date. For voicemail logs only the call date is
* returned because location information is displayed in the call action button
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java
index 40c0894f0..8b7a92bd4 100644
--- a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java
+++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java
@@ -32,6 +32,7 @@ public final class PhoneCallDetailsViews {
public final View transcriptionView;
public final TextView voicemailTranscriptionView;
public final TextView voicemailTranscriptionBrandingView;
+ public final View voicemailTranscriptionRatingView;
public final TextView callAccountLabel;
private PhoneCallDetailsViews(
@@ -42,6 +43,7 @@ public final class PhoneCallDetailsViews {
View transcriptionView,
TextView voicemailTranscriptionView,
TextView voicemailTranscriptionBrandingView,
+ View voicemailTranscriptionRatingView,
TextView callAccountLabel) {
this.nameView = nameView;
this.callTypeView = callTypeView;
@@ -50,6 +52,7 @@ public final class PhoneCallDetailsViews {
this.transcriptionView = transcriptionView;
this.voicemailTranscriptionView = voicemailTranscriptionView;
this.voicemailTranscriptionBrandingView = voicemailTranscriptionBrandingView;
+ this.voicemailTranscriptionRatingView = voicemailTranscriptionRatingView;
this.callAccountLabel = callAccountLabel;
}
@@ -69,6 +72,7 @@ public final class PhoneCallDetailsViews {
view.findViewById(R.id.transcription),
(TextView) view.findViewById(R.id.voicemail_transcription),
(TextView) view.findViewById(R.id.voicemail_transcription_branding),
+ view.findViewById(R.id.voicemail_transcription_rating),
(TextView) view.findViewById(R.id.call_account_label));
}
@@ -81,6 +85,7 @@ public final class PhoneCallDetailsViews {
new View(context),
new TextView(context),
new TextView(context),
+ new View(context),
new TextView(context));
}
}
diff --git a/java/com/android/dialer/app/res/layout/call_log_list_item.xml b/java/com/android/dialer/app/res/layout/call_log_list_item.xml
index e0f9e63b4..75c8fe6e2 100644
--- a/java/com/android/dialer/app/res/layout/call_log_list_item.xml
+++ b/java/com/android/dialer/app/res/layout/call_log_list_item.xml
@@ -150,7 +150,7 @@
<LinearLayout
android:id="@+id/transcription"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/call_log_icon_margin"
android:visibility="gone"
@@ -174,8 +174,54 @@
android:textSize="@dimen/call_log_voicemail_transcription_text_size"
android:focusable="true"
android:nextFocusUp="@id/voicemail_transcription"
+ android:nextFocusDown="@+id/voicemail_transcription_rating"
android:paddingTop="2dp"/>
+ <LinearLayout
+ android:id="@+id/voicemail_transcription_rating"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/call_log_icon_margin"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone"
+ android:paddingTop="2dp"
+ android:orientation="horizontal">
+
+ <TextView
+ style="@style/TranscriptionQualityRating"
+ android:id="@+id/voicemail_transcription_rating_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="start"
+ android:text="@string/voicemail_transcription_rating"/>
+
+ <TextView
+ style="@style/TranscriptionQualityRatingLink"
+ android:id="@+id/voicemail_transcription_rating_good"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:text="@string/voicemail_transcription_rating_good"/>
+
+ <TextView
+ style="@style/TranscriptionQualityRating"
+ android:id="@+id/voicemail_transcription_rating_separator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:text="@string/voicemail_transcription_rating_separator"/>
+
+ <TextView
+ style="@style/TranscriptionQualityRatingLink"
+ android:id="@+id/voicemail_transcription_rating_bad"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:text="@string/voicemail_transcription_rating_bad"/>
+
+ </LinearLayout>
+
</LinearLayout>
</LinearLayout>
diff --git a/java/com/android/dialer/app/res/values/colors.xml b/java/com/android/dialer/app/res/values/colors.xml
index f1f5002ed..84a381f21 100644
--- a/java/com/android/dialer/app/res/values/colors.xml
+++ b/java/com/android/dialer/app/res/values/colors.xml
@@ -34,6 +34,8 @@
<color name="call_log_voicemail_transcript_color">#de000000</color>
<!-- 54% black -->
<color name="call_log_voicemail_transcript_branding_color">#8a000000</color>
+ <!-- 100% blue -->
+ <color name="call_log_voicemail_transcript_rating_color">#ff2a56c6</color>
<!-- 70% black -->
<color name="call_log_action_color">#b3000000</color>
<!-- 54% black -->
diff --git a/java/com/android/dialer/app/res/values/strings.xml b/java/com/android/dialer/app/res/values/strings.xml
index 485bd8994..01c477680 100644
--- a/java/com/android/dialer/app/res/values/strings.xml
+++ b/java/com/android/dialer/app/res/values/strings.xml
@@ -665,6 +665,37 @@
[CHAR LIMIT=64] -->
<string name="voicemail_transcription_failed_no_speech">Transcript not available. No speech detected.</string>
+ <!-- Prompt asking the user to rate the quality of the voicemail transcription [CHAR LIMIT=30]
+ voicemail_transcription_rating, voicemail_transcription_rating_good,
+ voicemail_transcription_rating_separator and voicemail_transcription_rating_bad are
+ used together to form the rating prompt: 'Rate transcription quality Good or Bad'
+ where 'Good' and 'Bad' are clickable links. -->
+ <string name="voicemail_transcription_rating">Rate transcription quality</string>
+
+ <!-- Rating choice indicating that the voicemail transcription was good [CHAR LIMIT=10]
+ voicemail_transcription_rating, voicemail_transcription_rating_good,
+ voicemail_transcription_rating_separator and voicemail_transcription_rating_bad are
+ used together to form the rating prompt: 'Rate transcription quality Good or Bad'
+ where 'Good' and 'Bad' are clickable links. -->
+ <string name="voicemail_transcription_rating_good">Good</string>
+
+ <!-- Rating choice indicating that the voicemail transcription was bad [CHAR LIMIT=10]
+ voicemail_transcription_rating, voicemail_transcription_rating_good,
+ voicemail_transcription_rating_separator and voicemail_transcription_rating_bad are
+ used together to form the rating prompt: 'Rate transcription quality Good or Bad'
+ where 'Good' and 'Bad' are clickable links. -->
+ <string name="voicemail_transcription_rating_bad">Bad</string>
+
+ <!-- Separator between the good and bad transcription rating choices [CHAR LIMIT=10]
+ voicemail_transcription_rating, voicemail_transcription_rating_good,
+ voicemail_transcription_rating_separator and voicemail_transcription_rating_bad are
+ used together to form the rating prompt: 'Rate transcription quality Good or Bad'
+ where 'Good' and 'Bad' are clickable links. -->
+ <string name="voicemail_transcription_rating_separator"> or </string>
+
+ <!-- Message displayed after user has rated a voicemail transcription [CHAR LIMIT=30] -->
+ <string name="voicemail_transcription_rating_thanks">Thanks for your feedback</string>
+
<!-- Button text to prompt a user to open an sms conversation [CHAR LIMIT=NONE] -->
<string name="view_conversation">View</string>
diff --git a/java/com/android/dialer/app/res/values/styles.xml b/java/com/android/dialer/app/res/values/styles.xml
index d464ca7f2..c26821023 100644
--- a/java/com/android/dialer/app/res/values/styles.xml
+++ b/java/com/android/dialer/app/res/values/styles.xml
@@ -251,4 +251,19 @@
<item name="android:layout_height">1dp</item>
<item name="android:background">?android:attr/listDivider</item>
</style>
+
+ <style name="TranscriptionQualityRating">
+ <item name="android:textColor">@color/call_log_voicemail_transcript_branding_color</item>
+ <item name="android:textSize">@dimen/call_log_voicemail_transcription_text_size</item>
+ </style>
+
+ <style name="TranscriptionQualityRatingLink">
+ <item name="android:textColor">@color/call_log_voicemail_transcript_rating_color</item>
+ <item name="android:textSize">@dimen/call_log_voicemail_transcription_text_size</item>
+ <item name="android:paddingTop">8dp</item>
+ <item name="android:paddingBottom">8dp</item>
+ <item name="android:paddingLeft">4dp</item>
+ <item name="android:paddingRight">4dp</item>
+ <item name="android:minHeight">48dp</item>
+ </style>
</resources>
diff --git a/java/com/android/dialer/calllogutils/PhoneCallDetails.java b/java/com/android/dialer/calllogutils/PhoneCallDetails.java
index 869a3d099..fe8bfde43 100644
--- a/java/com/android/dialer/calllogutils/PhoneCallDetails.java
+++ b/java/com/android/dialer/calllogutils/PhoneCallDetails.java
@@ -134,6 +134,9 @@ public class PhoneCallDetails {
public int voicemailId;
public int previousGroup;
+ // The URI of the voicemail associated with this phone call, if this call went to voicemail.
+ public String voicemailUri;
+
/**
* Constructor with required fields for the details of a call with a number associated with a
* contact.
diff --git a/java/com/android/dialer/compat/android/provider/VoicemailCompat.java b/java/com/android/dialer/compat/android/provider/VoicemailCompat.java
index 175ea5d95..02eebb32c 100644
--- a/java/com/android/dialer/compat/android/provider/VoicemailCompat.java
+++ b/java/com/android/dialer/compat/android/provider/VoicemailCompat.java
@@ -72,4 +72,21 @@ public class VoicemailCompat {
* <p>Internal dialer use only, not part of the public SDK.
*/
public static final int TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED = -2;
+
+ /**
+ * Value of {@link #TRANSCRIPTION_STATE} when the voicemail transcription has completed and the
+ * result has been stored in the {@link #TRANSCRIPTION} column of the database, and the user has
+ * provided a quality rating for the transcription.
+ */
+ public static final int TRANSCRIPTION_AVAILABLE_AND_RATED = -3;
+
+ /**
+ * Voicemail transcription quality rating value sent to the server indicating a good transcription
+ */
+ public static final int TRANSCRIPTION_QUALITY_RATING_GOOD = 1;
+
+ /**
+ * Voicemail transcription quality rating value sent to the server indicating a bad transcription
+ */
+ public static final int TRANSCRIPTION_QUALITY_RATING_BAD = 2;
}
diff --git a/java/com/android/dialer/constants/ScheduledJobIds.java b/java/com/android/dialer/constants/ScheduledJobIds.java
index c0835b261..1a852d0e7 100644
--- a/java/com/android/dialer/constants/ScheduledJobIds.java
+++ b/java/com/android/dialer/constants/ScheduledJobIds.java
@@ -46,6 +46,7 @@ public final class ScheduledJobIds {
public static final int VVM_TRANSCRIPTION_JOB = 203;
public static final int VVM_TRANSCRIPTION_BACKFILL_JOB = 204;
public static final int VVM_NOTIFICATION_JOB = 205;
+ public static final int VVM_TRANSCRIPTION_RATING_JOB = 206;
public static final int VOIP_REGISTRATION = 300;
diff --git a/java/com/android/voicemail/impl/AndroidManifest.xml b/java/com/android/voicemail/impl/AndroidManifest.xml
index 4cad2247c..53636092a 100644
--- a/java/com/android/voicemail/impl/AndroidManifest.xml
+++ b/java/com/android/voicemail/impl/AndroidManifest.xml
@@ -102,6 +102,11 @@
android:exported="false"/>
<service
+ android:name="com.android.voicemail.impl.transcribe.TranscriptionRatingService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
+ android:exported="false"/>
+
+ <service
android:name="com.android.voicemail.impl.OmtpService"
android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
android:exported="true"
diff --git a/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java b/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java
index 8fcbf3b97..f6a00f6d6 100644
--- a/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java
+++ b/java/com/android/voicemail/impl/VoicemailTranscriptionServiceGrpc.java
@@ -73,6 +73,15 @@ public class VoicemailTranscriptionServiceGrpc {
"google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionService", "GetTranscript"),
io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest.getDefaultInstance()),
io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse.getDefaultInstance()));
+ @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901")
+ public static final io.grpc.MethodDescriptor<com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest,
+ com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse> METHOD_SEND_TRANSCRIPTION_FEEDBACK =
+ io.grpc.MethodDescriptor.create(
+ io.grpc.MethodDescriptor.MethodType.UNARY,
+ generateFullMethodName(
+ "google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionService", "SendTranscriptionFeedback"),
+ io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest.getDefaultInstance()),
+ io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse.getDefaultInstance()));
/**
* Creates a new async stub that supports all call types for the service
@@ -136,6 +145,17 @@ public class VoicemailTranscriptionServiceGrpc {
asyncUnimplementedUnaryCall(METHOD_GET_TRANSCRIPT, responseObserver);
}
+ /**
+ * <pre>
+ * Uploads user's transcription feedback. Feedback will only be collected from
+ * user's who have consented to donate their voicemails.
+ * </pre>
+ */
+ public void sendTranscriptionFeedback(com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest request,
+ io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse> responseObserver) {
+ asyncUnimplementedUnaryCall(METHOD_SEND_TRANSCRIPTION_FEEDBACK, responseObserver);
+ }
+
@java.lang.Override public io.grpc.ServerServiceDefinition bindService() {
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
.addMethod(
@@ -159,6 +179,13 @@ public class VoicemailTranscriptionServiceGrpc {
com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest,
com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse>(
this, METHODID_GET_TRANSCRIPT)))
+ .addMethod(
+ METHOD_SEND_TRANSCRIPTION_FEEDBACK,
+ asyncUnaryCall(
+ new MethodHandlers<
+ com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest,
+ com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse>(
+ this, METHODID_SEND_TRANSCRIPTION_FEEDBACK)))
.build();
}
}
@@ -218,6 +245,18 @@ public class VoicemailTranscriptionServiceGrpc {
asyncUnaryCall(
getChannel().newCall(METHOD_GET_TRANSCRIPT, getCallOptions()), request, responseObserver);
}
+
+ /**
+ * <pre>
+ * Uploads user's transcription feedback. Feedback will only be collected from
+ * user's who have consented to donate their voicemails.
+ * </pre>
+ */
+ public void sendTranscriptionFeedback(com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest request,
+ io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse> responseObserver) {
+ asyncUnaryCall(
+ getChannel().newCall(METHOD_SEND_TRANSCRIPTION_FEEDBACK, getCallOptions()), request, responseObserver);
+ }
}
/**
@@ -272,6 +311,17 @@ public class VoicemailTranscriptionServiceGrpc {
return blockingUnaryCall(
getChannel(), METHOD_GET_TRANSCRIPT, getCallOptions(), request);
}
+
+ /**
+ * <pre>
+ * Uploads user's transcription feedback. Feedback will only be collected from
+ * user's who have consented to donate their voicemails.
+ * </pre>
+ */
+ public com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse sendTranscriptionFeedback(com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest request) {
+ return blockingUnaryCall(
+ getChannel(), METHOD_SEND_TRANSCRIPTION_FEEDBACK, getCallOptions(), request);
+ }
}
/**
@@ -329,11 +379,24 @@ public class VoicemailTranscriptionServiceGrpc {
return futureUnaryCall(
getChannel().newCall(METHOD_GET_TRANSCRIPT, getCallOptions()), request);
}
+
+ /**
+ * <pre>
+ * Uploads user's transcription feedback. Feedback will only be collected from
+ * user's who have consented to donate their voicemails.
+ * </pre>
+ */
+ public com.google.common.util.concurrent.ListenableFuture<com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse> sendTranscriptionFeedback(
+ com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest request) {
+ return futureUnaryCall(
+ getChannel().newCall(METHOD_SEND_TRANSCRIPTION_FEEDBACK, getCallOptions()), request);
+ }
}
private static final int METHODID_TRANSCRIBE_VOICEMAIL = 0;
private static final int METHODID_TRANSCRIBE_VOICEMAIL_ASYNC = 1;
private static final int METHODID_GET_TRANSCRIPT = 2;
+ private static final int METHODID_SEND_TRANSCRIPTION_FEEDBACK = 3;
private static class MethodHandlers<Req, Resp> implements
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
@@ -364,6 +427,10 @@ public class VoicemailTranscriptionServiceGrpc {
serviceImpl.getTranscript((com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest) request,
(io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.GetTranscriptResponse>) responseObserver);
break;
+ case METHODID_SEND_TRANSCRIPTION_FEEDBACK:
+ serviceImpl.sendTranscriptionFeedback((com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest) request,
+ (io.grpc.stub.StreamObserver<com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse>) responseObserver);
+ break;
default:
throw new AssertionError();
}
@@ -384,7 +451,8 @@ public class VoicemailTranscriptionServiceGrpc {
return new io.grpc.ServiceDescriptor(SERVICE_NAME,
METHOD_TRANSCRIBE_VOICEMAIL,
METHOD_TRANSCRIBE_VOICEMAIL_ASYNC,
- METHOD_GET_TRANSCRIPT);
+ METHOD_GET_TRANSCRIPT,
+ METHOD_SEND_TRANSCRIPTION_FEEDBACK);
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java
new file mode 100644
index 000000000..1cafacecf
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingHelper.java
@@ -0,0 +1,97 @@
+/*
+ * 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.content.Context;
+import android.net.Uri;
+import com.android.dialer.common.concurrent.DialerExecutor;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.compat.android.provider.VoicemailCompat;
+import com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionRating;
+import com.google.internal.communications.voicemailtranscription.v1.TranscriptionRatingValue;
+import com.google.protobuf.ByteString;
+
+/**
+ * Send voicemail transcription rating feedback to the server and record the fact that feedback was
+ * provided in the local database.
+ */
+public class TranscriptionRatingHelper {
+
+ /** Callback invoked after the feedback has been recorded locally */
+ public interface SuccessListener {
+ void onRatingSuccess(Uri voicemailUri);
+ }
+
+ /** Callback invoked if there was an error recording the feedback */
+ public interface FailureListener {
+ void onRatingFailure(Throwable t);
+ }
+
+ /**
+ * Method for sending a user voicemail transcription feedback rating to the server and recording
+ * the fact that the voicemail was rated in the local database.
+ */
+ public static void sendRating(
+ Context context,
+ TranscriptionRatingValue ratingValue,
+ Uri voicemailUri,
+ SuccessListener successListener,
+ FailureListener failureListener) {
+ DialerExecutorComponent.get(context)
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new RatingWorker(context, ratingValue, voicemailUri))
+ .onSuccess(output -> successListener.onRatingSuccess(voicemailUri))
+ .onFailure(e -> failureListener.onRatingFailure(e))
+ .build()
+ .executeParallel(null);
+ }
+
+ /** Worker class used to record a user's quality rating of a voicemail transcription. */
+ private static class RatingWorker implements DialerExecutor.Worker<Void, Void> {
+ private final Context context;
+ private final TranscriptionRatingValue ratingValue;
+ private final Uri voicemailUri;
+
+ private RatingWorker(Context context, TranscriptionRatingValue ratingValue, Uri voicemailUri) {
+ this.context = context;
+ this.ratingValue = ratingValue;
+ this.voicemailUri = voicemailUri;
+ }
+
+ @Override
+ public Void doInBackground(Void input) {
+ // Schedule a task to upload the feedback (requires network connectivity)
+ TranscriptionRatingService.scheduleTask(context, getFeedbackRequest());
+
+ // Record the fact that the transcription has been rated
+ TranscriptionDbHelper dbHelper = new TranscriptionDbHelper(context, voicemailUri);
+ dbHelper.setTranscriptionState(VoicemailCompat.TRANSCRIPTION_AVAILABLE_AND_RATED);
+ return null;
+ }
+
+ private SendTranscriptionFeedbackRequest getFeedbackRequest() {
+ ByteString audioData = TranscriptionUtils.getAudioData(context, voicemailUri);
+ String voicemailId = TranscriptionUtils.getFingerprintFor(audioData);
+ TranscriptionRating rating =
+ TranscriptionRating.newBuilder()
+ .setTranscriptionId(voicemailId)
+ .setRatingValue(ratingValue)
+ .build();
+ return SendTranscriptionFeedbackRequest.newBuilder().addRating(rating).build();
+ }
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionRatingService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingService.java
new file mode 100644
index 000000000..cff2c6d84
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionRatingService.java
@@ -0,0 +1,90 @@
+/*
+ * 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.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.app.job.JobWorkItem;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.support.annotation.WorkerThread;
+import android.support.v4.app.JobIntentService;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.constants.ScheduledJobIds;
+import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
+import com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+/**
+ * JobScheduler service for uploading transcription feedback. This service requires a network
+ * connection.
+ */
+public class TranscriptionRatingService extends JobIntentService {
+ private static final String FEEDBACK_REQUEST_EXTRA = "feedback_request_extra";
+
+ /** Schedule a task to upload transcription rating feedback */
+ public static boolean scheduleTask(Context context, SendTranscriptionFeedbackRequest request) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ LogUtil.enterBlock("TranscriptionRatingService.scheduleTask");
+ ComponentName componentName = new ComponentName(context, TranscriptionRatingService.class);
+ JobInfo.Builder builder =
+ new JobInfo.Builder(ScheduledJobIds.VVM_TRANSCRIPTION_RATING_JOB, componentName)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ JobScheduler scheduler = context.getSystemService(JobScheduler.class);
+ return scheduler.enqueue(builder.build(), makeWorkItem(request))
+ == JobScheduler.RESULT_SUCCESS;
+ } else {
+ LogUtil.i("TranscriptionRatingService.scheduleTask", "not supported");
+ return false;
+ }
+ }
+
+ public TranscriptionRatingService() {}
+
+ private static JobWorkItem makeWorkItem(SendTranscriptionFeedbackRequest request) {
+ Intent intent = new Intent();
+ intent.putExtra(FEEDBACK_REQUEST_EXTRA, request.toByteArray());
+ return new JobWorkItem(intent);
+ }
+
+ @Override
+ @WorkerThread
+ protected void onHandleWork(Intent intent) {
+ LogUtil.enterBlock("TranscriptionRatingService.onHandleWork");
+
+ TranscriptionConfigProvider configProvider = new TranscriptionConfigProvider(this);
+ TranscriptionClientFactory factory = new TranscriptionClientFactory(this, configProvider);
+ try {
+ // Send rating to server
+ SendTranscriptionFeedbackRequest request =
+ SendTranscriptionFeedbackRequest.parseFrom(
+ intent.getByteArrayExtra(FEEDBACK_REQUEST_EXTRA));
+ factory.getClient().sendTranscriptFeedbackRequest(request);
+ } catch (InvalidProtocolBufferException e) {
+ LogUtil.e("TranscriptionRatingService.onHandleWork", "failed to send feedback", e);
+ } finally {
+ factory.shutdown();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ LogUtil.enterBlock("TranscriptionRatingService.onDestroy");
+ super.onDestroy();
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
index f3b1d587f..97cf89eef 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
@@ -15,7 +15,6 @@
*/
package com.android.voicemail.impl.transcribe;
-import android.annotation.TargetApi;
import android.app.job.JobWorkItem;
import android.content.Context;
import android.net.Uri;
@@ -37,8 +36,6 @@ import com.android.voicemail.impl.transcribe.grpc.TranscriptionResponse;
import com.google.internal.communications.voicemailtranscription.v1.AudioFormat;
import com.google.internal.communications.voicemailtranscription.v1.TranscriptionStatus;
import com.google.protobuf.ByteString;
-import java.io.IOException;
-import java.io.InputStream;
/**
* Background task to get a voicemail transcription and update the database.
@@ -71,8 +68,6 @@ public abstract class TranscriptionTask implements Runnable {
protected AudioFormat encoding;
protected volatile boolean cancelled;
- static final String AMR_PREFIX = "#!AMR\n";
-
/** Functional interface for sending requests to the transcription server */
public interface Request {
TranscriptionResponse getResponse(TranscriptionClient client);
@@ -226,8 +221,6 @@ public abstract class TranscriptionTask implements Runnable {
databaseHelper.setTranscriptionState(newState);
}
- // Uses try-with-resource
- @TargetApi(android.os.Build.VERSION_CODES.M)
private boolean readAndValidateAudioFile() {
if (voicemailUri == null) {
VvmLog.i(TAG, "Transcriber.readAndValidateAudioFile, file not found.");
@@ -236,15 +229,15 @@ public abstract class TranscriptionTask implements Runnable {
VvmLog.i(TAG, "Transcriber.readAndValidateAudioFile, reading: " + voicemailUri);
}
- try (InputStream in = context.getContentResolver().openInputStream(voicemailUri)) {
- audioData = ByteString.readFrom(in);
- VvmLog.i(TAG, "Transcriber.readAndValidateAudioFile, read " + audioData.size() + " bytes");
- } catch (IOException e) {
- VvmLog.e(TAG, "Transcriber.readAndValidateAudioFile", e);
+ audioData = TranscriptionUtils.getAudioData(context, voicemailUri);
+ if (audioData != null) {
+ VvmLog.i(TAG, "readAndValidateAudioFile, read " + audioData.size() + " bytes");
+ } else {
+ VvmLog.i(TAG, "readAndValidateAudioFile, unable to read audio data for " + voicemailUri);
return false;
}
- encoding = getAudioFormat(audioData);
+ encoding = TranscriptionUtils.getAudioFormat(audioData);
if (encoding == AudioFormat.AUDIO_FORMAT_UNSPECIFIED) {
VvmLog.i(TAG, "Transcriber.readAndValidateAudioFile, unknown encoding");
return false;
@@ -253,15 +246,9 @@ public abstract class TranscriptionTask implements Runnable {
return true;
}
- private static AudioFormat getAudioFormat(ByteString audioData) {
- return audioData != null && audioData.startsWith(ByteString.copyFromUtf8(AMR_PREFIX))
- ? AudioFormat.AMR_NB_8KHZ
- : AudioFormat.AUDIO_FORMAT_UNSPECIFIED;
- }
-
@VisibleForTesting
void setAudioDataForTesting(ByteString audioData) {
this.audioData = audioData;
- encoding = getAudioFormat(audioData);
+ encoding = TranscriptionUtils.getAudioFormat(audioData);
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java
index a001f179a..36b1400be 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java
@@ -16,17 +16,38 @@
package com.android.voicemail.impl.transcribe;
import android.annotation.TargetApi;
-import android.os.Build.VERSION_CODES;
+import android.content.Context;
+import android.net.Uri;
import android.util.Base64;
import com.android.dialer.common.Assert;
+import com.google.internal.communications.voicemailtranscription.v1.AudioFormat;
import com.google.protobuf.ByteString;
+import java.io.IOException;
+import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** Utility methods used by this transcription package. */
public class TranscriptionUtils {
+ static final String AMR_PREFIX = "#!AMR\n";
- @TargetApi(VERSION_CODES.O)
+ // Uses try-with-resource
+ @TargetApi(android.os.Build.VERSION_CODES.M)
+ static ByteString getAudioData(Context context, Uri voicemailUri) {
+ try (InputStream in = context.getContentResolver().openInputStream(voicemailUri)) {
+ return ByteString.readFrom(in);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ static AudioFormat getAudioFormat(ByteString audioData) {
+ return audioData != null && audioData.startsWith(ByteString.copyFromUtf8(AMR_PREFIX))
+ ? AudioFormat.AMR_NB_8KHZ
+ : AudioFormat.AUDIO_FORMAT_UNSPECIFIED;
+ }
+
+ @TargetApi(android.os.Build.VERSION_CODES.O)
static String getFingerprintFor(ByteString data) {
Assert.checkArgument(data != null);
try {
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java
index b18d95627..381cb3268 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionClient.java
@@ -17,6 +17,7 @@ package com.android.voicemail.impl.transcribe.grpc;
import android.support.annotation.WorkerThread;
import com.google.internal.communications.voicemailtranscription.v1.GetTranscriptRequest;
+import com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackRequest;
import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailAsyncRequest;
import com.google.internal.communications.voicemailtranscription.v1.TranscribeVoicemailRequest;
import com.google.internal.communications.voicemailtranscription.v1.VoicemailTranscriptionServiceGrpc;
@@ -58,4 +59,14 @@ public class TranscriptionClient {
return new GetTranscriptResponseAsync(e.getStatus());
}
}
+
+ @WorkerThread
+ public TranscriptionFeedbackResponseAsync sendTranscriptFeedbackRequest(
+ SendTranscriptionFeedbackRequest request) {
+ try {
+ return new TranscriptionFeedbackResponseAsync(stub.sendTranscriptionFeedback(request));
+ } catch (StatusRuntimeException e) {
+ return new TranscriptionFeedbackResponseAsync(e.getStatus());
+ }
+ }
}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionFeedbackResponseAsync.java b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionFeedbackResponseAsync.java
new file mode 100644
index 000000000..bc6155bbd
--- /dev/null
+++ b/java/com/android/voicemail/impl/transcribe/grpc/TranscriptionFeedbackResponseAsync.java
@@ -0,0 +1,35 @@
+/*
+ * 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.grpc;
+
+import android.support.annotation.VisibleForTesting;
+import com.android.dialer.common.Assert;
+import com.google.internal.communications.voicemailtranscription.v1.SendTranscriptionFeedbackResponse;
+import io.grpc.Status;
+
+/** Container for response and status objects for an asynchronous transcription feedback request */
+public class TranscriptionFeedbackResponseAsync extends TranscriptionResponse {
+
+ @VisibleForTesting
+ public TranscriptionFeedbackResponseAsync(SendTranscriptionFeedbackResponse response) {
+ Assert.checkArgument(response != null);
+ }
+
+ @VisibleForTesting
+ public TranscriptionFeedbackResponseAsync(Status status) {
+ super(status);
+ }
+}
diff --git a/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto b/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto
index c46fb2176..8248b02a7 100644
--- a/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto
+++ b/java/com/android/voicemail/impl/transcribe/grpc/voicemail_transcription.proto
@@ -184,7 +184,7 @@ service VoicemailTranscriptionService {
// Uploads user's transcription feedback. Feedback will only be collected from
// user's who have consented to donate their voicemails.
- rpc SendTranscriptionFeedback(SendTranscriptionFeedbackResponse)
+ rpc SendTranscriptionFeedback(SendTranscriptionFeedbackRequest)
returns (SendTranscriptionFeedbackResponse) {
}
}