Merge "Don't use guava prebuilts outside of annotations processing."
diff --git a/Android.mk b/Android.mk
index 461f122..0f13891 100644
--- a/Android.mk
+++ b/Android.mk
@@ -15,14 +15,6 @@
# The base directory for Dialer sources.
BASE_DIR := java/com/android
-# Primary dialer module sources.
-SRC_DIRS := \
- $(BASE_DIR)/contacts/common \
- $(BASE_DIR)/dialer \
- $(BASE_DIR)/dialershared \
- $(BASE_DIR)/incallui \
- $(BASE_DIR)/voicemail
-
# Exclude files incompatible with AOSP.
EXCLUDE_FILES := \
$(BASE_DIR)/incallui/calllocation/impl/AuthException.java \
@@ -76,8 +68,8 @@
LOCAL_FULL_LIBS_MANIFEST_FILES := \
$(addprefix $(LOCAL_PATH)/, $(DIALER_MANIFEST_FILES))
-LOCAL_SRC_FILES := $(call all-java-files-under, $(SRC_DIRS))
-LOCAL_SRC_FILES += $(call all-proto-files-under, $(SRC_DIRS))
+LOCAL_SRC_FILES := $(call all-java-files-under, $(BASE_DIR))
+LOCAL_SRC_FILES += $(call all-proto-files-under, $(BASE_DIR))
LOCAL_SRC_FILES := $(filter-out $(EXCLUDE_FILES),$(LOCAL_SRC_FILES))
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)
@@ -93,6 +85,7 @@
# We specify each package explicitly to glob resource files.
# find . -type f -name "AndroidManifest.xml" | uniq | sort | cut -c 8- | rev | cut -c 21- | rev | sed 's/\//./g' | sed 's/$/ \\/'
LOCAL_AAPT_FLAGS := \
+ com.android.bubble \
com.android.contacts.common \
com.android.dialer.about \
com.android.dialer.app \
@@ -131,7 +124,6 @@
com.android.dialer.searchfragment.list \
com.android.dialer.searchfragment.nearbyplaces \
com.android.dialer.searchfragment.remote \
- com.android.dialershared.bubble \
com.android.dialer.shortcuts \
com.android.dialer.simulator.impl \
com.android.dialer.speeddial \
@@ -233,7 +225,7 @@
# Proguard includes
-LOCAL_PROGUARD_FLAG_FILES := $(call all-named-files-under,proguard.*flags,$(SRC_DIRS))
+LOCAL_PROGUARD_FLAG_FILES := $(call all-named-files-under,proguard.*flags,$(BASE_DIR))
LOCAL_PROGUARD_ENABLED := custom
LOCAL_PROGUARD_ENABLED += optimization
@@ -255,7 +247,6 @@
# Cleanup local state
BASE_DIR :=
-SRC_DIRS :=
EXCLUDE_FILES :=
RES_DIRS :=
DIALER_MANIFEST_FILES :=
diff --git a/java/com/android/dialershared/bubble/AndroidManifest.xml b/java/com/android/bubble/AndroidManifest.xml
similarity index 94%
rename from java/com/android/dialershared/bubble/AndroidManifest.xml
rename to java/com/android/bubble/AndroidManifest.xml
index 1a94aaf..80efe5c 100644
--- a/java/com/android/dialershared/bubble/AndroidManifest.xml
+++ b/java/com/android/bubble/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.dialershared.bubble">
+ package="com.android.bubble">
<uses-sdk android:minSdkVersion="21"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
diff --git a/java/com/android/dialershared/bubble/Bubble.java b/java/com/android/bubble/Bubble.java
similarity index 95%
rename from java/com/android/dialershared/bubble/Bubble.java
rename to java/com/android/bubble/Bubble.java
index d245522..9abfa43 100644
--- a/java/com/android/dialershared/bubble/Bubble.java
+++ b/java/com/android/bubble/Bubble.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.dialershared.bubble;
+package com.android.bubble;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -61,7 +61,7 @@
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewAnimator;
-import com.android.dialershared.bubble.BubbleInfo.Action;
+import com.android.bubble.BubbleInfo.Action;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -87,8 +87,7 @@
private final Context context;
private final WindowManager windowManager;
- private final Handler handler = new Handler();
-
+ private final Handler handler;
private LayoutParams windowParams;
// Initialized in factory method
@@ -100,6 +99,7 @@
private boolean expanded;
private boolean textShowing;
private boolean hideAfterText;
+ private CharSequence textAfterShow;
private int collapseEndAction;
@VisibleForTesting ViewHolder viewHolder;
@@ -107,6 +107,21 @@
private Integer overrideGravity;
private ViewPropertyAnimator exitAnimator;
+ private final Runnable collapseRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ textShowing = false;
+ if (hideAfterText) {
+ // Always reset here since text shouldn't keep showing.
+ hideAndReset();
+ } else {
+ doResize(
+ () -> viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_ICON));
+ }
+ }
+ };
+
private BubbleExpansionStateListener bubbleExpansionStateListener;
@Retention(RetentionPolicy.SOURCE)
@@ -163,13 +178,13 @@
/** Creates instances of Bubble. The default implementation just calls the constructor. */
@VisibleForTesting
public interface BubbleFactory {
- Bubble createBubble(@NonNull Context context);
+ Bubble createBubble(@NonNull Context context, @NonNull Handler handler);
}
private static BubbleFactory bubbleFactory = Bubble::new;
public static Bubble createBubble(@NonNull Context context, @NonNull BubbleInfo info) {
- Bubble bubble = bubbleFactory.createBubble(context);
+ Bubble bubble = bubbleFactory.createBubble(context, new Handler());
bubble.setBubbleInfo(info);
return bubble;
}
@@ -185,14 +200,55 @@
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- Bubble(@NonNull Context context) {
+ Bubble(@NonNull Context context, @NonNull Handler handler) {
context = new ContextThemeWrapper(context, R.style.Theme_AppCompat);
this.context = context;
+ this.handler = handler;
windowManager = context.getSystemService(WindowManager.class);
viewHolder = new ViewHolder(context);
}
+ /** Expands the main bubble menu. */
+ public void expand() {
+ if (expanded || textShowing || currentInfo.getActions().isEmpty()) {
+ try {
+ currentInfo.getPrimaryIntent().send();
+ } catch (CanceledException e) {
+ throw new RuntimeException(e);
+ }
+ return;
+ }
+
+ if (bubbleExpansionStateListener != null) {
+ bubbleExpansionStateListener.onBubbleExpansionStateChanged(ExpansionState.START_EXPANDING);
+ }
+ doResize(
+ () -> {
+ onLeftRightSwitch(isDrawingFromRight());
+ viewHolder.setDrawerVisibility(View.VISIBLE);
+ });
+ View expandedView = viewHolder.getExpandedView();
+ expandedView
+ .getViewTreeObserver()
+ .addOnPreDrawListener(
+ new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ expandedView.getViewTreeObserver().removeOnPreDrawListener(this);
+ expandedView.setTranslationX(
+ isDrawingFromRight() ? expandedView.getWidth() : -expandedView.getWidth());
+ expandedView
+ .animate()
+ .setInterpolator(new LinearOutSlowInInterpolator())
+ .translationX(0);
+ return false;
+ }
+ });
+ setFocused(true);
+ expanded = true;
+ }
+
/**
* Make the bubble visible. Will show a short entrance animation as it enters. If the bubble is
* already showing this method does nothing.
@@ -249,7 +305,15 @@
.setInterpolator(new OvershootInterpolator())
.scaleX(1)
.scaleY(1)
- .withEndAction(() -> visibility = Visibility.SHOWING)
+ .withEndAction(
+ () -> {
+ visibility = Visibility.SHOWING;
+ // Show the queued up text, if available.
+ if (textAfterShow != null) {
+ showText(textAfterShow);
+ textAfterShow = null;
+ }
+ })
.start();
updatePrimaryIconAnimation();
@@ -325,6 +389,12 @@
transition.addTarget(startValues.view);
transition.captureStartValues(startValues);
+ // If our view is not laid out yet, postpone showing the text.
+ if (startValues.values.isEmpty()) {
+ textAfterShow = text;
+ return;
+ }
+
doResize(
() -> {
doShowText(text);
@@ -371,19 +441,8 @@
});
});
}
- handler.removeCallbacks(null);
- handler.postDelayed(
- () -> {
- textShowing = false;
- if (hideAfterText) {
- // Always reset here since text shouldn't keep showing.
- hideAndReset();
- } else {
- doResize(
- () -> viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_ICON));
- }
- },
- SHOW_TEXT_DURATION_MILLIS);
+ handler.removeCallbacks(collapseRunnable);
+ handler.postDelayed(collapseRunnable, SHOW_TEXT_DURATION_MILLIS);
}
public void setBubbleExpansionStateListener(
@@ -415,42 +474,7 @@
}
void primaryButtonClick() {
- if (expanded || textShowing || currentInfo.getActions().isEmpty()) {
- try {
- currentInfo.getPrimaryIntent().send();
- } catch (CanceledException e) {
- throw new RuntimeException(e);
- }
- return;
- }
-
- if (bubbleExpansionStateListener != null) {
- bubbleExpansionStateListener.onBubbleExpansionStateChanged(ExpansionState.START_EXPANDING);
- }
- doResize(
- () -> {
- onLeftRightSwitch(isDrawingFromRight());
- viewHolder.setDrawerVisibility(View.VISIBLE);
- });
- View expandedView = viewHolder.getExpandedView();
- expandedView
- .getViewTreeObserver()
- .addOnPreDrawListener(
- new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- expandedView.getViewTreeObserver().removeOnPreDrawListener(this);
- expandedView.setTranslationX(
- isDrawingFromRight() ? expandedView.getWidth() : -expandedView.getWidth());
- expandedView
- .animate()
- .setInterpolator(new LinearOutSlowInInterpolator())
- .translationX(0);
- return false;
- }
- });
- setFocused(true);
- expanded = true;
+ expand();
}
void onLeftRightSwitch(boolean onRight) {
diff --git a/java/com/android/dialershared/bubble/BubbleInfo.java b/java/com/android/bubble/BubbleInfo.java
similarity index 98%
rename from java/com/android/dialershared/bubble/BubbleInfo.java
rename to java/com/android/bubble/BubbleInfo.java
index eb9abd0..b4f81b3 100644
--- a/java/com/android/dialershared/bubble/BubbleInfo.java
+++ b/java/com/android/bubble/BubbleInfo.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.dialershared.bubble;
+package com.android.bubble;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
diff --git a/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java b/java/com/android/bubble/ChangeOnScreenBounds.java
similarity index 99%
rename from java/com/android/dialershared/bubble/ChangeOnScreenBounds.java
rename to java/com/android/bubble/ChangeOnScreenBounds.java
index 8cd61af..0a7adf6 100644
--- a/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java
+++ b/java/com/android/bubble/ChangeOnScreenBounds.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.dialershared.bubble;
+package com.android.bubble;
import android.animation.Animator;
import android.animation.AnimatorSet;
diff --git a/java/com/android/dialershared/bubble/CheckableImageButton.java b/java/com/android/bubble/CheckableImageButton.java
similarity index 98%
rename from java/com/android/dialershared/bubble/CheckableImageButton.java
rename to java/com/android/bubble/CheckableImageButton.java
index 7a5a432..dd9acce 100644
--- a/java/com/android/dialershared/bubble/CheckableImageButton.java
+++ b/java/com/android/bubble/CheckableImageButton.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.dialershared.bubble;
+package com.android.bubble;
import android.content.Context;
import android.support.v4.view.AccessibilityDelegateCompat;
diff --git a/java/com/android/dialershared/bubble/MoveHandler.java b/java/com/android/bubble/MoveHandler.java
similarity index 99%
rename from java/com/android/dialershared/bubble/MoveHandler.java
rename to java/com/android/bubble/MoveHandler.java
index 33507ef..06efbd4 100644
--- a/java/com/android/dialershared/bubble/MoveHandler.java
+++ b/java/com/android/bubble/MoveHandler.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.dialershared.bubble;
+package com.android.bubble;
import android.content.Context;
import android.graphics.Point;
diff --git a/java/com/android/dialershared/bubble/WindowRoot.java b/java/com/android/bubble/WindowRoot.java
similarity index 98%
rename from java/com/android/dialershared/bubble/WindowRoot.java
rename to java/com/android/bubble/WindowRoot.java
index 81d6b48..b9024c4 100644
--- a/java/com/android/dialershared/bubble/WindowRoot.java
+++ b/java/com/android/bubble/WindowRoot.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.dialershared.bubble;
+package com.android.bubble;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/java/com/android/dialershared/bubble/res/color/bubble_checkable_mask.xml b/java/com/android/bubble/res/color/bubble_checkable_mask.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/color/bubble_checkable_mask.xml
rename to java/com/android/bubble/res/color/bubble_checkable_mask.xml
diff --git a/java/com/android/dialershared/bubble/res/color/bubble_icon_tint_states.xml b/java/com/android/bubble/res/color/bubble_icon_tint_states.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/color/bubble_icon_tint_states.xml
rename to java/com/android/bubble/res/color/bubble_icon_tint_states.xml
diff --git a/java/com/android/dialershared/bubble/res/drawable/bubble_background_pill_ltr.xml b/java/com/android/bubble/res/drawable/bubble_background_pill_ltr.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/drawable/bubble_background_pill_ltr.xml
rename to java/com/android/bubble/res/drawable/bubble_background_pill_ltr.xml
diff --git a/java/com/android/dialershared/bubble/res/drawable/bubble_background_pill_rtl.xml b/java/com/android/bubble/res/drawable/bubble_background_pill_rtl.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/drawable/bubble_background_pill_rtl.xml
rename to java/com/android/bubble/res/drawable/bubble_background_pill_rtl.xml
diff --git a/java/com/android/dialershared/bubble/res/drawable/bubble_ripple_checkable_circle.xml b/java/com/android/bubble/res/drawable/bubble_ripple_checkable_circle.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/drawable/bubble_ripple_checkable_circle.xml
rename to java/com/android/bubble/res/drawable/bubble_ripple_checkable_circle.xml
diff --git a/java/com/android/dialershared/bubble/res/drawable/bubble_ripple_circle.xml b/java/com/android/bubble/res/drawable/bubble_ripple_circle.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/drawable/bubble_ripple_circle.xml
rename to java/com/android/bubble/res/drawable/bubble_ripple_circle.xml
diff --git a/java/com/android/dialershared/bubble/res/layout/bubble_base.xml b/java/com/android/bubble/res/layout/bubble_base.xml
similarity index 96%
rename from java/com/android/dialershared/bubble/res/layout/bubble_base.xml
rename to java/com/android/bubble/res/layout/bubble_base.xml
index 76970f0..3b5735c 100644
--- a/java/com/android/dialershared/bubble/res/layout/bubble_base.xml
+++ b/java/com/android/bubble/res/layout/bubble_base.xml
@@ -54,7 +54,7 @@
android:visibility="gone"
tools:backgroundTint="#FF0000FF"
tools:visibility="visible">
- <com.android.dialershared.bubble.CheckableImageButton
+ <com.android.bubble.CheckableImageButton
android:id="@+id/bubble_icon_first"
android:layout_width="@dimen/bubble_size"
android:layout_height="@dimen/bubble_size"
@@ -64,7 +64,7 @@
android:tintMode="src_in"
tools:background="@drawable/bubble_ripple_checkable_circle"
tools:src="@android:drawable/ic_lock_idle_lock"/>
- <com.android.dialershared.bubble.CheckableImageButton
+ <com.android.bubble.CheckableImageButton
android:id="@+id/bubble_icon_second"
android:layout_width="@dimen/bubble_size"
android:layout_height="@dimen/bubble_size"
@@ -74,7 +74,7 @@
android:tintMode="src_in"
tools:background="@drawable/bubble_ripple_checkable_circle"
tools:src="@android:drawable/ic_input_add"/>
- <com.android.dialershared.bubble.CheckableImageButton
+ <com.android.bubble.CheckableImageButton
android:id="@+id/bubble_icon_third"
android:layout_width="@dimen/bubble_size"
android:layout_height="@dimen/bubble_size"
diff --git a/java/com/android/dialershared/bubble/res/values/colors.xml b/java/com/android/bubble/res/values/colors.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/values/colors.xml
rename to java/com/android/bubble/res/values/colors.xml
diff --git a/java/com/android/dialershared/bubble/res/values/values.xml b/java/com/android/bubble/res/values/values.xml
similarity index 100%
rename from java/com/android/dialershared/bubble/res/values/values.xml
rename to java/com/android/bubble/res/values/values.xml
diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml
index 1c04b76..2ef5dad 100644
--- a/java/com/android/dialer/app/AndroidManifest.xml
+++ b/java/com/android/dialer/app/AndroidManifest.xml
@@ -61,6 +61,18 @@
<application android:theme="@style/Theme.AppCompat">
<activity
+ android:exported="false"
+ android:label="@string/manage_blocked_numbers_label"
+ android:name="com.android.dialer.app.filterednumber.BlockedNumbersSettingsActivity"
+ android:parentActivityName="com.android.dialer.app.settings.DialerSettingsActivity"
+ android:theme="@style/ManageBlockedNumbersStyle">
+ <intent-filter>
+ <action android:name="com.android.dialer.action.BLOCKED_NUMBERS_SETTINGS"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <activity
android:label="@string/call_log_activity_title"
android:name="com.android.dialer.app.calllog.CallLogActivity"
android:theme="@style/DialtactsThemeWithoutActionBarOverlay">
@@ -91,6 +103,11 @@
android:name="com.android.dialer.app.calllog.CallLogNotificationsService"
/>
+ <service
+ android:name="com.android.dialer.app.calllog.VoicemailNotificationJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE"
+ />
+
<receiver
android:directBootAware="true"
android:name="com.android.dialer.app.calllog.MissedCallNotificationReceiver">
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 7f5a9b9..ec67f54 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -695,10 +695,16 @@
int resId = view.getId();
if (resId == R.id.floating_action_button) {
if (!mIsDialpadShown) {
+ LogUtil.i(
+ "DialtactsActivity.onClick", "floating action button clicked, going to show dialpad");
PerformanceReport.recordClick(UiAction.Type.OPEN_DIALPAD);
mInCallDialpadUp = false;
showDialpadFragment(true);
PostCall.closePrompt();
+ } else {
+ LogUtil.i(
+ "DialtactsActivity.onClick",
+ "floating action button clicked, but dialpad is already showing");
}
} else if (resId == R.id.voice_search_button) {
try {
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 60ed7dd..ef6236b 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -781,19 +781,28 @@
View transcriptContainerView = phoneCallDetailsViews.transcriptionView;
TextView transcriptView = phoneCallDetailsViews.voicemailTranscriptionView;
TextView transcriptBrandingView = phoneCallDetailsViews.voicemailTranscriptionBrandingView;
- if (TextUtils.isEmpty(transcriptView.getText())) {
- Assert.checkArgument(TextUtils.isEmpty(transcriptBrandingView.getText()));
- }
- if (!isExpanded || TextUtils.isEmpty(transcriptView.getText())) {
+ if (!isExpanded) {
transcriptContainerView.setVisibility(View.GONE);
return;
}
- transcriptContainerView.setVisibility(View.VISIBLE);
- transcriptView.setVisibility(View.VISIBLE);
- if (TextUtils.isEmpty(transcriptBrandingView.getText())) {
- phoneCallDetailsViews.voicemailTranscriptionBrandingView.setVisibility(View.GONE);
+
+ boolean show = false;
+ if (TextUtils.isEmpty(transcriptView.getText())) {
+ transcriptView.setVisibility(View.GONE);
} else {
- phoneCallDetailsViews.voicemailTranscriptionBrandingView.setVisibility(View.VISIBLE);
+ transcriptView.setVisibility(View.VISIBLE);
+ show = true;
+ }
+ if (TextUtils.isEmpty(transcriptBrandingView.getText())) {
+ transcriptBrandingView.setVisibility(View.GONE);
+ } else {
+ transcriptBrandingView.setVisibility(View.VISIBLE);
+ show = true;
+ }
+ if (show) {
+ transcriptContainerView.setVisibility(View.VISIBLE);
+ } else {
+ transcriptContainerView.setVisibility(View.GONE);
}
}
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java
index 43e03e9..2f8b1f4 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java
@@ -24,7 +24,7 @@
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build.VERSION_CODES;
+import android.os.Build;
import android.provider.CallLog.Calls;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -35,14 +35,17 @@
import com.android.dialer.app.R;
import com.android.dialer.calllogutils.PhoneNumberDisplayUtil;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.android.provider.VoicemailCompat;
import com.android.dialer.location.GeoUtil;
import com.android.dialer.phonenumbercache.ContactInfo;
import com.android.dialer.phonenumbercache.ContactInfoHelper;
import com.android.dialer.util.PermissionsUtil;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/** Helper class operating on call log notifications. */
+@TargetApi(Build.VERSION_CODES.M)
public class CallLogNotificationsQueryHelper {
private final Context mContext;
@@ -133,6 +136,10 @@
return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver);
}
+ NewCallsQuery getNewCallsQuery() {
+ return mNewCallsQuery;
+ }
+
/**
* Get all voicemails with the "new" flag set to 1.
*
@@ -216,6 +223,10 @@
/** Returns the new calls of a certain type for which a notification should be generated. */
@Nullable
List<NewCall> query(int type);
+
+ /** Returns a {@link NewCall} pointed by the {@code callsUri} */
+ @Nullable
+ NewCall query(Uri callsUri);
}
/** Information about a new voicemail. */
@@ -230,6 +241,7 @@
public final String transcription;
public final String countryIso;
public final long dateMs;
+ public final int transcriptionState;
public NewCall(
Uri callsUri,
@@ -240,7 +252,8 @@
String accountId,
String transcription,
String countryIso,
- long dateMs) {
+ long dateMs,
+ int transcriptionState) {
this.callsUri = callsUri;
this.voicemailUri = voicemailUri;
this.number = number;
@@ -250,6 +263,7 @@
this.transcription = transcription;
this.countryIso = countryIso;
this.dateMs = dateMs;
+ this.transcriptionState = transcriptionState;
}
}
@@ -270,6 +284,16 @@
Calls.COUNTRY_ISO,
Calls.DATE
};
+
+ private static final String[] PROJECTION_O;
+
+ static {
+ List<String> list = new ArrayList<>();
+ list.addAll(Arrays.asList(PROJECTION));
+ list.add(VoicemailCompat.TRANSCRIPTION_STATE);
+ PROJECTION_O = list.toArray(new String[list.size()]);
+ }
+
private static final int ID_COLUMN_INDEX = 0;
private static final int NUMBER_COLUMN_INDEX = 1;
private static final int VOICEMAIL_URI_COLUMN_INDEX = 2;
@@ -279,6 +303,7 @@
private static final int TRANSCRIPTION_COLUMN_INDEX = 6;
private static final int COUNTRY_ISO_COLUMN_INDEX = 7;
private static final int DATE_COLUMN_INDEX = 8;
+ private static final int TRANSCRIPTION_STATE_COLUMN_INDEX = 9;
private final ContentResolver mContentResolver;
private final Context mContext;
@@ -290,7 +315,7 @@
@Override
@Nullable
- @TargetApi(VERSION_CODES.M)
+ @TargetApi(Build.VERSION_CODES.M)
public List<NewCall> query(int type) {
if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) {
LogUtil.w(
@@ -298,12 +323,18 @@
"no READ_CALL_LOG permission, returning null for calls lookup.");
return null;
}
- final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE);
+ // A call is "new" when:
+ // NEW is 1. usually set when a new row is inserted
+ // TYPE matches the query type.
+ // IS_READ is not 1. A call might be backed up and restored, so it will be "new" to the
+ // call log, but the user has already read it on another device.
+ final String selection =
+ String.format("%s = 1 AND %s = ? AND %s IS NOT 1", Calls.NEW, Calls.TYPE, Calls.IS_READ);
final String[] selectionArgs = new String[] {Integer.toString(type)};
try (Cursor cursor =
mContentResolver.query(
Calls.CONTENT_URI_WITH_VOICEMAIL,
- PROJECTION,
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? PROJECTION_O : PROJECTION,
selection,
selectionArgs,
Calls.DEFAULT_SORT_ORDER)) {
@@ -323,6 +354,26 @@
}
}
+ @Nullable
+ @Override
+ public NewCall query(Uri callsUri) {
+ if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) {
+ LogUtil.w(
+ "CallLogNotificationsQueryHelper.DefaultNewCallsQuery.query",
+ "No READ_CALL_LOG permission, returning null for calls lookup.");
+ return null;
+ }
+ try (Cursor cursor = mContentResolver.query(callsUri, PROJECTION, null, null, null)) {
+ if (cursor == null) {
+ return null;
+ }
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+ return createNewCallsFromCursor(cursor);
+ }
+ }
+
/** Returns an instance of {@link NewCall} created by using the values of the cursor. */
private NewCall createNewCallsFromCursor(Cursor cursor) {
String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX);
@@ -339,7 +390,10 @@
cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX),
cursor.getString(TRANSCRIPTION_COLUMN_INDEX),
cursor.getString(COUNTRY_ISO_COLUMN_INDEX),
- cursor.getLong(DATE_COLUMN_INDEX));
+ cursor.getLong(DATE_COLUMN_INDEX),
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ ? cursor.getInt(TRANSCRIPTION_STATE_COLUMN_INDEX)
+ : VoicemailCompat.TRANSCRIPTION_NOT_STARTED);
}
}
}
diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
index b363b5a..de76619 100644
--- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java
+++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
@@ -47,6 +47,7 @@
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.compat.android.provider.VoicemailCompat;
import com.android.dialer.notification.DialerNotificationManager;
import com.android.dialer.notification.NotificationChannelId;
import com.android.dialer.notification.NotificationManagerUtils;
@@ -153,7 +154,8 @@
null,
null,
null,
- System.currentTimeMillis());
+ System.currentTimeMillis(),
+ VoicemailCompat.TRANSCRIPTION_NOT_STARTED);
// TODO: look up caller ID that is not in contacts.
ContactInfo contactInfo =
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
index a6e8f10..189279e 100644
--- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
+++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
@@ -146,31 +146,41 @@
if (isVoicemail) {
int relevantLinkTypes = Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS;
views.voicemailTranscriptionView.setAutoLinkMask(relevantLinkTypes);
- boolean showTranscriptBranding = false;
+
+ String transcript = "";
+ String branding = "";
if (!TextUtils.isEmpty(details.transcription)) {
- views.voicemailTranscriptionView.setText(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
- showTranscriptBranding =
- details.transcriptionState == VoicemailCompat.TRANSCRIPTION_AVAILABLE;
+ if (details.transcriptionState == VoicemailCompat.TRANSCRIPTION_AVAILABLE) {
+ branding = mResources.getString(R.string.voicemail_transcription_branding_text);
+ }
} else {
- if (details.transcriptionState == VoicemailCompat.TRANSCRIPTION_IN_PROGRESS) {
- views.voicemailTranscriptionView.setText(
- mResources.getString(R.string.voicemail_transcription_in_progress));
- } else if (details.transcriptionState == VoicemailCompat.TRANSCRIPTION_FAILED) {
- views.voicemailTranscriptionView.setText(
- mResources.getString(R.string.voicemail_transcription_failed));
+ switch (details.transcriptionState) {
+ case VoicemailCompat.TRANSCRIPTION_IN_PROGRESS:
+ branding = mResources.getString(R.string.voicemail_transcription_in_progress);
+ break;
+ case VoicemailCompat.TRANSCRIPTION_FAILED_NO_SPEECH_DETECTED:
+ branding = mResources.getString(R.string.voicemail_transcription_failed_no_speech);
+ break;
+ case VoicemailCompat.TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED:
+ branding =
+ mResources.getString(
+ R.string.voicemail_transcription_failed_language_not_supported);
+ break;
+ case VoicemailCompat.TRANSCRIPTION_FAILED:
+ branding = mResources.getString(R.string.voicemail_transcription_failed);
+ break;
+ default:
+ break; // Fall through
}
}
- if (showTranscriptBranding) {
- views.voicemailTranscriptionBrandingView.setText(
- mResources.getString(R.string.voicemail_transcription_branding_text));
- } else {
- views.voicemailTranscriptionBrandingView.setText("");
- }
+ views.voicemailTranscriptionView.setText(transcript);
+ views.voicemailTranscriptionBrandingView.setText(branding);
}
// Bold if not read
diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java
index cbadfd3..ceae3d3 100644
--- a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java
+++ b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java
@@ -27,7 +27,6 @@
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v4.os.BuildCompat;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.TelephonyManager;
@@ -39,6 +38,7 @@
import com.android.dialer.app.contactinfo.ContactPhotoLoader;
import com.android.dialer.app.list.DialtactsPagerAdapter;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.android.provider.VoicemailCompat;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.notification.DialerNotificationManager;
@@ -54,9 +54,9 @@
/** Prefix used to generate a unique tag for each voicemail notification. */
private static final String NOTIFICATION_TAG_PREFIX = "VisualVoicemail_";
/** Common ID for all voicemail notifications. */
- private static final int NOTIFICATION_ID = 1;
+ static final int NOTIFICATION_ID = 1;
/** Tag for the group summary notification. */
- private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_VisualVoicemail";
+ static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_VisualVoicemail";
/**
* Key used to associate all voicemail notifications and the summary as belonging to a single
* group.
@@ -84,7 +84,7 @@
.setGroupSummary(true)
.setContentIntent(newVoicemailIntent(context, null));
- if (BuildCompat.isAtLeastO()) {
+ if (VERSION.SDK_INT >= VERSION_CODES.O) {
groupSummary.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
PhoneAccountHandle handle = getAccountForCall(context, newCalls.get(0));
groupSummary.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle));
@@ -136,7 +136,7 @@
.setAutoCancel(true);
}
- private static Notification createNotificationForVoicemail(
+ static Notification createNotificationForVoicemail(
@NonNull Context context,
@NonNull NewCall voicemail,
@NonNull Map<String, ContactInfo> contactInfos) {
@@ -146,10 +146,6 @@
Notification.Builder builder =
createNotificationBuilder(context)
.setContentTitle(
- context
- .getResources()
- .getQuantityString(R.plurals.notification_voicemail_title, 1, 1))
- .setContentText(
ContactDisplayUtils.getTtsSpannedPhoneNumber(
context.getResources(),
R.string.notification_new_voicemail_ticker,
@@ -158,13 +154,51 @@
.setSound(getVoicemailRingtoneUri(context, handle))
.setDefaults(getNotificationDefaultFlags(context, handle));
+ if (!TextUtils.isEmpty(voicemail.transcription)) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION);
+ builder.setContentText(voicemail.transcription);
+ } else {
+ switch (voicemail.transcriptionState) {
+ case VoicemailCompat.TRANSCRIPTION_IN_PROGRESS:
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_IN_PROGRESS);
+ builder.setContentText(context.getString(R.string.voicemail_transcription_in_progress));
+ break;
+ case VoicemailCompat.TRANSCRIPTION_FAILED_NO_SPEECH_DETECTED:
+ Logger.get(context)
+ .logImpression(
+ DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE);
+ builder.setContentText(
+ context.getString(R.string.voicemail_transcription_failed_no_speech));
+ break;
+ case VoicemailCompat.TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED:
+ Logger.get(context)
+ .logImpression(
+ DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE);
+ builder.setContentText(
+ context.getString(R.string.voicemail_transcription_failed_language_not_supported));
+ break;
+ case VoicemailCompat.TRANSCRIPTION_FAILED:
+ Logger.get(context)
+ .logImpression(
+ DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE);
+ builder.setContentText(context.getString(R.string.voicemail_transcription_failed));
+ break;
+ default:
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_NO_TRANSCRIPTION);
+ break;
+ }
+ }
+
if (voicemail.voicemailUri != null) {
builder.setDeleteIntent(
CallLogNotificationsService.createMarkSingleNewVoicemailAsOldIntent(
context, voicemail.voicemailUri));
}
- if (BuildCompat.isAtLeastO()) {
+ if (VERSION.SDK_INT >= VERSION_CODES.O) {
builder.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle));
}
@@ -173,11 +207,6 @@
if (photoIcon != null) {
builder.setLargeIcon(photoIcon);
}
- if (!TextUtils.isEmpty(voicemail.transcription)) {
- Logger.get(context)
- .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION);
- builder.setStyle(new Notification.BigTextStyle().bigText(voicemail.transcription));
- }
builder.setContentIntent(newVoicemailIntent(context, voicemail));
Logger.get(context).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED);
return builder.build();
diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
index d6601be..219ad67 100644
--- a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
+++ b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
@@ -17,6 +17,8 @@
package com.android.dialer.app.calllog;
import android.content.Context;
+import android.net.Uri;
+import android.service.notification.StatusBarNotification;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
@@ -30,6 +32,7 @@
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.Worker;
import com.android.dialer.common.concurrent.DialerExecutors;
+import com.android.dialer.notification.DialerNotificationManager;
import com.android.dialer.phonenumbercache.ContactInfo;
import com.android.dialer.telecom.TelecomUtil;
import java.util.ArrayList;
@@ -57,13 +60,20 @@
CallLogNotificationsQueryHelper queryHelper,
FilteredNumberAsyncQueryHandler queryHandler) {
Assert.isWorkerThread();
+ LogUtil.enterBlock("VisualVoicemailUpdateTask.updateNotification");
- List<NewCall> newCalls = queryHelper.getNewVoicemails();
- if (newCalls == null) {
+ List<NewCall> voicemailsToNotify = queryHelper.getNewVoicemails();
+ if (voicemailsToNotify == null) {
+ // Query failed, just return
return;
}
- newCalls = filterBlockedNumbers(context, queryHandler, newCalls);
- if (newCalls.isEmpty()) {
+
+ voicemailsToNotify.addAll(getAndUpdateVoicemailsWithExistingNotification(context, queryHelper));
+ voicemailsToNotify = filterBlockedNumbers(context, queryHandler, voicemailsToNotify);
+ if (voicemailsToNotify.isEmpty()) {
+ LogUtil.i("VisualVoicemailUpdateTask.updateNotification", "no voicemails to notify about");
+ VisualVoicemailNotifier.cancelAllVoicemailNotifications(context);
+ VoicemailNotificationJobService.cancelJob(context);
return;
}
@@ -73,7 +83,7 @@
// Maps each number into a name: if a number is in the map, it has already left a more
// recent voicemail.
Map<String, ContactInfo> contactInfos = new ArrayMap<>();
- for (NewCall newCall : newCalls) {
+ for (NewCall newCall : voicemailsToNotify) {
if (!contactInfos.containsKey(newCall.number)) {
ContactInfo contactInfo =
queryHelper.getContactInfo(
@@ -90,7 +100,43 @@
}
}
}
- VisualVoicemailNotifier.showNotifications(context, newCalls, contactInfos, callers);
+ VisualVoicemailNotifier.showNotifications(context, voicemailsToNotify, contactInfos, callers);
+
+ // Set trigger to update notifications when database changes.
+ VoicemailNotificationJobService.scheduleJob(context);
+ }
+
+ /**
+ * Cancel notification for voicemail that is already deleted. Returns a list of voicemails that
+ * already has notifications posted and should be updated.
+ */
+ @WorkerThread
+ @NonNull
+ private static List<NewCall> getAndUpdateVoicemailsWithExistingNotification(
+ Context context, CallLogNotificationsQueryHelper queryHelper) {
+ Assert.isWorkerThread();
+ List<NewCall> result = new ArrayList<>();
+ for (StatusBarNotification notification :
+ DialerNotificationManager.getActiveNotifications(context)) {
+ if (notification.getId() != VisualVoicemailNotifier.NOTIFICATION_ID) {
+ continue;
+ }
+ if (TextUtils.equals(
+ notification.getTag(), VisualVoicemailNotifier.GROUP_SUMMARY_NOTIFICATION_TAG)) {
+ // Group header
+ continue;
+ }
+ NewCall existingCall = queryHelper.getNewCallsQuery().query(Uri.parse(notification.getTag()));
+ if (existingCall != null) {
+ result.add(existingCall);
+ } else {
+ LogUtil.i(
+ "VisualVoicemailUpdateTask.getVoicemailsWithExistingNotification",
+ "voicemail deleted, removing notification");
+ DialerNotificationManager.cancel(context, notification.getTag(), notification.getId());
+ }
+ }
+ return result;
}
@WorkerThread
diff --git a/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java b/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java
new file mode 100644
index 0000000..ba61601
--- /dev/null
+++ b/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java
@@ -0,0 +1,89 @@
+/*
+ * 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.dialer.app.calllog;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.provider.VoicemailContract;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.constants.ScheduledJobIds;
+
+/** Monitors voicemail provider changes to update active notifications. */
+public class VoicemailNotificationJobService extends JobService {
+
+ private static JobInfo jobInfo;
+
+ /**
+ * Start monitoring the provider. The provider should be monitored whenever a visual voicemail
+ * notification is visible.
+ */
+ public static void scheduleJob(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ LogUtil.i("VoicemailNotificationJobService.scheduleJob", "not supported");
+ } else {
+ context.getSystemService(JobScheduler.class).schedule(getJobInfo(context));
+ LogUtil.i("VoicemailNotificationJobService.scheduleJob", "job scheduled");
+ }
+ }
+
+ /**
+ * Stop monitoring the provider. The provider should not be monitored when visual voicemail
+ * notification is cleared.
+ */
+ public static void cancelJob(Context context) {
+ context.getSystemService(JobScheduler.class).cancel(ScheduledJobIds.VVM_NOTIFICATION_JOB);
+ LogUtil.i("VoicemailNotificationJobService.scheduleJob", "job canceled");
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ LogUtil.i("VoicemailNotificationJobService.onStartJob", "updating notification");
+ VisualVoicemailUpdateTask.scheduleTask(
+ this,
+ () -> {
+ jobFinished(params, false);
+ });
+ return true; // Running in background
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ private static JobInfo getJobInfo(Context context) {
+ if (jobInfo == null) {
+ jobInfo =
+ new JobInfo.Builder(
+ ScheduledJobIds.VVM_NOTIFICATION_JOB,
+ new ComponentName(context, VoicemailNotificationJobService.class))
+ .addTriggerContentUri(
+ new JobInfo.TriggerContentUri(
+ VoicemailContract.Voicemails.CONTENT_URI,
+ JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS))
+ .setTriggerContentMaxDelay(0)
+ .build();
+ }
+
+ return jobInfo;
+ }
+}
diff --git a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
index 2fbebdd..169d0fd 100644
--- a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
+++ b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
@@ -45,7 +45,8 @@
public static void markAllNewVoicemailsAsRead(final @NonNull Context context) {
ThreadUtil.postOnUiThread(
() -> {
- new VoicemailQueryHandler(context.getContentResolver()).markNewVoicemailsAsOld(null);
+ new VoicemailQueryHandler(context.getContentResolver())
+ .markNewVoicemailsAsOld(context, null);
});
}
@@ -59,12 +60,12 @@
ThreadUtil.postOnUiThread(
() -> {
new VoicemailQueryHandler(context.getContentResolver())
- .markNewVoicemailsAsOld(voicemailUri);
+ .markNewVoicemailsAsOld(context, voicemailUri);
});
}
/** Updates all new voicemails to mark them as old. */
- private void markNewVoicemailsAsOld(@Nullable Uri voicemailUri) {
+ private void markNewVoicemailsAsOld(Context context, @Nullable Uri voicemailUri) {
// Mark all "new" voicemails as not new anymore.
StringBuilder where = new StringBuilder();
where.append(Calls.NEW);
@@ -88,5 +89,8 @@
voicemailUri == null
? new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}
: new String[] {Integer.toString(Calls.VOICEMAIL_TYPE), voicemailUri.toString()});
+
+ // No more notifications, stop monitoring the voicemail provider
+ VoicemailNotificationJobService.cancelJob(context);
}
}
diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersAdapter.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersAdapter.java
new file mode 100644
index 0000000..4f8bc66
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersAdapter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.filterednumber;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.telephony.PhoneNumberUtils;
+import android.view.View;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.BlockNumberDialogFragment;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.logging.InteractionEvent;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.phonenumbercache.ContactInfoHelper;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedNumbersAdapter extends NumbersAdapter {
+
+ private BlockedNumbersAdapter(
+ Context context,
+ FragmentManager fragmentManager,
+ ContactInfoHelper contactInfoHelper,
+ ContactPhotoManager contactPhotoManager) {
+ super(context, fragmentManager, contactInfoHelper, contactPhotoManager);
+ }
+
+ public static BlockedNumbersAdapter newBlockedNumbersAdapter(
+ Context context, FragmentManager fragmentManager) {
+ return new BlockedNumbersAdapter(
+ context,
+ fragmentManager,
+ new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)),
+ ContactPhotoManager.getInstance(context));
+ }
+
+ @Override
+ public void bindView(View view, final Context context, Cursor cursor) {
+ super.bindView(view, context, cursor);
+ final Integer id = cursor.getInt(cursor.getColumnIndex(FilteredNumberColumns._ID));
+ final String countryIso =
+ cursor.getString(cursor.getColumnIndex(FilteredNumberColumns.COUNTRY_ISO));
+ final String number = cursor.getString(cursor.getColumnIndex(FilteredNumberColumns.NUMBER));
+
+ final View deleteButton = view.findViewById(R.id.delete_button);
+ deleteButton.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ BlockNumberDialogFragment.show(
+ id,
+ number,
+ countryIso,
+ PhoneNumberUtils.formatNumber(number, countryIso),
+ R.id.blocked_numbers_activity_container,
+ getFragmentManager(),
+ new BlockNumberDialogFragment.Callback() {
+ @Override
+ public void onFilterNumberSuccess() {}
+
+ @Override
+ public void onUnfilterNumberSuccess() {
+ Logger.get(context)
+ .logInteraction(InteractionEvent.Type.UNBLOCK_NUMBER_MANAGEMENT_SCREEN);
+ }
+
+ @Override
+ public void onChangeFilteredNumberUndo() {}
+ });
+ }
+ });
+
+ updateView(view, number, countryIso);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ // Always return false, so that the header with blocking-related options always shows.
+ return false;
+ }
+}
diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java
new file mode 100644
index 0000000..36afe54
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.filterednumber;
+
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.BlockedNumbersMigrator;
+import com.android.dialer.blocking.BlockedNumbersMigrator.Listener;
+import com.android.dialer.blocking.FilteredNumberCompat;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.blocking.FilteredNumbersUtil.CheckForSendToVoicemailContactListener;
+import com.android.dialer.blocking.FilteredNumbersUtil.ImportSendToVoicemailContactsListener;
+import com.android.dialer.database.FilteredNumberContract;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.voicemailstatus.VisualVoicemailEnabledChecker;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedNumbersFragment extends ListFragment
+ implements LoaderManager.LoaderCallbacks<Cursor>,
+ View.OnClickListener,
+ VisualVoicemailEnabledChecker.Callback {
+
+ private static final char ADD_BLOCKED_NUMBER_ICON_LETTER = '+';
+ protected View migratePromoView;
+ private BlockedNumbersMigrator blockedNumbersMigratorForTest;
+ private TextView blockedNumbersText;
+ private TextView footerText;
+ private BlockedNumbersAdapter mAdapter;
+ private VisualVoicemailEnabledChecker mVoicemailEnabledChecker;
+ private View mImportSettings;
+ private View mBlockedNumbersDisabledForEmergency;
+ private View mBlockedNumberListDivider;
+
+ @Override
+ public Context getContext() {
+ return getActivity();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ LayoutInflater inflater =
+ (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null));
+ getListView().addFooterView(inflater.inflate(R.layout.blocked_number_footer, null));
+ //replace the icon for add number with LetterTileDrawable(), so it will have identical style
+ ImageView addNumberIcon = (ImageView) getActivity().findViewById(R.id.add_number_icon);
+ LetterTileDrawable drawable = new LetterTileDrawable(getResources());
+ drawable.setLetter(ADD_BLOCKED_NUMBER_ICON_LETTER);
+ drawable.setColor(
+ ActivityCompat.getColor(getActivity(), R.color.add_blocked_number_icon_color));
+ drawable.setIsCircular(true);
+ addNumberIcon.setImageDrawable(drawable);
+
+ if (mAdapter == null) {
+ mAdapter =
+ BlockedNumbersAdapter.newBlockedNumbersAdapter(
+ getContext(), getActivity().getFragmentManager());
+ }
+ setListAdapter(mAdapter);
+
+ blockedNumbersText = (TextView) getListView().findViewById(R.id.blocked_number_text_view);
+ migratePromoView = getListView().findViewById(R.id.migrate_promo);
+ getListView().findViewById(R.id.migrate_promo_allow_button).setOnClickListener(this);
+ mImportSettings = getListView().findViewById(R.id.import_settings);
+ mBlockedNumbersDisabledForEmergency =
+ getListView().findViewById(R.id.blocked_numbers_disabled_for_emergency);
+ mBlockedNumberListDivider = getActivity().findViewById(R.id.blocked_number_list_divider);
+ getListView().findViewById(R.id.import_button).setOnClickListener(this);
+ getListView().findViewById(R.id.view_numbers_button).setOnClickListener(this);
+ getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(this);
+
+ footerText = (TextView) getActivity().findViewById(R.id.blocked_number_footer_textview);
+ mVoicemailEnabledChecker = new VisualVoicemailEnabledChecker(getContext(), this);
+ mVoicemailEnabledChecker.asyncUpdate();
+ updateActiveVoicemailProvider();
+ }
+
+ @Override
+ public void onDestroy() {
+ setListAdapter(null);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ ColorDrawable backgroundDrawable =
+ new ColorDrawable(ActivityCompat.getColor(getActivity(), R.color.dialer_theme_color));
+ actionBar.setBackgroundDrawable(backgroundDrawable);
+ actionBar.setDisplayShowCustomEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setTitle(R.string.manage_blocked_numbers_label);
+
+ // If the device can use the framework blocking solution, users should not be able to add
+ // new blocked numbers from the Blocked Management UI. They will be shown a promo card
+ // asking them to migrate to new blocking instead.
+ if (FilteredNumberCompat.canUseNewFiltering()) {
+ migratePromoView.setVisibility(View.VISIBLE);
+ blockedNumbersText.setVisibility(View.GONE);
+ getListView().findViewById(R.id.add_number_linear_layout).setVisibility(View.GONE);
+ getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(null);
+ mBlockedNumberListDivider.setVisibility(View.GONE);
+ mImportSettings.setVisibility(View.GONE);
+ getListView().findViewById(R.id.import_button).setOnClickListener(null);
+ getListView().findViewById(R.id.view_numbers_button).setOnClickListener(null);
+ mBlockedNumbersDisabledForEmergency.setVisibility(View.GONE);
+ footerText.setVisibility(View.GONE);
+ } else {
+ FilteredNumbersUtil.checkForSendToVoicemailContact(
+ getActivity(),
+ new CheckForSendToVoicemailContactListener() {
+ @Override
+ public void onComplete(boolean hasSendToVoicemailContact) {
+ final int visibility = hasSendToVoicemailContact ? View.VISIBLE : View.GONE;
+ mImportSettings.setVisibility(visibility);
+ }
+ });
+ }
+
+ // All views except migrate and the block list are hidden when new filtering is available
+ if (!FilteredNumberCompat.canUseNewFiltering()
+ && FilteredNumbersUtil.hasRecentEmergencyCall(getContext())) {
+ mBlockedNumbersDisabledForEmergency.setVisibility(View.VISIBLE);
+ } else {
+ mBlockedNumbersDisabledForEmergency.setVisibility(View.GONE);
+ }
+
+ mVoicemailEnabledChecker.asyncUpdate();
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.blocked_number_fragment, container, false);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ final String[] projection = {
+ FilteredNumberContract.FilteredNumberColumns._ID,
+ FilteredNumberContract.FilteredNumberColumns.COUNTRY_ISO,
+ FilteredNumberContract.FilteredNumberColumns.NUMBER,
+ FilteredNumberContract.FilteredNumberColumns.NORMALIZED_NUMBER
+ };
+ final String selection =
+ FilteredNumberContract.FilteredNumberColumns.TYPE
+ + "="
+ + FilteredNumberContract.FilteredNumberTypes.BLOCKED_NUMBER;
+ return new CursorLoader(
+ getContext(),
+ FilteredNumberContract.FilteredNumber.CONTENT_URI,
+ projection,
+ selection,
+ null,
+ null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ mAdapter.swapCursor(data);
+ if (FilteredNumberCompat.canUseNewFiltering() || data.getCount() == 0) {
+ mBlockedNumberListDivider.setVisibility(View.INVISIBLE);
+ } else {
+ mBlockedNumberListDivider.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ mAdapter.swapCursor(null);
+ }
+
+ @Override
+ public void onClick(final View view) {
+ final BlockedNumbersSettingsActivity activity = (BlockedNumbersSettingsActivity) getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ int resId = view.getId();
+ if (resId == R.id.add_number_linear_layout) {
+ activity.showSearchUi();
+ } else if (resId == R.id.view_numbers_button) {
+ activity.showNumbersToImportPreviewUi();
+ } else if (resId == R.id.import_button) {
+ FilteredNumbersUtil.importSendToVoicemailContacts(
+ activity,
+ new ImportSendToVoicemailContactsListener() {
+ @Override
+ public void onImportComplete() {
+ mImportSettings.setVisibility(View.GONE);
+ }
+ });
+ } else if (resId == R.id.migrate_promo_allow_button) {
+ view.setEnabled(false);
+ (blockedNumbersMigratorForTest != null
+ ? blockedNumbersMigratorForTest
+ : new BlockedNumbersMigrator(getContext()))
+ .migrate(
+ new Listener() {
+ @Override
+ public void onComplete() {
+ getContext()
+ .startActivity(
+ FilteredNumberCompat.createManageBlockedNumbersIntent(getContext()));
+ // Remove this activity from the backstack
+ activity.finish();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onVisualVoicemailEnabledStatusChanged(boolean newStatus) {
+ updateActiveVoicemailProvider();
+ }
+
+ private void updateActiveVoicemailProvider() {
+ if (getActivity() == null || getActivity().isFinishing()) {
+ return;
+ }
+ if (mVoicemailEnabledChecker.isVisualVoicemailEnabled()) {
+ footerText.setText(R.string.block_number_footer_message_vvm);
+ } else {
+ footerText.setText(R.string.block_number_footer_message_no_vvm);
+ }
+ }
+
+ void setBlockedNumbersMigratorForTest(BlockedNumbersMigrator blockedNumbersMigrator) {
+ blockedNumbersMigratorForTest = blockedNumbersMigrator;
+ }
+}
diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java
new file mode 100644
index 0000000..858d283
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.filterednumber;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import com.android.dialer.app.R;
+import com.android.dialer.app.list.BlockedListSearchFragment;
+import com.android.dialer.app.list.SearchFragment;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.ScreenEvent;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedNumbersSettingsActivity extends AppCompatActivity
+ implements SearchFragment.HostInterface {
+
+ private static final String TAG_BLOCKED_MANAGEMENT_FRAGMENT = "blocked_management";
+ private static final String TAG_BLOCKED_SEARCH_FRAGMENT = "blocked_search";
+ private static final String TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT = "view_numbers_to_import";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.blocked_numbers_activity);
+
+ // If savedInstanceState != null, the Activity will automatically restore the last fragment.
+ if (savedInstanceState == null) {
+ showManagementUi();
+ }
+ }
+
+ /** Shows fragment with the list of currently blocked numbers and settings related to blocking. */
+ public void showManagementUi() {
+ BlockedNumbersFragment fragment =
+ (BlockedNumbersFragment)
+ getFragmentManager().findFragmentByTag(TAG_BLOCKED_MANAGEMENT_FRAGMENT);
+ if (fragment == null) {
+ fragment = new BlockedNumbersFragment();
+ }
+
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.blocked_numbers_activity_container, fragment, TAG_BLOCKED_MANAGEMENT_FRAGMENT)
+ .commit();
+
+ Logger.get(this).logScreenView(ScreenEvent.Type.BLOCKED_NUMBER_MANAGEMENT, this);
+ }
+
+ /** Shows fragment with search UI for browsing/finding numbers to block. */
+ public void showSearchUi() {
+ BlockedListSearchFragment fragment =
+ (BlockedListSearchFragment)
+ getFragmentManager().findFragmentByTag(TAG_BLOCKED_SEARCH_FRAGMENT);
+ if (fragment == null) {
+ fragment = new BlockedListSearchFragment();
+ fragment.setHasOptionsMenu(false);
+ fragment.setShowEmptyListForNullQuery(true);
+ fragment.setDirectorySearchEnabled(false);
+ }
+
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.blocked_numbers_activity_container, fragment, TAG_BLOCKED_SEARCH_FRAGMENT)
+ .addToBackStack(null)
+ .commit();
+
+ Logger.get(this).logScreenView(ScreenEvent.Type.BLOCKED_NUMBER_ADD_NUMBER, this);
+ }
+
+ /**
+ * Shows fragment with UI to preview the numbers of contacts currently marked as send-to-voicemail
+ * in Contacts. These numbers can be imported into Dialer's blocked number list.
+ */
+ public void showNumbersToImportPreviewUi() {
+ ViewNumbersToImportFragment fragment =
+ (ViewNumbersToImportFragment)
+ getFragmentManager().findFragmentByTag(TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT);
+ if (fragment == null) {
+ fragment = new ViewNumbersToImportFragment();
+ }
+
+ getFragmentManager()
+ .beginTransaction()
+ .replace(
+ R.id.blocked_numbers_activity_container, fragment, TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT)
+ .addToBackStack(null)
+ .commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ // TODO: Achieve back navigation without overriding onBackPressed.
+ if (getFragmentManager().getBackStackEntryCount() > 0) {
+ getFragmentManager().popBackStack();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean isActionBarShowing() {
+ return false;
+ }
+
+ @Override
+ public boolean isDialpadShown() {
+ return false;
+ }
+
+ @Override
+ public int getDialpadHeight() {
+ return 0;
+ }
+
+ @Override
+ public int getActionBarHeight() {
+ return 0;
+ }
+}
diff --git a/java/com/android/dialer/app/filterednumber/NumbersAdapter.java b/java/com/android/dialer/app/filterednumber/NumbersAdapter.java
new file mode 100644
index 0000000..938a784
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/NumbersAdapter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.filterednumber;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.QuickContactBadge;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import com.android.dialer.app.R;
+import com.android.dialer.compat.CompatUtils;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.phonenumbercache.ContactInfoHelper;
+import com.android.dialer.phonenumberutil.PhoneNumberHelper;
+import com.android.dialer.util.UriUtils;
+
+/** TODO(calderwoodra): documentation */
+public class NumbersAdapter extends SimpleCursorAdapter {
+
+ private final Context mContext;
+ private final FragmentManager mFragmentManager;
+ private final ContactInfoHelper mContactInfoHelper;
+ private final BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
+ private final ContactPhotoManager mContactPhotoManager;
+
+ public NumbersAdapter(
+ Context context,
+ FragmentManager fragmentManager,
+ ContactInfoHelper contactInfoHelper,
+ ContactPhotoManager contactPhotoManager) {
+ super(context, R.layout.blocked_number_item, null, new String[] {}, new int[] {}, 0);
+ mContext = context;
+ mFragmentManager = fragmentManager;
+ mContactInfoHelper = contactInfoHelper;
+ mContactPhotoManager = contactPhotoManager;
+ }
+
+ public void updateView(View view, String number, String countryIso) {
+ final TextView callerName = (TextView) view.findViewById(R.id.caller_name);
+ final TextView callerNumber = (TextView) view.findViewById(R.id.caller_number);
+ final QuickContactBadge quickContactBadge =
+ (QuickContactBadge) view.findViewById(R.id.quick_contact_photo);
+ quickContactBadge.setOverlay(null);
+ if (CompatUtils.hasPrioritizedMimeType()) {
+ quickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
+ }
+
+ ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
+ if (info == null) {
+ info = new ContactInfo();
+ info.number = number;
+ }
+ final CharSequence locationOrType = getNumberTypeOrLocation(info);
+ final String displayNumber = getDisplayNumber(info);
+ final String displayNumberStr =
+ mBidiFormatter.unicodeWrap(displayNumber, TextDirectionHeuristics.LTR);
+
+ String nameForDefaultImage;
+ if (!TextUtils.isEmpty(info.name)) {
+ nameForDefaultImage = info.name;
+ callerName.setText(info.name);
+ callerNumber.setText(locationOrType + " " + displayNumberStr);
+ } else {
+ nameForDefaultImage = displayNumber;
+ callerName.setText(displayNumberStr);
+ if (!TextUtils.isEmpty(locationOrType)) {
+ callerNumber.setText(locationOrType);
+ callerNumber.setVisibility(View.VISIBLE);
+ } else {
+ callerNumber.setVisibility(View.GONE);
+ }
+ }
+ loadContactPhoto(info, nameForDefaultImage, quickContactBadge);
+ }
+
+ private void loadContactPhoto(ContactInfo info, String displayName, QuickContactBadge badge) {
+ final String lookupKey =
+ info.lookupUri == null ? null : UriUtils.getLookupKeyFromUri(info.lookupUri);
+ final int contactType =
+ mContactInfoHelper.isBusiness(info.sourceType)
+ ? LetterTileDrawable.TYPE_BUSINESS
+ : LetterTileDrawable.TYPE_DEFAULT;
+ final DefaultImageRequest request =
+ new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */);
+ badge.assignContactUri(info.lookupUri);
+ badge.setContentDescription(
+ mContext.getResources().getString(R.string.description_contact_details, displayName));
+ mContactPhotoManager.loadDirectoryPhoto(
+ badge, info.photoUri, false /* darkTheme */, true /* isCircular */, request);
+ }
+
+ private String getDisplayNumber(ContactInfo info) {
+ if (!TextUtils.isEmpty(info.formattedNumber)) {
+ return info.formattedNumber;
+ } else if (!TextUtils.isEmpty(info.number)) {
+ return info.number;
+ } else {
+ return "";
+ }
+ }
+
+ private CharSequence getNumberTypeOrLocation(ContactInfo info) {
+ if (!TextUtils.isEmpty(info.name)) {
+ return ContactsContract.CommonDataKinds.Phone.getTypeLabel(
+ mContext.getResources(), info.type, info.label);
+ } else {
+ return PhoneNumberHelper.getGeoDescription(mContext, info.number);
+ }
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ protected FragmentManager getFragmentManager() {
+ return mFragmentManager;
+ }
+}
diff --git a/java/com/android/dialer/app/filterednumber/ViewNumbersToImportAdapter.java b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportAdapter.java
new file mode 100644
index 0000000..106c4fb
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportAdapter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.filterednumber;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.phonenumbercache.ContactInfoHelper;
+
+/** TODO(calderwoodra): documentation */
+public class ViewNumbersToImportAdapter extends NumbersAdapter {
+
+ private ViewNumbersToImportAdapter(
+ Context context,
+ FragmentManager fragmentManager,
+ ContactInfoHelper contactInfoHelper,
+ ContactPhotoManager contactPhotoManager) {
+ super(context, fragmentManager, contactInfoHelper, contactPhotoManager);
+ }
+
+ public static ViewNumbersToImportAdapter newViewNumbersToImportAdapter(
+ Context context, FragmentManager fragmentManager) {
+ return new ViewNumbersToImportAdapter(
+ context,
+ fragmentManager,
+ new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)),
+ ContactPhotoManager.getInstance(context));
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ super.bindView(view, context, cursor);
+
+ final String number = cursor.getString(FilteredNumbersUtil.PhoneQuery.NUMBER_COLUMN_INDEX);
+
+ view.findViewById(R.id.delete_button).setVisibility(View.GONE);
+ updateView(view, number, null /* countryIso */);
+ }
+}
diff --git a/java/com/android/dialer/app/filterednumber/ViewNumbersToImportFragment.java b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportFragment.java
new file mode 100644
index 0000000..1de7682
--- /dev/null
+++ b/java/com/android/dialer/app/filterednumber/ViewNumbersToImportFragment.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.filterednumber;
+
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.dialer.app.R;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.blocking.FilteredNumbersUtil.ImportSendToVoicemailContactsListener;
+
+/** TODO(calderwoodra): documentation */
+public class ViewNumbersToImportFragment extends ListFragment
+ implements LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener {
+
+ private ViewNumbersToImportAdapter mAdapter;
+
+ @Override
+ public Context getContext() {
+ return getActivity();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ if (mAdapter == null) {
+ mAdapter =
+ ViewNumbersToImportAdapter.newViewNumbersToImportAdapter(
+ getContext(), getActivity().getFragmentManager());
+ }
+ setListAdapter(mAdapter);
+ }
+
+ @Override
+ public void onDestroy() {
+ setListAdapter(null);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ actionBar.setTitle(R.string.import_send_to_voicemail_numbers_label);
+ actionBar.setDisplayShowCustomEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setDisplayShowHomeEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+
+ getActivity().findViewById(R.id.cancel_button).setOnClickListener(this);
+ getActivity().findViewById(R.id.import_button).setOnClickListener(this);
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.view_numbers_to_import_fragment, container, false);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ final CursorLoader cursorLoader =
+ new CursorLoader(
+ getContext(),
+ Phone.CONTENT_URI,
+ FilteredNumbersUtil.PhoneQuery.PROJECTION,
+ FilteredNumbersUtil.PhoneQuery.SELECT_SEND_TO_VOICEMAIL_TRUE,
+ null,
+ null);
+ return cursorLoader;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ mAdapter.swapCursor(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ mAdapter.swapCursor(null);
+ }
+
+ @Override
+ public void onClick(final View view) {
+ if (view.getId() == R.id.import_button) {
+ FilteredNumbersUtil.importSendToVoicemailContacts(
+ getContext(),
+ new ImportSendToVoicemailContactsListener() {
+ @Override
+ public void onImportComplete() {
+ if (getActivity() != null) {
+ getActivity().onBackPressed();
+ }
+ }
+ });
+ } else if (view.getId() == R.id.cancel_button) {
+ getActivity().onBackPressed();
+ }
+ }
+}
diff --git a/java/com/android/dialer/app/list/BlockedListSearchFragment.java b/java/com/android/dialer/app/list/BlockedListSearchFragment.java
new file mode 100644
index 0000000..bef5af7
--- /dev/null
+++ b/java/com/android/dialer/app/list/BlockedListSearchFragment.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2015 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.dialer.app.list;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.telephony.PhoneNumberUtils;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.Toast;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.dialer.app.R;
+import com.android.dialer.app.widget.SearchEditTextLayout;
+import com.android.dialer.blocking.BlockNumberDialogFragment;
+import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.location.GeoUtil;
+import com.android.dialer.logging.InteractionEvent;
+import com.android.dialer.logging.Logger;
+
+/** TODO(calderwoodra): documentation */
+public class BlockedListSearchFragment extends RegularSearchFragment
+ implements BlockNumberDialogFragment.Callback {
+
+ private final TextWatcher mPhoneSearchQueryTextListener =
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ setQueryString(s.toString());
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+ };
+ private final SearchEditTextLayout.Callback mSearchLayoutCallback =
+ new SearchEditTextLayout.Callback() {
+ @Override
+ public void onBackButtonClicked() {
+ getActivity().onBackPressed();
+ }
+
+ @Override
+ public void onSearchViewClicked() {}
+ };
+ private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
+ private EditText mSearchView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setShowEmptyListForNullQuery(true);
+ /*
+ * Pass in the empty string here so ContactEntryListFragment#setQueryString interprets it as
+ * an empty search query, rather than as an uninitalized value. In the latter case, the
+ * adapter returned by #createListAdapter is used, which populates the view with contacts.
+ * Passing in the empty string forces ContactEntryListFragment to interpret it as an empty
+ * query, which results in showing an empty view
+ */
+ setQueryString(getQueryString() == null ? "" : getQueryString());
+ mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(getContext());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ actionBar.setCustomView(R.layout.search_edittext);
+ actionBar.setDisplayShowCustomEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setDisplayShowHomeEnabled(false);
+
+ final SearchEditTextLayout searchEditTextLayout =
+ (SearchEditTextLayout) actionBar.getCustomView().findViewById(R.id.search_view_container);
+ searchEditTextLayout.expand(false, true);
+ searchEditTextLayout.setCallback(mSearchLayoutCallback);
+ searchEditTextLayout.setBackgroundDrawable(null);
+
+ mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
+ mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
+ mSearchView.setHint(R.string.block_number_search_hint);
+
+ searchEditTextLayout
+ .findViewById(R.id.search_box_expanded)
+ .setBackgroundColor(getContext().getResources().getColor(android.R.color.white));
+
+ if (!TextUtils.isEmpty(getQueryString())) {
+ mSearchView.setText(getQueryString());
+ }
+
+ // TODO: Don't set custom text size; use default search text size.
+ mSearchView.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimension(R.dimen.blocked_number_search_text_size));
+ }
+
+ @Override
+ protected ContactEntryListAdapter createListAdapter() {
+ BlockedListSearchAdapter adapter = new BlockedListSearchAdapter(getActivity());
+ adapter.setDisplayPhotos(true);
+ // Don't show SIP addresses.
+ adapter.setUseCallableUri(false);
+ // Keep in sync with the queryString set in #onCreate
+ adapter.setQueryString(getQueryString() == null ? "" : getQueryString());
+ return adapter;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ super.onItemClick(parent, view, position, id);
+ final int adapterPosition = position - getListView().getHeaderViewsCount();
+ final BlockedListSearchAdapter adapter = (BlockedListSearchAdapter) getAdapter();
+ final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition);
+ final Integer blockId = (Integer) view.getTag(R.id.block_id);
+ final String number;
+ switch (shortcutType) {
+ case DialerPhoneNumberListAdapter.SHORTCUT_INVALID:
+ // Handles click on a search result, either contact or nearby places result.
+ number = adapter.getPhoneNumber(adapterPosition);
+ blockContactNumber(number, blockId);
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_BLOCK_NUMBER:
+ // Handles click on 'Block number' shortcut to add the user query as a number.
+ number = adapter.getQueryString();
+ blockNumber(number);
+ break;
+ default:
+ LogUtil.w(
+ "BlockedListSearchFragment.onItemClick",
+ "ignoring unsupported shortcut type: " + shortcutType);
+ break;
+ }
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ // Prevent SearchFragment.onItemClicked from being called.
+ }
+
+ private void blockNumber(final String number) {
+ final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
+ final OnCheckBlockedListener onCheckListener =
+ new OnCheckBlockedListener() {
+ @Override
+ public void onCheckComplete(Integer id) {
+ if (id == null) {
+ BlockNumberDialogFragment.show(
+ id,
+ number,
+ countryIso,
+ PhoneNumberUtils.formatNumber(number, countryIso),
+ R.id.blocked_numbers_activity_container,
+ getFragmentManager(),
+ BlockedListSearchFragment.this);
+ } else if (id == FilteredNumberAsyncQueryHandler.INVALID_ID) {
+ Toast.makeText(
+ getContext(),
+ ContactDisplayUtils.getTtsSpannedPhoneNumber(
+ getResources(), R.string.invalidNumber, number),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(
+ getContext(),
+ ContactDisplayUtils.getTtsSpannedPhoneNumber(
+ getResources(), R.string.alreadyBlocked, number),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ };
+ mFilteredNumberAsyncQueryHandler.isBlockedNumber(onCheckListener, number, countryIso);
+ }
+
+ @Override
+ public void onFilterNumberSuccess() {
+ Logger.get(getContext()).logInteraction(InteractionEvent.Type.BLOCK_NUMBER_MANAGEMENT_SCREEN);
+ goBack();
+ }
+
+ @Override
+ public void onUnfilterNumberSuccess() {
+ LogUtil.e(
+ "BlockedListSearchFragment.onUnfilterNumberSuccess",
+ "unblocked a number from the BlockedListSearchFragment");
+ goBack();
+ }
+
+ private void goBack() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ activity.onBackPressed();
+ }
+
+ @Override
+ public void onChangeFilteredNumberUndo() {
+ getAdapter().notifyDataSetChanged();
+ }
+
+ private void blockContactNumber(final String number, final Integer blockId) {
+ if (blockId != null) {
+ Toast.makeText(
+ getContext(),
+ ContactDisplayUtils.getTtsSpannedPhoneNumber(
+ getResources(), R.string.alreadyBlocked, number),
+ Toast.LENGTH_SHORT)
+ .show();
+ return;
+ }
+
+ BlockNumberDialogFragment.show(
+ blockId,
+ number,
+ GeoUtil.getCurrentCountryIso(getContext()),
+ number,
+ R.id.blocked_numbers_activity_container,
+ getFragmentManager(),
+ this);
+ }
+}
diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java
index dc1bd94..8dbe18c 100644
--- a/java/com/android/dialer/app/list/ListsFragment.java
+++ b/java/com/android/dialer/app/list/ListsFragment.java
@@ -16,6 +16,8 @@
package com.android.dialer.app.list;
+import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
+
import android.app.Fragment;
import android.content.SharedPreferences;
import android.database.ContentObserver;
@@ -77,6 +79,13 @@
private CallLogQueryHandler mCallLogQueryHandler;
private UiAction.Type[] actionTypeList;
+ private final DialerImpression.Type[] swipeImpressionList =
+ new DialerImpression.Type[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
+ private final DialerImpression.Type[] clickImpressionList =
+ new DialerImpression.Type[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
+
+ // Only for detecting page selected by swiping or clicking.
+ private boolean onPageScrolledBeforeScrollStateSettling;
private final ContentObserver mVoicemailStatusObserver =
new ContentObserver(new Handler()) {
@@ -156,6 +165,24 @@
actionTypeList[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
UiAction.Type.CHANGE_TAB_TO_VOICEMAIL;
+ swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
+ DialerImpression.Type.SWITCH_TAB_TO_FAVORITE_BY_SWIPE;
+ swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_HISTORY] =
+ DialerImpression.Type.SWITCH_TAB_TO_CALL_LOG_BY_SWIPE;
+ swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] =
+ DialerImpression.Type.SWITCH_TAB_TO_CONTACTS_BY_SWIPE;
+ swipeImpressionList[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
+ DialerImpression.Type.SWITCH_TAB_TO_VOICEMAIL_BY_SWIPE;
+
+ clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
+ DialerImpression.Type.SWITCH_TAB_TO_FAVORITE_BY_CLICK;
+ clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_HISTORY] =
+ DialerImpression.Type.SWITCH_TAB_TO_CALL_LOG_BY_CLICK;
+ clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS] =
+ DialerImpression.Type.SWITCH_TAB_TO_CONTACTS_BY_CLICK;
+ clickImpressionList[DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL] =
+ DialerImpression.Type.SWITCH_TAB_TO_VOICEMAIL_BY_CLICK;
+
String[] tabTitles = new String[DialtactsPagerAdapter.TAB_COUNT_WITH_VOICEMAIL];
tabTitles[DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL] =
getResources().getString(R.string.tab_speed_dial);
@@ -240,6 +267,11 @@
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // onPageScrolled(0, 0, 0) is called when app launch. And we should ignore it.
+ // It's also called when swipe right from first tab, but we don't care.
+ if (positionOffsetPixels != 0) {
+ onPageScrolledBeforeScrollStateSettling = true;
+ }
mTabIndex = mAdapter.getRtlPosition(position);
final int count = mOnPageChangeListeners.size();
@@ -250,6 +282,16 @@
@Override
public void onPageSelected(int position) {
+ // onPageScrollStateChanged(SCROLL_STATE_SETTLING) must be called before this.
+ // If onPageScrolled() is called before that, the page is selected by swiping;
+ // otherwise the page is selected by clicking.
+ if (onPageScrolledBeforeScrollStateSettling) {
+ Logger.get(getContext()).logImpression(swipeImpressionList[position]);
+ onPageScrolledBeforeScrollStateSettling = false;
+ } else {
+ Logger.get(getContext()).logImpression(clickImpressionList[position]);
+ }
+
PerformanceReport.recordClick(actionTypeList[position]);
LogUtil.i("ListsFragment.onPageSelected", "position: %d", position);
@@ -275,6 +317,10 @@
@Override
public void onPageScrollStateChanged(int state) {
+ if (state != SCROLL_STATE_SETTLING) {
+ onPageScrolledBeforeScrollStateSettling = false;
+ }
+
final int count = mOnPageChangeListeners.size();
for (int i = 0; i < count; i++) {
mOnPageChangeListeners.get(i).onPageScrollStateChanged(state);
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 e967924..2c24819 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
@@ -157,15 +157,6 @@
android:orientation="vertical">
<TextView
- android:id="@+id/voicemail_transcription_branding"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/call_log_voicemail_transcript_branding_color"
- android:textSize="@dimen/call_log_voicemail_transcription_text_size"
- android:paddingBottom="2dp"
- android:singleLine="true"/>
-
- <TextView
android:id="@+id/voicemail_transcription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -175,6 +166,15 @@
android:singleLine="false"
android:maxLines="10"/>
+ <TextView
+ android:id="@+id/voicemail_transcription_branding"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/call_log_voicemail_transcript_branding_color"
+ android:textSize="@dimen/call_log_voicemail_transcription_text_size"
+ android:paddingTop="2dp"
+ android:singleLine="true"/>
+
</LinearLayout>
</LinearLayout>
diff --git a/java/com/android/dialer/app/res/values/strings.xml b/java/com/android/dialer/app/res/values/strings.xml
index 50e7174..841eb9c 100644
--- a/java/com/android/dialer/app/res/values/strings.xml
+++ b/java/com/android/dialer/app/res/values/strings.xml
@@ -648,14 +648,22 @@
<!-- Label for setting that shows more information about the Phone app [CHAR LIMIT=30] -->
<string name="about_phone_label">About</string>
- <!-- Label indicating who provided the voicemail transcription [CHAR LIMIT=40] -->
+ <!-- Label indicating who provided the voicemail transcription [CHAR LIMIT=64] -->
<string name="voicemail_transcription_branding_text">Transcribed by Google</string>
- <!-- Label indicating that a voicemail transcription is in progress [CHAR LIMIT=40] -->
- <string name="voicemail_transcription_in_progress">Google is transcribing …</string>
+ <!-- Label indicating that a voicemail transcription is in progress [CHAR LIMIT=64] -->
+ <string name="voicemail_transcription_in_progress">Google is transcribing…</string>
- <!-- Label indicating that a voicemail transcription failed [CHAR LIMIT=40] -->
- <string name="voicemail_transcription_failed">Transcript not available</string>
+ <!-- Label indicating that a voicemail transcription failed [CHAR LIMIT=64] -->
+ <string name="voicemail_transcription_failed">Transcript not available.</string>
+
+ <!-- Label indicating that a voicemail transcription failed because it was in an
+ unsupported language [CHAR LIMIT=64] -->
+ <string name="voicemail_transcription_failed_language_not_supported">Transcript not available. Language not supported.</string>
+
+ <!-- Label indicating that a voicemail transcription failed because no speech was detected
+ [CHAR LIMIT=64] -->
+ <string name="voicemail_transcription_failed_no_speech">Transcript not available. No speech detected.</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/voicemail/error/VoicemailTosMessageCreator.java b/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
index baf7b99..53f4680 100644
--- a/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
+++ b/java/com/android/dialer/app/voicemail/error/VoicemailTosMessageCreator.java
@@ -143,7 +143,7 @@
if (isVvm3() && Vvm3VoicemailMessageCreator.PIN_NOT_SET == status.configurationState) {
LogUtil.i(
"VoicemailTosMessageCreator.showDeclineTosDialog", "PIN_NOT_SET, showing set PIN dialog");
- showSetPinBeforeDeclineDialog();
+ showSetPinBeforeDeclineDialog(handle);
return;
}
LogUtil.i(
@@ -180,7 +180,7 @@
builder.show();
}
- private void showSetPinBeforeDeclineDialog() {
+ private void showSetPinBeforeDeclineDialog(PhoneAccountHandle phoneAccountHandle) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.verizon_terms_and_conditions_decline_set_pin_dialog_message);
builder.setPositiveButton(
@@ -191,6 +191,7 @@
Logger.get(context)
.logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINE_CHANGE_PIN_SHOWN);
Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
+ intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
context.startActivity(intent);
}
});
diff --git a/java/com/android/dialer/app/widget/SearchEditTextLayout.java b/java/com/android/dialer/app/widget/SearchEditTextLayout.java
index 2051b65..95bd12a 100644
--- a/java/com/android/dialer/app/widget/SearchEditTextLayout.java
+++ b/java/com/android/dialer/app/widget/SearchEditTextLayout.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
+import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.FrameLayout;
@@ -37,6 +38,7 @@
/* Subclass-visible for testing */
protected boolean mIsExpanded = false;
protected boolean mIsFadedOut = false;
+ private OnKeyListener mPreImeKeyListener;
private int mTopMargin;
private int mBottomMargin;
private int mLeftMargin;
@@ -54,10 +56,20 @@
private ValueAnimator mAnimator;
+ private Callback mCallback;
+
public SearchEditTextLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
+ public void setPreImeKeyListener(OnKeyListener listener) {
+ mPreImeKeyListener = listener;
+ }
+
+ public void setCallback(Callback listener) {
+ mCallback = listener;
+ }
+
@Override
protected void onFinishInflate() {
MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
@@ -70,7 +82,7 @@
mCollapsed = findViewById(R.id.search_box_collapsed);
mExpanded = findViewById(R.id.search_box_expanded);
- mSearchView = mExpanded.findViewById(R.id.search_view);
+ mSearchView = (EditText) mExpanded.findViewById(R.id.search_view);
mSearchIcon = findViewById(R.id.search_magnifying_glass);
mCollapsedSearchBox = findViewById(R.id.search_box_start_search);
@@ -111,6 +123,16 @@
}
});
+ mSearchView.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onSearchViewClicked();
+ }
+ }
+ });
+
mSearchView.addTextChangedListener(
new TextWatcher() {
@Override
@@ -125,10 +147,43 @@
public void afterTextChanged(Editable s) {}
});
- mClearButtonView.setOnClickListener(v -> mSearchView.setText(null));
+ findViewById(R.id.search_close_button)
+ .setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSearchView.setText(null);
+ }
+ });
+
+ findViewById(R.id.search_back_button)
+ .setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onBackButtonClicked();
+ }
+ }
+ });
+
super.onFinishInflate();
}
+ @Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ if (mPreImeKeyListener != null) {
+ if (mPreImeKeyListener.onKey(this, event.getKeyCode(), event)) {
+ return true;
+ }
+ }
+ return super.dispatchKeyEventPreIme(event);
+ }
+
+ public void fadeOut() {
+ fadeOut(null);
+ }
+
public void fadeOut(AnimUtils.AnimationCallback callback) {
AnimUtils.fadeOut(this, ANIMATION_DURATION, callback);
mIsFadedOut = true;
@@ -269,4 +324,12 @@
params.rightMargin = (int) (mRightMargin * fraction);
requestLayout();
}
+
+ /** Listener for the back button next to the search view being pressed */
+ public interface Callback {
+
+ void onBackButtonClicked();
+
+ void onSearchViewClicked();
+ }
}
diff --git a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java
index 22ec70c..db1dd4a 100644
--- a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java
+++ b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java
@@ -30,6 +30,7 @@
import com.android.dialer.telecom.TelecomUtil;
import java.lang.reflect.InvocationTargetException;
+/** Hidden APIs in {@link android.telephony.TelephonyManager}. */
public class TelephonyManagerCompat {
// TODO(maxwelb): Use public API for these constants when available
diff --git a/java/com/android/dialer/constants/ScheduledJobIds.java b/java/com/android/dialer/constants/ScheduledJobIds.java
index cf93a46..3fcbb0c 100644
--- a/java/com/android/dialer/constants/ScheduledJobIds.java
+++ b/java/com/android/dialer/constants/ScheduledJobIds.java
@@ -34,6 +34,7 @@
public static final int VVM_DEVICE_PROVISIONED_JOB = 202;
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 VOIP_REGISTRATION = 300;
diff --git a/java/com/android/dialer/contactsfragment/ContactsAdapter.java b/java/com/android/dialer/contactsfragment/ContactsAdapter.java
index 1389531..481574e 100644
--- a/java/com/android/dialer/contactsfragment/ContactsAdapter.java
+++ b/java/com/android/dialer/contactsfragment/ContactsAdapter.java
@@ -27,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
import com.android.dialer.contactphoto.ContactPhotoManager;
import com.android.dialer.contactsfragment.ContactsFragment.ClickAction;
import com.android.dialer.contactsfragment.ContactsFragment.Header;
@@ -66,6 +67,17 @@
this.clickAction = clickAction;
headers = cursor.getExtras().getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
counts = cursor.getExtras().getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ if (counts != null) {
+ int sum = 0;
+ for (int count : counts) {
+ sum += count;
+ }
+
+ if (sum != cursor.getCount()) {
+ LogUtil.e(
+ "ContactsAdapter", "Count sum (%d) != cursor count (%d).", sum, cursor.getCount());
+ }
+ }
}
@Override
diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java
index ddf00b3..7d20976 100644
--- a/java/com/android/dialer/contactsfragment/ContactsFragment.java
+++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java
@@ -179,7 +179,7 @@
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor.getCount() == 0) {
+ if (cursor == null || cursor.getCount() == 0) {
emptyContentView.setDescription(R.string.all_contacts_empty);
emptyContentView.setActionLabel(R.string.all_contacts_empty_add_contact_action);
emptyContentView.setVisibility(View.VISIBLE);
diff --git a/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java b/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java
index 0e5f79f..a7d656a 100644
--- a/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java
+++ b/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java
@@ -16,7 +16,9 @@
package com.android.dialer.enrichedcall.videoshare;
+import android.content.Context;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
/** Receives updates when video share status has changed. */
public interface VideoShareListener {
@@ -26,5 +28,5 @@
* invite received or canceled, or when a session changes).
*/
@MainThread
- void onVideoShareChanged();
+ void onVideoShareChanged(@NonNull Context context);
}
diff --git a/java/com/android/dialer/lightbringer/Lightbringer.java b/java/com/android/dialer/lightbringer/Lightbringer.java
index 9120b24..fa57b01 100644
--- a/java/com/android/dialer/lightbringer/Lightbringer.java
+++ b/java/com/android/dialer/lightbringer/Lightbringer.java
@@ -40,7 +40,7 @@
Intent getIntent(@NonNull Context context, @NonNull String number);
@MainThread
- void requestUpgrade(Call call);
+ void requestUpgrade(@NonNull Context context, Call call);
@MainThread
void registerListener(@NonNull LightbringerListener listener);
diff --git a/java/com/android/dialer/lightbringer/stub/LightbringerStub.java b/java/com/android/dialer/lightbringer/stub/LightbringerStub.java
index c98ae09..a030922 100644
--- a/java/com/android/dialer/lightbringer/stub/LightbringerStub.java
+++ b/java/com/android/dialer/lightbringer/stub/LightbringerStub.java
@@ -67,7 +67,7 @@
@MainThread
@Override
- public void requestUpgrade(Call call) {
+ public void requestUpgrade(@NonNull Context context, Call call) {
Assert.isMainThread();
Assert.isNotNull(call);
}
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index ef249c2..f273a36 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -530,5 +530,22 @@
IN_CALL_DIALPAD_NUMBER_BUTTON_PRESSED = 1265;
IN_CALL_DIALPAD_HANG_UP_BUTTON_PRESSED = 1266;
IN_CALL_DIALPAD_CLOSE_BUTTON_PRESSED = 1267;
+
+ // More voicemail transcription impressions
+ VVM_NOTIFICATION_CREATED_WITH_IN_PROGRESS = 1268;
+ VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE = 1269;
+ VVM_NOTIFICATION_CREATED_WITH_NO_TRANSCRIPTION = 1270;
+ VVM_TRANSCRIPTION_JOB_STOPPED = 1271;
+ VVM_TRANSCRIPTION_TASK_CANCELLED = 1272;
+
+ // Swipe/click to switch tabs
+ SWITCH_TAB_TO_FAVORITE_BY_SWIPE = 1273;
+ SWITCH_TAB_TO_CALL_LOG_BY_SWIPE = 1274;
+ SWITCH_TAB_TO_CONTACTS_BY_SWIPE = 1275;
+ SWITCH_TAB_TO_VOICEMAIL_BY_SWIPE = 1276;
+ SWITCH_TAB_TO_FAVORITE_BY_CLICK = 1277;
+ SWITCH_TAB_TO_CALL_LOG_BY_CLICK = 1278;
+ SWITCH_TAB_TO_CONTACTS_BY_CLICK = 1279;
+ SWITCH_TAB_TO_VOICEMAIL_BY_CLICK = 1280;
}
}
diff --git a/java/com/android/dialer/proguard/proguard_release.flags b/java/com/android/dialer/proguard/proguard_release.flags
index c6bdd49..1429740 100644
--- a/java/com/android/dialer/proguard/proguard_release.flags
+++ b/java/com/android/dialer/proguard/proguard_release.flags
@@ -22,3 +22,9 @@
static *** v(...);
static *** isLoggable(...);
}
+
+# This allows proguard to strip Trace code from release builds.
+-assumenosideeffects class android.os.Trace {
+ static *** beginSection(...);
+ static *** endSection(...);
+}
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 0623d39..2527b87 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -210,7 +210,7 @@
}
boolean slideUp = start > end;
Interpolator interpolator = slideUp ? AnimUtils.EASE_IN : AnimUtils.EASE_OUT;
- int startHeight = getView().getHeight();
+ int startHeight = getActivity().findViewById(android.R.id.content).getHeight();
int endHeight = startHeight - (end - start);
getView().setTranslationY(start);
getView()
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
index e6f3c26..5d80a45 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
@@ -118,12 +118,19 @@
int position = getPosition();
// proceed backwards until we reach the header row, which contains the directory ID.
while (moveToPrevious()) {
- int id = getInt(getColumnIndex(COLUMN_DIRECTORY_ID));
- if (id != -1) {
- // return the cursor to it's original position/state
- moveToPosition(position);
- return id;
+ int columnIndex = getColumnIndex(COLUMN_DIRECTORY_ID);
+ if (columnIndex == -1) {
+ continue;
}
+
+ int id = getInt(columnIndex);
+ if (id == -1) {
+ continue;
+ }
+
+ // return the cursor to it's original position/state
+ moveToPosition(position);
+ return id;
}
throw Assert.createIllegalStateFailException("No directory id for contact at: " + position);
}
diff --git a/java/com/android/dialer/simulator/Simulator.java b/java/com/android/dialer/simulator/Simulator.java
index f416415..f753e5f 100644
--- a/java/com/android/dialer/simulator/Simulator.java
+++ b/java/com/android/dialer/simulator/Simulator.java
@@ -22,6 +22,7 @@
import android.view.ActionProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/** Used to add menu items to the Dialer menu to test the app using simulated calls and data. */
public interface Simulator {
@@ -42,6 +43,7 @@
DISCONNECT,
STATE_CHANGE,
DTMF,
+ SESSION_MODIFY_REQUEST,
})
public @interface Type {}
@@ -53,6 +55,7 @@
public static final int DISCONNECT = 5;
public static final int STATE_CHANGE = 6;
public static final int DTMF = 7;
+ public static final int SESSION_MODIFY_REQUEST = 8;
@Type public final int type;
/** Holds event specific information. For example, for DTMF this could be the keycode. */
@@ -71,5 +74,24 @@
this.data1 = data1;
this.data2 = data2;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Event)) {
+ return false;
+ }
+ Event event = (Event) other;
+ return type == event.type
+ && Objects.equals(data1, event.data1)
+ && Objects.equals(data2, event.data2);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Integer.valueOf(type), data1, data2);
+ }
}
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java b/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java
deleted file mode 100644
index f095a59..0000000
--- a/java/com/android/dialer/simulator/impl/SimulatorActionProvider.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.dialer.simulator.impl;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.provider.VoicemailContract;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.view.ActionProvider;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-import com.android.dialer.common.Assert;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutor.Worker;
-import com.android.dialer.common.concurrent.DialerExecutors;
-import com.android.dialer.databasepopulator.CallLogPopulator;
-import com.android.dialer.databasepopulator.ContactsPopulator;
-import com.android.dialer.databasepopulator.VoicemailPopulator;
-import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity;
-import com.android.dialer.persistentlog.PersistentLogger;
-
-/** Implements the simulator submenu. */
-final class SimulatorActionProvider extends ActionProvider {
- @NonNull private final Context context;
-
- private static class ShareLogWorker implements Worker<Void, String> {
-
- @Nullable
- @Override
- public String doInBackground(Void unused) {
- return PersistentLogger.dumpLogToString();
- }
- }
-
- public SimulatorActionProvider(@NonNull Context context) {
- super(Assert.isNotNull(context));
- this.context = context;
- }
-
- @Override
- public View onCreateActionView() {
- LogUtil.enterBlock("SimulatorActionProvider.onCreateActionView(null)");
- return null;
- }
-
- @Override
- public View onCreateActionView(MenuItem forItem) {
- LogUtil.enterBlock("SimulatorActionProvider.onCreateActionView(MenuItem)");
- return null;
- }
-
- @Override
- public boolean hasSubMenu() {
- LogUtil.enterBlock("SimulatorActionProvider.hasSubMenu");
- return true;
- }
-
- @Override
- public void onPrepareSubMenu(SubMenu subMenu) {
- super.onPrepareSubMenu(subMenu);
- LogUtil.enterBlock("SimulatorActionProvider.onPrepareSubMenu");
- subMenu.clear();
-
- subMenu
- .add("Add call")
- .setOnMenuItemClickListener(
- (item) -> {
- SimulatorVoiceCall.addNewIncomingCall(context);
- return true;
- });
-
- subMenu
- .add("Notifiations")
- .setActionProvider(SimulatorNotifications.getActionProvider(context));
- subMenu
- .add("Populate database")
- .setOnMenuItemClickListener(
- (item) -> {
- populateDatabase();
- return true;
- });
- subMenu
- .add("Clean database")
- .setOnMenuItemClickListener(
- (item) -> {
- cleanDatabase();
- return true;
- });
- subMenu
- .add("Sync Voicemail")
- .setOnMenuItemClickListener(
- (item) -> {
- Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
- context.sendBroadcast(intent);
- return true;
- });
-
- subMenu
- .add("Share persistent log")
- .setOnMenuItemClickListener(
- (item) -> {
- DialerExecutors.createNonUiTaskBuilder(new ShareLogWorker())
- .onSuccess(
- (String log) -> {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_TEXT, log);
- if (intent.resolveActivity(context.getPackageManager()) != null) {
- context.startActivity(intent);
- }
- })
- .build()
- .executeSerial(null);
- return true;
- });
- subMenu
- .add("Enriched call simulator")
- .setOnMenuItemClickListener(
- (item) -> {
- context.startActivity(EnrichedCallSimulatorActivity.newIntent(context));
- return true;
- });
- }
-
- private void populateDatabase() {
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- ContactsPopulator.populateContacts(context);
- CallLogPopulator.populateCallLog(context);
- VoicemailPopulator.populateVoicemail(context);
- return null;
- }
- }.execute();
- }
-
- private void cleanDatabase() {
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- ContactsPopulator.deleteAllContacts(context);
- CallLogPopulator.deleteAllCallLog(context);
- VoicemailPopulator.deleteAllVoicemail(context);
- return null;
- }
- }.execute();
- }
-}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnection.java b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
index b462b54..70c1095 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnection.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnection.java
@@ -16,8 +16,11 @@
package com.android.dialer.simulator.impl;
+import android.content.Context;
import android.support.annotation.NonNull;
import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.VideoProfile;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.simulator.Simulator.Event;
@@ -30,6 +33,18 @@
private final List<Event> events = new ArrayList<>();
private int currentState = STATE_NEW;
+ SimulatorConnection(@NonNull Context context, @NonNull ConnectionRequest request) {
+ Assert.isNotNull(context);
+ Assert.isNotNull(request);
+ putExtras(request.getExtras());
+ setConnectionCapabilities(
+ CAPABILITY_MUTE
+ | CAPABILITY_SUPPORT_HOLD
+ | CAPABILITY_HOLD
+ | CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+ setVideoProvider(new SimulatorVideoProvider(context, this));
+ }
+
public void addListener(@NonNull Listener listener) {
listeners.add(Assert.isNotNull(listener));
}
@@ -44,9 +59,9 @@
}
@Override
- public void onAnswer() {
+ public void onAnswer(int videoState) {
LogUtil.enterBlock("SimulatorConnection.onAnswer");
- onEvent(new Event(Event.ANSWER));
+ onEvent(new Event(Event.ANSWER, Integer.toString(videoState), null));
}
@Override
@@ -75,9 +90,14 @@
@Override
public void onStateChanged(int newState) {
- LogUtil.enterBlock("SimulatorConnection.onStateChanged");
- onEvent(new Event(Event.STATE_CHANGE, stateToString(currentState), stateToString(newState)));
+ LogUtil.i(
+ "SimulatorConnection.onStateChanged",
+ "%s -> %s",
+ stateToString(currentState),
+ stateToString(newState));
+ int oldState = currentState;
currentState = newState;
+ onEvent(new Event(Event.STATE_CHANGE, stateToString(oldState), stateToString(newState)));
}
@Override
@@ -86,13 +106,22 @@
onEvent(new Event(Event.DTMF, Character.toString(c), null));
}
- private void onEvent(@NonNull Event event) {
+ void onEvent(@NonNull Event event) {
events.add(Assert.isNotNull(event));
for (Listener listener : listeners) {
listener.onEvent(this, event);
}
}
+ void handleSessionModifyRequest(@NonNull Event event) {
+ VideoProfile fromProfile = new VideoProfile(Integer.parseInt(event.data1));
+ VideoProfile toProfile = new VideoProfile(Integer.parseInt(event.data2));
+ setVideoState(toProfile.getVideoState());
+ getVideoProvider()
+ .receiveSessionModifyResponse(
+ Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS, fromProfile, toProfile);
+ }
+
/** Callback for when a new event arrives. */
public interface Listener {
void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event);
diff --git a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
index 06c2591..25d4a72 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorConnectionService.java
@@ -16,10 +16,7 @@
package com.android.dialer.simulator.impl;
-import android.content.ComponentName;
-import android.content.Context;
import android.net.Uri;
-import android.os.Bundle;
import android.support.annotation.NonNull;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -34,72 +31,15 @@
import java.util.ArrayList;
import java.util.List;
-/** Simple connection provider to create an incoming call. This is useful for emulators. */
+/** Simple connection provider to create phone calls. This is useful for emulators. */
public class SimulatorConnectionService extends ConnectionService {
-
- private static final String PHONE_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID";
- private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection";
private static final List<Listener> listeners = new ArrayList<>();
private static SimulatorConnectionService instance;
- private static void register(@NonNull Context context) {
- LogUtil.enterBlock("SimulatorConnectionService.register");
- Assert.isNotNull(context);
- context.getSystemService(TelecomManager.class).registerPhoneAccount(buildPhoneAccount(context));
- }
-
- private static void unregister(@NonNull Context context) {
- LogUtil.enterBlock("SimulatorConnectionService.unregister");
- Assert.isNotNull(context);
- context
- .getSystemService(TelecomManager.class)
- .unregisterPhoneAccount(buildPhoneAccount(context).getAccountHandle());
- }
-
public static SimulatorConnectionService getInstance() {
return instance;
}
- public static void addNewOutgoingCall(
- @NonNull Context context, @NonNull Bundle extras, @NonNull String phoneNumber) {
- LogUtil.enterBlock("SimulatorConnectionService.addNewOutgoingCall");
- Assert.isNotNull(context);
- Assert.isNotNull(extras);
- Assert.isNotNull(phoneNumber);
-
- register(context);
-
- Bundle bundle = new Bundle(extras);
- bundle.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
- Bundle outgoingCallExtras = new Bundle();
- outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, bundle);
-
- // Use the system's phone account so that these look like regular SIM call.
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- telecomManager.placeCall(
- Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), outgoingCallExtras);
- }
-
- public static void addNewIncomingCall(
- @NonNull Context context, @NonNull Bundle extras, @NonNull String callerId) {
- LogUtil.enterBlock("SimulatorConnectionService.addNewIncomingCall");
- Assert.isNotNull(context);
- Assert.isNotNull(extras);
- Assert.isNotNull(callerId);
-
- register(context);
-
- Bundle bundle = new Bundle(extras);
- bundle.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId);
- bundle.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
-
- // Use the system's phone account so that these look like regular SIM call.
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- PhoneAccountHandle systemPhoneAccount =
- telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
- telecomManager.addNewIncomingCall(systemPhoneAccount, bundle);
- }
-
public static void addListener(@NonNull Listener listener) {
listeners.add(Assert.isNotNull(listener));
}
@@ -108,32 +48,6 @@
listeners.remove(Assert.isNotNull(listener));
}
- @NonNull
- private static PhoneAccount buildPhoneAccount(Context context) {
- PhoneAccount.Builder builder =
- new PhoneAccount.Builder(
- getConnectionServiceHandle(context), "Simulator connection service");
- List<String> uriSchemes = new ArrayList<>();
- uriSchemes.add(PhoneAccount.SCHEME_TEL);
-
- return builder
- .setCapabilities(
- PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
- .setShortDescription("Simulator Connection Service")
- .setSupportedUriSchemes(uriSchemes)
- .build();
- }
-
- public static PhoneAccountHandle getConnectionServiceHandle(Context context) {
- return new PhoneAccountHandle(
- new ComponentName(context, SimulatorConnectionService.class), PHONE_ACCOUNT_ID);
- }
-
- private static Uri getPhoneNumber(ConnectionRequest request) {
- String phoneNumber = request.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
- return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null);
- }
-
@Override
public void onCreate() {
super.onCreate();
@@ -151,25 +65,19 @@
public Connection onCreateOutgoingConnection(
PhoneAccountHandle phoneAccount, ConnectionRequest request) {
LogUtil.enterBlock("SimulatorConnectionService.onCreateOutgoingConnection");
- if (!isSimulatorConnectionRequest(request)) {
+ if (!SimulatorSimCallManager.isSimulatorConnectionRequest(request)) {
LogUtil.i(
"SimulatorConnectionService.onCreateOutgoingConnection",
"outgoing call not from simulator, unregistering");
- Toast.makeText(
- this, "Unregistering Dialer simulator, making a real phone call", Toast.LENGTH_LONG)
+ Toast.makeText(this, "Unregistering simulator, making a real phone call", Toast.LENGTH_LONG)
.show();
- unregister(this);
+ SimulatorSimCallManager.unregister(this);
return null;
}
- SimulatorConnection connection = new SimulatorConnection();
+ SimulatorConnection connection = new SimulatorConnection(this, request);
connection.setDialing();
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
- connection.setConnectionCapabilities(
- Connection.CAPABILITY_MUTE
- | Connection.CAPABILITY_SUPPORT_HOLD
- | Connection.CAPABILITY_HOLD);
- connection.putExtras(request.getExtras());
for (Listener listener : listeners) {
listener.onNewOutgoingConnection(connection);
@@ -181,23 +89,19 @@
public Connection onCreateIncomingConnection(
PhoneAccountHandle phoneAccount, ConnectionRequest request) {
LogUtil.enterBlock("SimulatorConnectionService.onCreateIncomingConnection");
- if (!isSimulatorConnectionRequest(request)) {
+ if (!SimulatorSimCallManager.isSimulatorConnectionRequest(request)) {
LogUtil.i(
"SimulatorConnectionService.onCreateIncomingConnection",
"incoming call not from simulator, unregistering");
- Toast.makeText(
- this, "Unregistering Dialer simulator, got a real incoming call", Toast.LENGTH_LONG)
+ Toast.makeText(this, "Unregistering simulator, got a real incoming call", Toast.LENGTH_LONG)
.show();
- unregister(this);
+ SimulatorSimCallManager.unregister(this);
return null;
}
- SimulatorConnection connection = new SimulatorConnection();
+ SimulatorConnection connection = new SimulatorConnection(this, request);
connection.setRinging();
connection.setAddress(getPhoneNumber(request), TelecomManager.PRESENTATION_ALLOWED);
- connection.setConnectionCapabilities(
- Connection.CAPABILITY_MUTE | Connection.CAPABILITY_SUPPORT_HOLD);
- connection.putExtras(request.getExtras());
for (Listener listener : listeners) {
listener.onNewIncomingConnection(connection);
@@ -205,9 +109,9 @@
return connection;
}
- private static boolean isSimulatorConnectionRequest(@NonNull ConnectionRequest request) {
- return request.getExtras() != null
- && request.getExtras().getBoolean(EXTRA_IS_SIMULATOR_CONNECTION);
+ private static Uri getPhoneNumber(ConnectionRequest request) {
+ String phoneNumber = request.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
+ return Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null);
}
/** Callback used to notify listeners when a new connection has been added. */
diff --git a/java/com/android/dialer/simulator/impl/SimulatorImpl.java b/java/com/android/dialer/simulator/impl/SimulatorImpl.java
index 2dd180e..d6ee5ef 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorImpl.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorImpl.java
@@ -35,6 +35,6 @@
@Override
public ActionProvider getActionProvider(Context context) {
- return new SimulatorActionProvider(context);
+ return SimulatorMainMenu.getActionProvider(context);
}
}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
new file mode 100644
index 0000000..d663d58
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorMainMenu.java
@@ -0,0 +1,113 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.VoicemailContract;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.ActionProvider;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutors;
+import com.android.dialer.databasepopulator.CallLogPopulator;
+import com.android.dialer.databasepopulator.ContactsPopulator;
+import com.android.dialer.databasepopulator.VoicemailPopulator;
+import com.android.dialer.enrichedcall.simulator.EnrichedCallSimulatorActivity;
+import com.android.dialer.persistentlog.PersistentLogger;
+
+/** Implements the top level simulator menu. */
+final class SimulatorMainMenu {
+
+ static ActionProvider getActionProvider(@NonNull Context context) {
+ return new SimulatorSubMenu(context)
+ .addItem("Voice call", SimulatorVoiceCall.getActionProvider(context))
+ .addItem("IMS video", SimulatorVideoCall.getActionProvider(context))
+ .addItem("Notifications", SimulatorNotifications.getActionProvider(context))
+ .addItem("Populate database", () -> populateDatabase(context))
+ .addItem("Clean database", () -> cleanDatabase(context))
+ .addItem("Sync voicemail", () -> syncVoicemail(context))
+ .addItem("Share persistent log", () -> sharePersistentLog(context))
+ .addItem(
+ "Enriched call simulator",
+ () -> context.startActivity(EnrichedCallSimulatorActivity.newIntent(context)));
+ }
+
+ private static void populateDatabase(@NonNull Context context) {
+ DialerExecutors.createNonUiTaskBuilder(new PopulateDatabaseWorker())
+ .build()
+ .executeSerial(context);
+ }
+
+ private static void cleanDatabase(@NonNull Context context) {
+ DialerExecutors.createNonUiTaskBuilder(new CleanDatabaseWorker())
+ .build()
+ .executeSerial(context);
+ }
+
+ private static void syncVoicemail(@NonNull Context context) {
+ Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
+ context.sendBroadcast(intent);
+ }
+
+ private static void sharePersistentLog(@NonNull Context context) {
+ DialerExecutors.createNonUiTaskBuilder(new ShareLogWorker())
+ .onSuccess(
+ (String log) -> {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TEXT, log);
+ if (intent.resolveActivity(context.getPackageManager()) != null) {
+ context.startActivity(intent);
+ }
+ })
+ .build()
+ .executeSerial(null);
+ }
+
+ private SimulatorMainMenu() {}
+
+ private static class PopulateDatabaseWorker implements Worker<Context, Void> {
+ @Nullable
+ @Override
+ public Void doInBackground(Context context) {
+ ContactsPopulator.populateContacts(context);
+ CallLogPopulator.populateCallLog(context);
+ VoicemailPopulator.populateVoicemail(context);
+ return null;
+ }
+ }
+
+ private static class CleanDatabaseWorker implements Worker<Context, Void> {
+ @Nullable
+ @Override
+ public Void doInBackground(Context context) {
+ ContactsPopulator.deleteAllContacts(context);
+ CallLogPopulator.deleteAllCallLog(context);
+ VoicemailPopulator.deleteAllVoicemail(context);
+ return null;
+ }
+ }
+
+ private static class ShareLogWorker implements Worker<Void, String> {
+ @Nullable
+ @Override
+ public String doInBackground(Void unused) {
+ return PersistentLogger.dumpLogToString();
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
index 22eb967..f85f466 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorMissedCallCreator.java
@@ -74,7 +74,7 @@
extras.putInt(EXTRA_CALL_COUNT, callCount - 1);
extras.putBoolean(EXTRA_IS_MISSED_CALL_CONNECTION, true);
- SimulatorConnectionService.addNewIncomingCall(context, extras, callerId);
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras);
}
private static boolean isMissedCallConnection(@NonNull Connection connection) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorNotifications.java b/java/com/android/dialer/simulator/impl/SimulatorNotifications.java
index ebe8ecd..3f402d3 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorNotifications.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorNotifications.java
@@ -20,10 +20,6 @@
import android.provider.VoicemailContract.Voicemails;
import android.support.annotation.NonNull;
import android.view.ActionProvider;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.databasepopulator.VoicemailPopulator;
import java.util.concurrent.TimeUnit;
@@ -33,68 +29,18 @@
private static final int NOTIFICATION_COUNT = 12;
static ActionProvider getActionProvider(@NonNull Context context) {
- return new NotificationsActionProvider(context);
- }
-
- private static class NotificationsActionProvider extends ActionProvider {
- @NonNull private final Context context;
-
- public NotificationsActionProvider(@NonNull Context context) {
- super(Assert.isNotNull(context));
- this.context = context;
- }
-
- @Override
- public View onCreateActionView() {
- return null;
- }
-
- @Override
- public View onCreateActionView(MenuItem forItem) {
- return null;
- }
-
- @Override
- public boolean hasSubMenu() {
- return true;
- }
-
- @Override
- public void onPrepareSubMenu(@NonNull SubMenu subMenu) {
- LogUtil.enterBlock("NotificationsActionProvider.onPrepareSubMenu");
- Assert.isNotNull(subMenu);
- super.onPrepareSubMenu(subMenu);
-
- subMenu.clear();
- subMenu
- .add("Missed Calls")
- .setOnMenuItemClickListener(
- (item) -> {
- new SimulatorMissedCallCreator(context).start(NOTIFICATION_COUNT);
- return true;
- });
- subMenu
- .add("Voicemails")
- .setOnMenuItemClickListener(
- (item) -> {
- addVoicemailNotifications(context);
- return true;
- });
- subMenu
- .add("Non spam")
- .setOnMenuItemClickListener(
- (item) -> {
- new SimulatorSpamCallCreator(context, false /* isSpam */).start(NOTIFICATION_COUNT);
- return true;
- });
- subMenu
- .add("Confirm spam")
- .setOnMenuItemClickListener(
- (item) -> {
- new SimulatorSpamCallCreator(context, true /* isSpam */).start(NOTIFICATION_COUNT);
- return true;
- });
- }
+ return new SimulatorSubMenu(context)
+ .addItem(
+ "Missed calls", () -> new SimulatorMissedCallCreator(context).start(NOTIFICATION_COUNT))
+ .addItem("Voicemails", () -> addVoicemailNotifications(context))
+ .addItem(
+ "Non spam",
+ () ->
+ new SimulatorSpamCallCreator(context, false /* isSpam */).start(NOTIFICATION_COUNT))
+ .addItem(
+ "Confirm spam",
+ () ->
+ new SimulatorSpamCallCreator(context, true /* isSpam */).start(NOTIFICATION_COUNT));
}
private static void addVoicemailNotifications(@NonNull Context context) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java b/java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java
new file mode 100644
index 0000000..e089f75
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorPreviewCamera.java
@@ -0,0 +1,166 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.util.Size;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import java.util.Arrays;
+
+/**
+ * Used by the video provider to draw the local camera. The in-call UI is responsible for setting
+ * the camera (front or back) and the view to draw to. The video provider then uses this class to
+ * capture frames from the given camera and draw to the given view.
+ */
+final class SimulatorPreviewCamera {
+ @NonNull private final Context context;
+ @NonNull private final String cameraId;
+ @NonNull private final Surface surface;
+ @Nullable private CameraDevice camera;
+ private boolean isStopped;
+
+ SimulatorPreviewCamera(
+ @NonNull Context context, @NonNull String cameraId, @NonNull Surface surface) {
+ this.context = Assert.isNotNull(context);
+ this.cameraId = Assert.isNotNull(cameraId);
+ this.surface = Assert.isNotNull(surface);
+ }
+
+ void startCamera() {
+ LogUtil.enterBlock("SimulatorPreviewCamera.startCamera");
+ Assert.checkState(!isStopped);
+ try {
+ context
+ .getSystemService(CameraManager.class)
+ .openCamera(cameraId, new CameraListener(), null /* handler */);
+ } catch (CameraAccessException | SecurityException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+ }
+
+ void stopCamera() {
+ LogUtil.enterBlock("SimulatorPreviewCamera.stopCamera");
+ isStopped = true;
+ if (camera != null) {
+ camera.close();
+ camera = null;
+ }
+ }
+
+ @Nullable
+ static CameraCapabilities getCameraCapabilities(
+ @NonNull Context context, @Nullable String cameraId) {
+ if (cameraId == null) {
+ LogUtil.e("SimulatorPreviewCamera.getCameraCapabilities", "null camera ID");
+ return null;
+ }
+
+ CameraManager cameraManager = context.getSystemService(CameraManager.class);
+ CameraCharacteristics characteristics;
+ try {
+ characteristics = cameraManager.getCameraCharacteristics(cameraId);
+ } catch (CameraAccessException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+
+ StreamConfigurationMap map =
+ characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size previewSize = map.getOutputSizes(SurfaceTexture.class)[0];
+ LogUtil.i("SimulatorPreviewCamera.getCameraCapabilities", "preview size: " + previewSize);
+ return new CameraCapabilities(previewSize.getWidth(), previewSize.getHeight());
+ }
+
+ private final class CameraListener extends CameraDevice.StateCallback {
+ @Override
+ public void onOpened(CameraDevice camera) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CameraListener.onOpened");
+ SimulatorPreviewCamera.this.camera = camera;
+ if (isStopped) {
+ LogUtil.i("SimulatorPreviewCamera.CameraListener.onOpened", "stopped");
+ stopCamera();
+ return;
+ }
+
+ try {
+ camera.createCaptureSession(
+ Arrays.asList(Assert.isNotNull(surface)),
+ new CaptureSessionCallback(),
+ null /* handler */);
+ } catch (CameraAccessException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ LogUtil.i("SimulatorPreviewCamera.CameraListener.onError", "error: " + error);
+ stopCamera();
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CameraListener.onDisconnected");
+ stopCamera();
+ }
+
+ @Override
+ public void onClosed(CameraDevice camera) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CameraListener.onCLosed");
+ }
+ }
+
+ private final class CaptureSessionCallback extends CameraCaptureSession.StateCallback {
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession session) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CaptureSessionCallback.onConfigured");
+
+ if (isStopped) {
+ LogUtil.i("SimulatorPreviewCamera.CaptureSessionCallback.onConfigured", "stopped");
+ stopCamera();
+ return;
+ }
+ try {
+ CaptureRequest.Builder builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ builder.addTarget(surface);
+ builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ session.setRepeatingRequest(
+ builder.build(), null /* captureCallback */, null /* handler */);
+ } catch (CameraAccessException e) {
+ throw Assert.createIllegalStateFailException("camera error: " + e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ LogUtil.enterBlock("SimulatorPreviewCamera.CaptureSessionCallback.onConfigureFailed");
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java b/java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java
new file mode 100644
index 0000000..b14bba3
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorRemoteVideo.java
@@ -0,0 +1,163 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+
+/**
+ * Used by the video provider to draw the remote party's video. The in-call UI is responsible for
+ * setting the view to draw to. Since the simulator doesn't have a remote party we simply draw a
+ * green screen with a ball bouncing around.
+ */
+final class SimulatorRemoteVideo {
+ @NonNull private final RenderThread thread;
+ private boolean isStopped;
+
+ SimulatorRemoteVideo(@NonNull Surface surface) {
+ thread = new RenderThread(new Renderer(surface));
+ }
+
+ void startVideo() {
+ LogUtil.enterBlock("SimulatorRemoteVideo.startVideo");
+ Assert.checkState(!isStopped);
+ thread.start();
+ }
+
+ void stopVideo() {
+ LogUtil.enterBlock("SimulatorRemoteVideo.stopVideo");
+ isStopped = true;
+ thread.quitSafely();
+ }
+
+ @VisibleForTesting
+ Runnable getRenderer() {
+ return thread.getRenderer();
+ }
+
+ private static class Renderer implements Runnable {
+ private static final int FRAME_DELAY_MILLIS = 33;
+ private static final float CIRCLE_STEP = 16.0f;
+
+ @NonNull private final Surface surface;
+ private float circleX;
+ private float circleY;
+ private float radius;
+ private double angle;
+
+ Renderer(@NonNull Surface surface) {
+ this.surface = Assert.isNotNull(surface);
+ }
+
+ @Override
+ public void run() {
+ drawFrame();
+ schedule();
+ }
+
+ @WorkerThread
+ void schedule() {
+ Assert.isWorkerThread();
+ new Handler().postDelayed(this, FRAME_DELAY_MILLIS);
+ }
+
+ @WorkerThread
+ private void drawFrame() {
+ Assert.isWorkerThread();
+ Canvas canvas;
+ try {
+ canvas = surface.lockCanvas(null /* dirtyRect */);
+ } catch (IllegalArgumentException e) {
+ // This can happen when the video fragment tears down.
+ LogUtil.e("SimulatorRemoteVideo.RenderThread.drawFrame", "unable to lock canvas", e);
+ return;
+ }
+
+ LogUtil.i(
+ "SimulatorRemoteVideo.RenderThread.drawFrame",
+ "size; %d x %d",
+ canvas.getWidth(),
+ canvas.getHeight());
+ canvas.drawColor(Color.GREEN);
+ moveCircle(canvas);
+ drawCircle(canvas);
+ surface.unlockCanvasAndPost(canvas);
+ }
+
+ @WorkerThread
+ private void moveCircle(Canvas canvas) {
+ Assert.isWorkerThread();
+ int width = canvas.getWidth();
+ int height = canvas.getHeight();
+ if (circleX == 0 && circleY == 0) {
+ circleX = width / 2.0f;
+ circleY = height / 2.0f;
+ angle = Math.PI / 4.0;
+ radius = Math.min(canvas.getWidth(), canvas.getHeight()) * 0.15f;
+ } else {
+ circleX += (float) Math.cos(angle) * CIRCLE_STEP;
+ circleY += (float) Math.sin(angle) * CIRCLE_STEP;
+ // Bounce the circle off the edge.
+ if (circleX + radius >= width
+ || circleX - radius <= 0
+ || circleY + radius >= height
+ || circleY - radius <= 0) {
+ angle += Math.PI / 2.0;
+ }
+ }
+ }
+
+ @WorkerThread
+ private void drawCircle(Canvas canvas) {
+ Assert.isWorkerThread();
+ Paint paint = new Paint();
+ paint.setColor(Color.MAGENTA);
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawCircle(circleX, circleY, radius, paint);
+ }
+ }
+
+ private static class RenderThread extends HandlerThread {
+ @NonNull private final Renderer renderer;
+
+ RenderThread(@NonNull Renderer renderer) {
+ super("SimulatorRemoteVideo");
+ this.renderer = Assert.isNotNull(renderer);
+ }
+
+ @Override
+ @WorkerThread
+ protected void onLooperPrepared() {
+ Assert.isWorkerThread();
+ renderer.schedule();
+ }
+
+ @VisibleForTesting
+ Runnable getRenderer() {
+ return renderer;
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
new file mode 100644
index 0000000..33eac51
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java
@@ -0,0 +1,199 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Utility to use the simulator connection service to add phone calls. To ensure that the added
+ * calls are routed through the simulator we register ourselves as a SIM call manager using
+ * CAPABILITY_CONNECTION_MANAGER. This ensures that all calls on the device must first go through
+ * our connection service.
+ *
+ * <p>For video calls this will only work if the underlying telephony phone account also supports
+ * video. To ensure that video always works we use a separate video account. The user must manually
+ * enable this account in call settings for video calls to work.
+ */
+public class SimulatorSimCallManager {
+
+ private static final String SIM_CALL_MANAGER_ACCOUNT_ID = "SIMULATOR_ACCOUNT_ID";
+ private static final String VIDEO_PROVIDER_ACCOUNT_ID = "SIMULATOR_VIDEO_ACCOUNT_ID";
+ private static final String EXTRA_IS_SIMULATOR_CONNECTION = "is_simulator_connection";
+
+ static void register(@NonNull Context context) {
+ LogUtil.enterBlock("SimulatorSimCallManager.register");
+ Assert.isNotNull(context);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ telecomManager.registerPhoneAccount(buildSimCallManagerAccount(context));
+ telecomManager.registerPhoneAccount(buildVideoProviderAccount(context));
+ }
+
+ static void unregister(@NonNull Context context) {
+ LogUtil.enterBlock("SimulatorSimCallManager.unregister");
+ Assert.isNotNull(context);
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ telecomManager.unregisterPhoneAccount(getSimCallManagerHandle(context));
+ telecomManager.unregisterPhoneAccount(getVideoProviderHandle(context));
+ }
+
+ @NonNull
+ public static String addNewOutgoingCall(
+ @NonNull Context context, @NonNull String phoneNumber, boolean isVideo) {
+ return addNewOutgoingCall(context, phoneNumber, isVideo, new Bundle());
+ }
+
+ @NonNull
+ public static String addNewOutgoingCall(
+ @NonNull Context context,
+ @NonNull String phoneNumber,
+ boolean isVideo,
+ @NonNull Bundle extras) {
+ LogUtil.enterBlock("SimulatorSimCallManager.addNewOutgoingCall");
+ Assert.isNotNull(context);
+ Assert.isNotNull(extras);
+ Assert.isNotNull(phoneNumber);
+ Assert.isNotNull(extras);
+
+ register(context);
+
+ extras = new Bundle(extras);
+ extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
+ String connectionTag = createUniqueConnectionTag();
+ extras.putBoolean(connectionTag, true);
+
+ Bundle outgoingCallExtras = new Bundle();
+ outgoingCallExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
+ outgoingCallExtras.putParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context));
+
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ try {
+ telecomManager.placeCall(
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null), outgoingCallExtras);
+ } catch (SecurityException e) {
+ throw Assert.createIllegalStateFailException("Unable to place call: " + e);
+ }
+ return connectionTag;
+ }
+
+ @NonNull
+ public static String addNewIncomingCall(
+ @NonNull Context context, @NonNull String callerId, boolean isVideo) {
+ return addNewIncomingCall(context, callerId, isVideo, new Bundle());
+ }
+
+ @NonNull
+ public static String addNewIncomingCall(
+ @NonNull Context context, @NonNull String callerId, boolean isVideo, @NonNull Bundle extras) {
+ LogUtil.enterBlock("SimulatorSimCallManager.addNewIncomingCall");
+ Assert.isNotNull(context);
+ Assert.isNotNull(callerId);
+ Assert.isNotNull(extras);
+
+ register(context);
+
+ extras = new Bundle(extras);
+ extras.putString(TelephonyManager.EXTRA_INCOMING_NUMBER, callerId);
+ extras.putBoolean(EXTRA_IS_SIMULATOR_CONNECTION, true);
+ String connectionTag = createUniqueConnectionTag();
+ extras.putBoolean(connectionTag, true);
+
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ telecomManager.addNewIncomingCall(
+ isVideo ? getVideoProviderHandle(context) : getSystemPhoneAccountHandle(context), extras);
+ return connectionTag;
+ }
+
+ @NonNull
+ private static PhoneAccount buildSimCallManagerAccount(Context context) {
+ return new PhoneAccount.Builder(getSimCallManagerHandle(context), "Simulator SIM call manager")
+ .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+ .setShortDescription("Simulator SIM call manager")
+ .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
+ .build();
+ }
+
+ @NonNull
+ private static PhoneAccount buildVideoProviderAccount(Context context) {
+ return new PhoneAccount.Builder(getVideoProviderHandle(context), "Simulator video provider")
+ .setCapabilities(
+ PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING
+ | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ .setShortDescription("Simulator video provider")
+ .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
+ .build();
+ }
+
+ @NonNull
+ public static PhoneAccountHandle getSimCallManagerHandle(@NonNull Context context) {
+ return new PhoneAccountHandle(
+ new ComponentName(context, SimulatorConnectionService.class), SIM_CALL_MANAGER_ACCOUNT_ID);
+ }
+
+ @NonNull
+ static PhoneAccountHandle getVideoProviderHandle(@NonNull Context context) {
+ return new PhoneAccountHandle(
+ new ComponentName(context, SimulatorConnectionService.class), VIDEO_PROVIDER_ACCOUNT_ID);
+ }
+
+ @NonNull
+ private static PhoneAccountHandle getSystemPhoneAccountHandle(@NonNull Context context) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ List<PhoneAccountHandle> handles;
+ try {
+ handles = telecomManager.getCallCapablePhoneAccounts();
+ } catch (SecurityException e) {
+ throw Assert.createIllegalStateFailException("Unable to get phone accounts: " + e);
+ }
+ for (PhoneAccountHandle handle : handles) {
+ PhoneAccount account = telecomManager.getPhoneAccount(handle);
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ return handle;
+ }
+ }
+ throw Assert.createIllegalStateFailException("no SIM phone account available");
+ }
+
+ public static boolean isSimulatorConnectionRequest(@NonNull ConnectionRequest request) {
+ return request.getExtras() != null
+ && request.getExtras().getBoolean(EXTRA_IS_SIMULATOR_CONNECTION);
+ }
+
+ @NonNull
+ private static String createUniqueConnectionTag() {
+ int callId = new Random().nextInt();
+ return String.format("simulator_phone_call_%x", Math.abs(callId));
+ }
+
+ private SimulatorSimCallManager() {}
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
index ae97bc1..757658d 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorSpamCallCreator.java
@@ -91,7 +91,7 @@
// We need to clear the call log because spam notifications are only shown for new calls.
clearCallLog(context);
- SimulatorConnectionService.addNewIncomingCall(context, extras, callerId);
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */, extras);
}
private static boolean isSpamCallConnection(@NonNull Connection connection) {
diff --git a/java/com/android/dialer/simulator/impl/SimulatorSubMenu.java b/java/com/android/dialer/simulator/impl/SimulatorSubMenu.java
new file mode 100644
index 0000000..64a2e72
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorSubMenu.java
@@ -0,0 +1,100 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.ActionProvider;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import com.android.dialer.common.Assert;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Makes it easier to create submenus in the simulator. */
+final class SimulatorSubMenu extends ActionProvider {
+ List<Item> items = new ArrayList<>();
+
+ SimulatorSubMenu(@NonNull Context context) {
+ super(Assert.isNotNull(context));
+ }
+
+ SimulatorSubMenu addItem(@NonNull String title, @NonNull Runnable clickHandler) {
+ items.add(new Item(title, clickHandler));
+ return this;
+ }
+
+ SimulatorSubMenu addItem(@NonNull String title, @NonNull ActionProvider actionProvider) {
+ items.add(new Item(title, actionProvider));
+ return this;
+ }
+
+ @Override
+ public View onCreateActionView() {
+ return null;
+ }
+
+ @Override
+ public View onCreateActionView(MenuItem forItem) {
+ return null;
+ }
+
+ @Override
+ public boolean hasSubMenu() {
+ return true;
+ }
+
+ @Override
+ public void onPrepareSubMenu(SubMenu subMenu) {
+ super.onPrepareSubMenu(subMenu);
+ subMenu.clear();
+
+ for (Item item : items) {
+ if (item.clickHandler != null) {
+ subMenu
+ .add(item.title)
+ .setOnMenuItemClickListener(
+ (i) -> {
+ item.clickHandler.run();
+ return true;
+ });
+ } else {
+ subMenu.add(item.title).setActionProvider(item.actionProvider);
+ }
+ }
+ }
+
+ private static final class Item {
+ @NonNull final String title;
+ @Nullable final Runnable clickHandler;
+ @Nullable final ActionProvider actionProvider;
+
+ Item(@NonNull String title, @NonNull Runnable clickHandler) {
+ this.title = Assert.isNotNull(title);
+ this.clickHandler = Assert.isNotNull(clickHandler);
+ actionProvider = null;
+ }
+
+ Item(@NonNull String title, @NonNull ActionProvider actionProvider) {
+ this.title = Assert.isNotNull(title);
+ this.clickHandler = null;
+ this.actionProvider = Assert.isNotNull(actionProvider);
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
new file mode 100644
index 0000000..3f00ab1
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorVideoCall.java
@@ -0,0 +1,164 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.view.ActionProvider;
+import android.widget.Toast;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.simulator.Simulator.Event;
+
+/** Entry point in the simulator to create video calls. */
+final class SimulatorVideoCall
+ implements SimulatorConnectionService.Listener, SimulatorConnection.Listener {
+ @NonNull private final Context context;
+ private final int initialVideoCapability;
+ private final int initialVideoState;
+ @Nullable private String connectionTag;
+
+ static ActionProvider getActionProvider(@NonNull Context context) {
+ return new SimulatorSubMenu(context)
+ .addItem(
+ "Incoming one way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_RX_ENABLED).addNewIncomingCall())
+ .addItem(
+ "Incoming two way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_BIDIRECTIONAL)
+ .addNewIncomingCall())
+ .addItem(
+ "Outgoing one way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_TX_ENABLED).addNewOutgoingCall())
+ .addItem(
+ "Outgoing two way",
+ () ->
+ new SimulatorVideoCall(context, VideoProfile.STATE_BIDIRECTIONAL)
+ .addNewOutgoingCall());
+ }
+
+ private SimulatorVideoCall(@NonNull Context context, int initialVideoState) {
+ this.context = Assert.isNotNull(context);
+ this.initialVideoCapability =
+ Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL
+ | Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL;
+ this.initialVideoState = initialVideoState;
+ SimulatorConnectionService.addListener(this);
+ }
+
+ private void addNewIncomingCall() {
+ if (!isVideoAccountEnabled()) {
+ showVideoAccountSettings();
+ return;
+ }
+ String callerId = "+44 (0) 20 7031 3000"; // Google London office
+ connectionTag =
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, true /* isVideo */);
+ }
+
+ private void addNewOutgoingCall() {
+ if (!isVideoAccountEnabled()) {
+ showVideoAccountSettings();
+ return;
+ }
+ String phoneNumber = "+44 (0) 20 7031 3000"; // Google London office
+ connectionTag =
+ SimulatorSimCallManager.addNewOutgoingCall(context, phoneNumber, true /* isVideo */);
+ }
+
+ @Override
+ public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVideoCall.onNewOutgoingConnection", "connection created");
+ handleNewConnection(connection);
+ // Telecom will force the connection to switch to Dialing when we return it. Wait until after
+ // we're returned it before changing call state.
+ ThreadUtil.postOnUiThread(() -> connection.setActive());
+ }
+ }
+
+ @Override
+ public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVideoCall.onNewIncomingConnection", "connection created");
+ handleNewConnection(connection);
+ }
+ }
+
+ private boolean isVideoAccountEnabled() {
+ SimulatorSimCallManager.register(context);
+ return context
+ .getSystemService(TelecomManager.class)
+ .getPhoneAccount(SimulatorSimCallManager.getVideoProviderHandle(context))
+ .isEnabled();
+ }
+
+ private void showVideoAccountSettings() {
+ context.startActivity(new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS));
+ Toast.makeText(context, "Please enable simulator video provider", Toast.LENGTH_LONG).show();
+ }
+
+ private void handleNewConnection(@NonNull SimulatorConnection connection) {
+ connection.addListener(this);
+ connection.setConnectionCapabilities(
+ connection.getConnectionCapabilities() | initialVideoCapability);
+ connection.setVideoState(initialVideoState);
+ }
+
+ @Override
+ public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
+ switch (event.type) {
+ case Event.NONE:
+ throw Assert.createIllegalStateFailException();
+ case Event.ANSWER:
+ connection.setVideoState(Integer.parseInt(event.data1));
+ connection.setActive();
+ break;
+ case Event.REJECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+ break;
+ case Event.HOLD:
+ connection.setOnHold();
+ break;
+ case Event.UNHOLD:
+ connection.setActive();
+ break;
+ case Event.DISCONNECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ break;
+ case Event.STATE_CHANGE:
+ break;
+ case Event.DTMF:
+ break;
+ case Event.SESSION_MODIFY_REQUEST:
+ ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException();
+ }
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java b/java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java
new file mode 100644
index 0000000..a596728
--- /dev/null
+++ b/java/com/android/dialer/simulator/impl/SimulatorVideoProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.dialer.simulator.impl;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.VideoProfile;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.simulator.Simulator.Event;
+
+/**
+ * Implements the telecom video provider API to simulate IMS video calling. A video capable phone
+ * always has one video provider associated with it. Actual drawing of local and remote video is
+ * done by {@link SimulatorPreviewCamera} and {@link SimulatorRemoteVideo} respectively.
+ */
+final class SimulatorVideoProvider extends Connection.VideoProvider {
+ @NonNull private final Context context;
+ @NonNull private final SimulatorConnection connection;
+ @Nullable private String previewCameraId;;
+ @Nullable private SimulatorPreviewCamera simulatorPreviewCamera;
+ @Nullable private SimulatorRemoteVideo simulatorRemoteVideo;
+
+ SimulatorVideoProvider(@NonNull Context context, @NonNull SimulatorConnection connection) {
+ this.context = Assert.isNotNull(context);
+ this.connection = Assert.isNotNull(connection);
+ }
+
+ @Override
+ public void onSetCamera(String previewCameraId) {
+ LogUtil.i("SimulatorVideoProvider.onSetCamera", "previewCameraId: " + previewCameraId);
+ this.previewCameraId = previewCameraId;
+ if (simulatorPreviewCamera != null) {
+ simulatorPreviewCamera.stopCamera();
+ simulatorPreviewCamera = null;
+ }
+ }
+
+ @Override
+ public void onSetPreviewSurface(Surface surface) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSetPreviewSurface");
+ if (simulatorPreviewCamera != null) {
+ simulatorPreviewCamera.stopCamera();
+ simulatorPreviewCamera = null;
+ }
+ if (surface != null && previewCameraId != null) {
+ simulatorPreviewCamera = new SimulatorPreviewCamera(context, previewCameraId, surface);
+ simulatorPreviewCamera.startCamera();
+ }
+ }
+
+ @Override
+ public void onSetDisplaySurface(Surface surface) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSetDisplaySurface");
+ if (simulatorRemoteVideo != null) {
+ simulatorRemoteVideo.stopVideo();
+ simulatorRemoteVideo = null;
+ }
+ if (surface != null) {
+ simulatorRemoteVideo = new SimulatorRemoteVideo(surface);
+ simulatorRemoteVideo.startVideo();
+ }
+ }
+
+ @Override
+ public void onSetDeviceOrientation(int rotation) {
+ LogUtil.i("SimulatorVideoProvider.onSetDeviceOrientation", "rotation: " + rotation);
+ }
+
+ @Override
+ public void onSetZoom(float value) {
+ LogUtil.i("SimulatorVideoProvider.onSetZoom", "zoom: " + value);
+ }
+
+ @Override
+ public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSendSessionModifyRequest");
+ connection.onEvent(
+ new Event(
+ Event.SESSION_MODIFY_REQUEST,
+ Integer.toString(fromProfile.getVideoState()),
+ Integer.toString(toProfile.getVideoState())));
+ }
+
+ @Override
+ public void onSendSessionModifyResponse(VideoProfile responseProfile) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSendSessionModifyResponse");
+ }
+
+ @Override
+ public void onRequestCameraCapabilities() {
+ LogUtil.enterBlock("SimulatorVideoProvider.onRequestCameraCapabilities");
+ changeCameraCapabilities(
+ SimulatorPreviewCamera.getCameraCapabilities(context, previewCameraId));
+ }
+
+ @Override
+ public void onRequestConnectionDataUsage() {
+ LogUtil.enterBlock("SimulatorVideoProvider.onRequestConnectionDataUsage");
+ setCallDataUsage(10 * 1024);
+ }
+
+ @Override
+ public void onSetPauseImage(Uri uri) {
+ LogUtil.enterBlock("SimulatorVideoProvider.onSetPauseImage");
+ }
+}
diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
index 2512828..8eefb48 100644
--- a/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
+++ b/java/com/android/dialer/simulator/impl/SimulatorVoiceCall.java
@@ -17,18 +17,103 @@
package com.android.dialer.simulator.impl;
import android.content.Context;
-import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.view.ActionProvider;
+import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.simulator.Simulator.Event;
-/** Utilities to simulate phone calls. */
-final class SimulatorVoiceCall {
- static void addNewIncomingCall(@NonNull Context context) {
- LogUtil.enterBlock("SimulatorVoiceCall.addNewIncomingCall");
- // Set the caller ID to the Google London office.
- String callerId = "+44 (0) 20 7031 3000";
- SimulatorConnectionService.addNewIncomingCall(context, new Bundle(), callerId);
+/** Entry point in the simulator to create voice calls. */
+final class SimulatorVoiceCall
+ implements SimulatorConnectionService.Listener, SimulatorConnection.Listener {
+ @NonNull private final Context context;
+ @Nullable private String connectionTag;
+
+ static ActionProvider getActionProvider(@NonNull Context context) {
+ return new SimulatorSubMenu(context)
+ .addItem("Incoming call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(false))
+ .addItem("Outgoing call", () -> new SimulatorVoiceCall(context).addNewOutgoingCall())
+ .addItem("Spam call", () -> new SimulatorVoiceCall(context).addNewIncomingCall(true));
}
- private SimulatorVoiceCall() {}
+ private SimulatorVoiceCall(@NonNull Context context) {
+ this.context = Assert.isNotNull(context);
+ SimulatorConnectionService.addListener(this);
+ }
+
+ private void addNewIncomingCall(boolean isSpam) {
+ String callerId =
+ isSpam
+ ? "+1-661-778-3020" /* Blacklisted custom spam number */
+ : "+44 (0) 20 7031 3000" /* Google London office */;
+ connectionTag =
+ SimulatorSimCallManager.addNewIncomingCall(context, callerId, false /* isVideo */);
+ }
+
+ private void addNewOutgoingCall() {
+ String callerId = "+55-31-2128-6800"; // Brazil office.
+ connectionTag =
+ SimulatorSimCallManager.addNewOutgoingCall(context, callerId, false /* isVideo */);
+ }
+
+ @Override
+ public void onNewOutgoingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVoiceCall.onNewOutgoingConnection", "connection created");
+ handleNewConnection(connection);
+ connection.setActive();
+ }
+ }
+
+ @Override
+ public void onNewIncomingConnection(@NonNull SimulatorConnection connection) {
+ if (connection.getExtras().getBoolean(connectionTag)) {
+ LogUtil.i("SimulatorVoiceCall.onNewIncomingConnection", "connection created");
+ handleNewConnection(connection);
+ }
+ }
+
+ private void handleNewConnection(@NonNull SimulatorConnection connection) {
+ connection.addListener(this);
+ connection.setConnectionCapabilities(
+ connection.getConnectionCapabilities()
+ | Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL
+ | Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+ }
+
+ @Override
+ public void onEvent(@NonNull SimulatorConnection connection, @NonNull Event event) {
+ switch (event.type) {
+ case Event.NONE:
+ throw Assert.createIllegalStateFailException();
+ case Event.ANSWER:
+ connection.setActive();
+ break;
+ case Event.REJECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.REJECTED));
+ break;
+ case Event.HOLD:
+ connection.setOnHold();
+ break;
+ case Event.UNHOLD:
+ connection.setActive();
+ break;
+ case Event.DISCONNECT:
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+ break;
+ case Event.STATE_CHANGE:
+ break;
+ case Event.DTMF:
+ break;
+ case Event.SESSION_MODIFY_REQUEST:
+ ThreadUtil.postDelayedOnUiThread(() -> connection.handleSessionModifyRequest(event), 2000);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException();
+ }
+ }
}
diff --git a/java/com/android/dialershared/bubble/g3doc/INTEGRATION.md b/java/com/android/dialershared/bubble/g3doc/INTEGRATION.md
deleted file mode 100644
index a13a605..0000000
--- a/java/com/android/dialershared/bubble/g3doc/INTEGRATION.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Floating Bubble Integration
-
-go/bubble-integration
-
-Author: keyboardr@
-
-Last Updated: 2017-06-06
-
-Floating bubbles provide a lightweight means of providing interactive UI while
-the user is away from the app. This document details the steps necessary to
-integrate these bubbles into your app.
-
-[TOC]
-
-{height=400}
-
-## Ensure Bubbles can be shown
-
-Add the `android.permission.SYSTEM_ALERT_WINDOW` permission to your manifest.
-Before you show the bubble, call `Bubble.canShowBubbles(Context)` to see if the
-user has granted you permission. If not, you can start an Activity from
-`Bubble.getRequestPermissionIntent(Context)` to navigate the user to the system
-settings to enable drawing over other apps. This is more than just a simple
-runtime permission; the user must explicitly allow you to draw over other apps
-via this system setting. System apps may have this allowed by default, but be
-sure to test.
-
-## Create your initial `BubbleInfo`
-
-Use `BubbleInfo.builder()` to populate a `BubbleInfo` with your color, main
-icon, main Intent (which should navigate back to your app), starting Y position,
-and a list of `Actions` to put in the drawer. Each `Action` will define its
-icon, user-displayable name (used for content description), Intent to perform
-when clicked, whether it is enabled (optional, default true), and whether it is
-checked (optional, default false).
-
-{height=400}
-
-## Create, show, and hide the Bubble
-
-Create the bubble using `Bubble.createBubble(Context, BubbleInfo)`. The `show()`
-method is safe to call at any time. If the Bubble is already showing, it is a
-no-op. `hide()` may also be called at any time and will collapse the drawer
-before hiding if already open. While `show()` will show immediately, `hide()`
-may need to wait for other operations or animations before the bubble is hidden.
-It is unlikely you will need to keep track of this, however. The bubble will be
-hidden at its next opportunity, and `hide()` will not block.
-
-{height=400}
-
-## Update the Bubble's state
-
-Call `Bubble.setBubbleInfo(BubbleInfo)` to update all displayed state.
-`BubbleInfo`s are immutable, so to make a new one using an existing
-`BubbleInfo`, use `BubbleInfo.from(BubbleInfo)` to get a `Builder` with
-prepopulated info. If only the `Action` state has changed, it is more efficient
-to just call `Bubble.updateActions(List<Action>)`
-
-{height=400}
-
-## Show text
-
-To temporarily replace the icon with a textual message, call
-`Bubble.showText(CharSequence)`. The text will be displayed for several seconds
-before transitioning back to the primary icon. The drawer will be closed if open
-and cannot be reopened while the text is displayed. Any calls to `hide()` will
-be deferred until after the text is done being displayed, so if you wish to show
-an ending message of some sort you may call `hide()` immediately after
-`showText(CharSequence)`.
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.png
deleted file mode 100644
index 7ecc067..0000000
--- a/java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.png
+++ /dev/null
Binary files differ
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.png
deleted file mode 100644
index cd477f3..0000000
--- a/java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.png
+++ /dev/null
Binary files differ
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_state.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_state.png
deleted file mode 100644
index 21ca8a8..0000000
--- a/java/com/android/dialershared/bubble/g3doc/images/bubble_state.png
+++ /dev/null
Binary files differ
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_text.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_text.png
deleted file mode 100644
index 9c476dc..0000000
--- a/java/com/android/dialershared/bubble/g3doc/images/bubble_text.png
+++ /dev/null
Binary files differ
diff --git a/java/com/android/incallui/AnswerScreenPresenter.java b/java/com/android/incallui/AnswerScreenPresenter.java
index d530401..58231d5 100644
--- a/java/com/android/incallui/AnswerScreenPresenter.java
+++ b/java/com/android/incallui/AnswerScreenPresenter.java
@@ -104,7 +104,7 @@
DialerImpression.Type.VIDEO_CALL_REQUEST_ACCEPTED,
call.getUniqueCallId(),
call.getTimeAddedMs());
- call.getVideoTech().acceptVideoRequest();
+ call.getVideoTech().acceptVideoRequest(context);
}
} else {
if (answerVideoAsAudio) {
diff --git a/java/com/android/incallui/AudioRouteSelectorActivity.java b/java/com/android/incallui/AudioRouteSelectorActivity.java
index dfd4d1a..f0ae79b 100644
--- a/java/com/android/incallui/AudioRouteSelectorActivity.java
+++ b/java/com/android/incallui/AudioRouteSelectorActivity.java
@@ -32,7 +32,7 @@
protected void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
AudioRouteSelectorDialogFragment.newInstance(AudioModeProvider.getInstance().getAudioState())
- .show(getSupportFragmentManager(), null);
+ .show(getSupportFragmentManager(), AudioRouteSelectorDialogFragment.TAG);
}
@Override
@@ -44,4 +44,20 @@
public void onAudioRouteSelectorDismiss() {
finish();
}
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ AudioRouteSelectorDialogFragment audioRouteSelectorDialogFragment =
+ (AudioRouteSelectorDialogFragment)
+ getSupportFragmentManager().findFragmentByTag(AudioRouteSelectorDialogFragment.TAG);
+ // If Android back button is pressed, the fragment is dismissed and removed. If home button is
+ // pressed, we have to manually dismiss the fragment here. The fragment is also removed when
+ // dismissed.
+ if (audioRouteSelectorDialogFragment != null) {
+ audioRouteSelectorDialogFragment.dismiss();
+ }
+ // We don't expect the activity to resume
+ finish();
+ }
}
diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java
index 658ae64..bd5bb78 100644
--- a/java/com/android/incallui/CallButtonPresenter.java
+++ b/java/com/android/incallui/CallButtonPresenter.java
@@ -294,7 +294,7 @@
DialerImpression.Type.VIDEO_CALL_UPGRADE_REQUESTED,
mCall.getUniqueCallId(),
mCall.getTimeAddedMs());
- mCall.getVideoTech().upgradeToVideo();
+ mCall.getVideoTech().upgradeToVideo(mContext);
}
@Override
@@ -360,7 +360,7 @@
} else {
updateCamera(
InCallPresenter.getInstance().getInCallCameraManager().isUsingFrontFacingCamera());
- mCall.getVideoTech().resumeTransmission();
+ mCall.getVideoTech().resumeTransmission(mContext);
}
mInCallButtonUi.setVideoPaused(pause);
diff --git a/java/com/android/incallui/NotificationBroadcastReceiver.java b/java/com/android/incallui/NotificationBroadcastReceiver.java
index 5e757cf..0daa017 100644
--- a/java/com/android/incallui/NotificationBroadcastReceiver.java
+++ b/java/com/android/incallui/NotificationBroadcastReceiver.java
@@ -95,7 +95,7 @@
} else {
DialerCall call = callList.getVideoUpgradeRequestCall();
if (call != null) {
- call.getVideoTech().acceptVideoRequest();
+ call.getVideoTech().acceptVideoRequest(context);
}
}
}
diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java
index e54102c..fd48b37 100644
--- a/java/com/android/incallui/ReturnToCallController.java
+++ b/java/com/android/incallui/ReturnToCallController.java
@@ -23,16 +23,16 @@
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.telecom.CallAudioState;
+import com.android.bubble.Bubble;
+import com.android.bubble.Bubble.BubbleExpansionStateListener;
+import com.android.bubble.Bubble.ExpansionState;
+import com.android.bubble.BubbleInfo;
+import com.android.bubble.BubbleInfo.Action;
import com.android.dialer.common.LogUtil;
import com.android.dialer.configprovider.ConfigProviderBindings;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.telecom.TelecomUtil;
-import com.android.dialershared.bubble.Bubble;
-import com.android.dialershared.bubble.Bubble.BubbleExpansionStateListener;
-import com.android.dialershared.bubble.Bubble.ExpansionState;
-import com.android.dialershared.bubble.BubbleInfo;
-import com.android.dialershared.bubble.BubbleInfo.Action;
import com.android.incallui.InCallPresenter.InCallUiListener;
import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.audiomode.AudioModeProvider.AudioModeListener;
diff --git a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
index c7a9d63..860d2d2 100644
--- a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
+++ b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
@@ -37,6 +37,7 @@
/** Shows picker for audio routes */
public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment {
+ public static final String TAG = "AudioRouteSelectorDialogFragment";
private static final String ARG_AUDIO_STATE = "audio_state";
/** Called when an audio route is picked */
diff --git a/java/com/android/incallui/videotech/VideoTech.java b/java/com/android/incallui/videotech/VideoTech.java
index 79a8c60..e3753bc 100644
--- a/java/com/android/incallui/videotech/VideoTech.java
+++ b/java/com/android/incallui/videotech/VideoTech.java
@@ -17,6 +17,7 @@
package com.android.incallui.videotech;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.dialer.logging.DialerImpression;
import com.android.incallui.video.protocol.VideoCallScreen;
@@ -48,9 +49,9 @@
@SessionModificationState
int getSessionModificationState();
- void upgradeToVideo();
+ void upgradeToVideo(@NonNull Context context);
- void acceptVideoRequest();
+ void acceptVideoRequest(@NonNull Context context);
void acceptVideoRequestAsAudio();
@@ -60,7 +61,7 @@
void stopTransmission();
- void resumeTransmission();
+ void resumeTransmission(@NonNull Context context);
void pause();
diff --git a/java/com/android/incallui/videotech/empty/EmptyVideoTech.java b/java/com/android/incallui/videotech/empty/EmptyVideoTech.java
index 34dd1bf..76766df 100644
--- a/java/com/android/incallui/videotech/empty/EmptyVideoTech.java
+++ b/java/com/android/incallui/videotech/empty/EmptyVideoTech.java
@@ -17,6 +17,7 @@
package com.android.incallui.videotech.empty;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.dialer.common.Assert;
import com.android.incallui.video.protocol.VideoCallScreen;
@@ -65,10 +66,10 @@
}
@Override
- public void upgradeToVideo() {}
+ public void upgradeToVideo(@NonNull Context context) {}
@Override
- public void acceptVideoRequest() {}
+ public void acceptVideoRequest(@NonNull Context context) {}
@Override
public void acceptVideoRequestAsAudio() {}
@@ -85,7 +86,7 @@
public void stopTransmission() {}
@Override
- public void resumeTransmission() {}
+ public void resumeTransmission(@NonNull Context context) {}
@Override
public void pause() {}
diff --git a/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java b/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
index b839293..954dfcd 100644
--- a/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
+++ b/java/com/android/incallui/videotech/ims/ImsVideoCallCallback.java
@@ -16,6 +16,7 @@
package com.android.incallui.videotech.ims;
+import android.content.Context;
import android.os.Handler;
import android.telecom.Call;
import android.telecom.Connection;
@@ -37,17 +38,20 @@
private final Call call;
private final ImsVideoTech videoTech;
private final VideoTechListener listener;
+ private final Context context;
private int requestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
ImsVideoCallCallback(
final LoggingBindings logger,
final Call call,
ImsVideoTech videoTech,
- VideoTechListener listener) {
+ VideoTechListener listener,
+ Context context) {
this.logger = logger;
this.call = call;
this.videoTech = videoTech;
this.listener = listener;
+ this.context = context;
}
@Override
@@ -66,10 +70,16 @@
"ImsVideoTech.onSessionModifyRequestReceived", "call downgraded to %d", newVideoState);
} else if (previousVideoState != newVideoState) {
requestedVideoState = newVideoState;
- videoTech.setSessionModificationState(
- SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
- listener.onVideoUpgradeRequestReceived();
- logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_RECEIVED);
+ if (!wasVideoCall) {
+ videoTech.setSessionModificationState(
+ SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
+ listener.onVideoUpgradeRequestReceived();
+ logger.logImpression(DialerImpression.Type.IMS_VIDEO_REQUEST_RECEIVED);
+ } else {
+ LogUtil.i(
+ "ImsVideoTech.onSessionModifyRequestReceived", "call updated to %d", newVideoState);
+ videoTech.acceptVideoRequest(context);
+ }
}
}
diff --git a/java/com/android/incallui/videotech/ims/ImsVideoTech.java b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
index fec05dc..c12474d 100644
--- a/java/com/android/incallui/videotech/ims/ImsVideoTech.java
+++ b/java/com/android/incallui/videotech/ims/ImsVideoTech.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telecom.Call;
import android.telecom.Call.Details;
@@ -120,7 +121,7 @@
}
if (callback == null) {
- callback = new ImsVideoCallCallback(logger, call, this, listener);
+ callback = new ImsVideoCallCallback(logger, call, this, listener, context);
call.getVideoCall().registerCallback(callback);
}
@@ -165,7 +166,7 @@
}
@Override
- public void upgradeToVideo() {
+ public void upgradeToVideo(@NonNull Context context) {
LogUtil.enterBlock("ImsVideoTech.upgradeToVideo");
int unpausedVideoState = getUnpausedVideoState(call.getDetails().getVideoState());
@@ -177,7 +178,7 @@
}
@Override
- public void acceptVideoRequest() {
+ public void acceptVideoRequest(@NonNull Context context) {
int requestedVideoState = callback.getRequestedVideoState();
Assert.checkArgument(requestedVideoState != VideoProfile.STATE_AUDIO_ONLY);
LogUtil.i("ImsVideoTech.acceptUpgradeRequest", "videoState: " + requestedVideoState);
@@ -223,7 +224,7 @@
}
@Override
- public void resumeTransmission() {
+ public void resumeTransmission(@NonNull Context context) {
LogUtil.enterBlock("ImsVideoTech.resumeTransmission");
transmissionStopped = false;
diff --git a/java/com/android/incallui/videotech/lightbringer/LightbringerTech.java b/java/com/android/incallui/videotech/lightbringer/LightbringerTech.java
index 4882ba8..a807759 100644
--- a/java/com/android/incallui/videotech/lightbringer/LightbringerTech.java
+++ b/java/com/android/incallui/videotech/lightbringer/LightbringerTech.java
@@ -21,7 +21,6 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telecom.Call;
-import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.configprovider.ConfigProviderBindings;
@@ -55,7 +54,7 @@
@Override
public boolean isAvailable(Context context) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
LogUtil.v("LightbringerTech.isAvailable", "upgrade unavailable, only supported on O+");
return false;
}
@@ -71,11 +70,6 @@
return false;
}
- if (!TelecomManagerCompat.supportsHandover()) {
- LogUtil.v("LightbringerTech.isAvailable", "upgrade unavailable, telephony support missing");
- return false;
- }
-
if (!lightbringer.supportsUpgrade(context, callingNumber)) {
LogUtil.v("LightbringerTech.isAvailable", "upgrade unavailable, number does not support it");
return false;
@@ -125,13 +119,13 @@
}
@Override
- public void upgradeToVideo() {
+ public void upgradeToVideo(@NonNull Context context) {
listener.onImpressionLoggingNeeded(DialerImpression.Type.LIGHTBRINGER_UPGRADE_REQUESTED);
- lightbringer.requestUpgrade(call);
+ lightbringer.requestUpgrade(context, call);
}
@Override
- public void acceptVideoRequest() {
+ public void acceptVideoRequest(@NonNull Context context) {
throw Assert.createUnsupportedOperationFailException();
}
@@ -156,7 +150,7 @@
}
@Override
- public void resumeTransmission() {
+ public void resumeTransmission(@NonNull Context context) {
throw Assert.createUnsupportedOperationFailException();
}
diff --git a/java/com/android/voicemail/impl/ActivationTask.java b/java/com/android/voicemail/impl/ActivationTask.java
index d7a122c..29c91e0 100644
--- a/java/com/android/voicemail/impl/ActivationTask.java
+++ b/java/com/android/voicemail/impl/ActivationTask.java
@@ -170,7 +170,9 @@
if (VvmAccountManager.isAccountActivated(getContext(), phoneAccountHandle)) {
VvmLog.i(TAG, "Account is already activated");
- onSuccess(getContext(), phoneAccountHandle);
+ // The activated state might come from restored data, the filter still needs to be set up.
+ helper.activateSmsFilter();
+ onSuccess(getContext(), phoneAccountHandle, helper);
return;
}
helper.handleEvent(
@@ -230,7 +232,7 @@
+ message.getReturnCode());
if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
VvmLog.d(TAG, "subscriber ready, no activation required");
- updateSource(getContext(), phoneAccountHandle, message);
+ updateSource(getContext(), phoneAccountHandle, message, helper);
} else {
if (helper.supportsProvisioning()) {
VvmLog.i(TAG, "Subscriber not ready, start provisioning");
@@ -240,7 +242,7 @@
VvmLog.i(TAG, "Subscriber new but provisioning is not supported");
// Ignore the non-ready state and attempt to use the provided info as is.
// This is probably caused by not completing the new user tutorial.
- updateSource(getContext(), phoneAccountHandle, message);
+ updateSource(getContext(), phoneAccountHandle, message, helper);
} else {
VvmLog.i(TAG, "Subscriber not ready but provisioning is not supported");
helper.handleEvent(status, OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE);
@@ -251,20 +253,23 @@
}
private static void updateSource(
- Context context, PhoneAccountHandle phone, StatusMessage message) {
+ Context context,
+ PhoneAccountHandle phone,
+ StatusMessage message,
+ OmtpVvmCarrierConfigHelper config) {
if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
// Save the IMAP credentials in preferences so they are persistent and can be retrieved.
VvmAccountManager.addAccount(context, phone, message);
- onSuccess(context, phone);
+ onSuccess(context, phone, config);
} else {
VvmLog.e(TAG, "Visual voicemail not available for subscriber.");
}
}
- private static void onSuccess(Context context, PhoneAccountHandle phoneAccountHandle) {
- OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, phoneAccountHandle);
- helper.handleEvent(
+ private static void onSuccess(
+ Context context, PhoneAccountHandle phoneAccountHandle, OmtpVvmCarrierConfigHelper config) {
+ config.handleEvent(
VoicemailStatus.edit(context, phoneAccountHandle),
OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
clearLegacyVoicemailNotification(context, phoneAccountHandle);
diff --git a/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java b/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java
index 700e1cb..90303f5 100644
--- a/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java
+++ b/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java
@@ -50,6 +50,8 @@
* that may clutter CarrierConfigManager too much.
*
* <p>The current hidden configs are: {@link #getSslPort()} {@link #getDisabledCapabilities()}
+ *
+ * <p>TODO(twyen): refactor this to an interface.
*/
@TargetApi(VERSION_CODES.O)
public class OmtpVvmCarrierConfigHelper {
@@ -112,19 +114,19 @@
return;
}
- mCarrierConfig = getCarrierConfig(telephonyManager);
- mTelephonyConfig =
- new TelephonyVvmConfigManager(context).getConfig(telephonyManager.getSimOperator());
-
- mVvmType = getVvmType();
- mProtocol = VisualVoicemailProtocolFactory.create(mContext.getResources(), mVvmType);
-
if (ConfigOverrideFragment.isOverridden(context)) {
mOverrideConfig = ConfigOverrideFragment.getConfig(context);
VvmLog.w(TAG, "Config override is activated: " + mOverrideConfig);
} else {
mOverrideConfig = null;
}
+
+ mCarrierConfig = getCarrierConfig(telephonyManager);
+ mTelephonyConfig =
+ new TelephonyVvmConfigManager(context).getConfig(telephonyManager.getSimOperator());
+
+ mVvmType = getVvmType();
+ mProtocol = VisualVoicemailProtocolFactory.create(mContext.getResources(), mVvmType);
}
@VisibleForTesting
@@ -187,7 +189,11 @@
@Nullable
public Set<String> getCarrierVvmPackageNames() {
Assert.checkArgument(isValid());
- Set<String> names = getCarrierVvmPackageNames(mCarrierConfig);
+ Set<String> names = getCarrierVvmPackageNames(mOverrideConfig);
+ if (names != null) {
+ return names;
+ }
+ names = getCarrierVvmPackageNames(mCarrierConfig);
if (names != null) {
return names;
}
@@ -278,7 +284,12 @@
@Nullable
public Set<String> getDisabledCapabilities() {
Assert.checkArgument(isValid());
- Set<String> disabledCapabilities = getDisabledCapabilities(mCarrierConfig);
+ Set<String> disabledCapabilities;
+ disabledCapabilities = getDisabledCapabilities(mOverrideConfig);
+ if (disabledCapabilities != null) {
+ return disabledCapabilities;
+ }
+ disabledCapabilities = getDisabledCapabilities(mCarrierConfig);
if (disabledCapabilities != null) {
return disabledCapabilities;
}
diff --git a/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java b/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java
index 1624ce5..caf33df 100644
--- a/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java
+++ b/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java
@@ -49,7 +49,8 @@
* Any preference with key that starts with this prefix will be written to the dialer carrier
* config.
*/
- @VisibleForTesting static final String CONFIG_OVERRIDE_KEY_PREFIX = "vvm_config_override_key_";
+ @VisibleForTesting
+ public static final String CONFIG_OVERRIDE_KEY_PREFIX = "vvm_config_override_key_";
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
diff --git a/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java b/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java
index 9ce32a9..ae526d1 100644
--- a/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java
+++ b/java/com/android/voicemail/impl/settings/VisualVoicemailSettingsUtil.java
@@ -16,6 +16,7 @@
package com.android.voicemail.impl.settings;
import android.content.Context;
+import android.support.annotation.VisibleForTesting;
import android.telecom.PhoneAccountHandle;
import com.android.dialer.common.Assert;
import com.android.voicemail.VoicemailComponent;
@@ -28,7 +29,7 @@
/** Save whether or not a particular account is enabled in shared to be retrieved later. */
public class VisualVoicemailSettingsUtil {
- private static final String IS_ENABLED_KEY = "is_enabled";
+ @VisibleForTesting public static final String IS_ENABLED_KEY = "is_enabled";
public static void setEnabled(
Context context, PhoneAccountHandle phoneAccount, boolean isEnabled) {
diff --git a/java/com/android/voicemail/impl/settings/VoicemailSettingsFragment.java b/java/com/android/voicemail/impl/settings/VoicemailSettingsFragment.java
index 4860649..e2ea725 100644
--- a/java/com/android/voicemail/impl/settings/VoicemailSettingsFragment.java
+++ b/java/com/android/voicemail/impl/settings/VoicemailSettingsFragment.java
@@ -151,6 +151,8 @@
(PreferenceScreen) findPreference(getString(R.string.voicemail_advanced_settings_key));
Intent advancedSettingsIntent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
advancedSettingsIntent.putExtra(TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS, true);
+ advancedSettingsIntent.putExtra(
+ TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
advancedSettings.setIntent(advancedSettingsIntent);
voicemailChangePinPreference.setOnPreferenceClickListener(
new OnPreferenceClickListener() {
diff --git a/java/com/android/voicemail/impl/sms/LegacyModeSmsHandler.java b/java/com/android/voicemail/impl/sms/LegacyModeSmsHandler.java
index e902825..d55e3b5 100644
--- a/java/com/android/voicemail/impl/sms/LegacyModeSmsHandler.java
+++ b/java/com/android/voicemail/impl/sms/LegacyModeSmsHandler.java
@@ -101,6 +101,8 @@
Intent launchVoicemailSettingsIntent =
new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
launchVoicemailSettingsIntent.putExtra(TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS, true);
+ launchVoicemailSettingsIntent.putExtra(
+ TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
launchVoicemailSettingsPendingIntent =
PendingIntent.getActivity(
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
index 2ca16fb..b733928 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
@@ -49,6 +49,8 @@
private JobParameters jobParameters;
private TranscriptionClientFactory clientFactory;
private TranscriptionConfigProvider configProvider;
+ private TranscriptionTask activeTask;
+ private boolean stopped;
/** Callback used by a task to indicate it has finished processing its work item */
interface JobCallback {
@@ -134,8 +136,14 @@
@MainThread
public boolean onStopJob(JobParameters params) {
Assert.isMainThread();
- LogUtil.enterBlock("TranscriptionService.onStopJob");
- cleanup();
+ LogUtil.i("TranscriptionService.onStopJob", "params: " + params);
+ stopped = true;
+ Logger.get(this).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_JOB_STOPPED);
+ if (activeTask != null) {
+ LogUtil.i("TranscriptionService.onStopJob", "cancelling active task");
+ activeTask.cancel();
+ Logger.get(this).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_TASK_CANCELLED);
+ }
return true;
}
@@ -161,15 +169,20 @@
@MainThread
private boolean checkForWork() {
Assert.isMainThread();
+ if (stopped) {
+ LogUtil.i("TranscriptionService.checkForWork", "stopped");
+ return false;
+ }
JobWorkItem workItem = jobParameters.dequeueWork();
if (workItem != null) {
- TranscriptionTask task =
+ Assert.checkState(activeTask == null);
+ activeTask =
configProvider.shouldUseSyncApi()
? new TranscriptionTaskSync(
this, new Callback(), workItem, getClientFactory(), configProvider)
: new TranscriptionTaskAsync(
this, new Callback(), workItem, getClientFactory(), configProvider);
- getExecutorService().execute(task);
+ getExecutorService().execute(activeTask);
return true;
} else {
return false;
@@ -196,8 +209,13 @@
public void onWorkCompleted(JobWorkItem completedWorkItem) {
Assert.isMainThread();
LogUtil.i("TranscriptionService.Callback.onWorkCompleted", completedWorkItem.toString());
- jobParameters.completeWork(completedWorkItem);
- checkForWork();
+ activeTask = null;
+ if (stopped) {
+ LogUtil.i("TranscriptionService.Callback.onWorkCompleted", "stopped");
+ } else {
+ jobParameters.completeWork(completedWorkItem);
+ checkForWork();
+ }
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
index fbab076..60b97da 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
@@ -19,8 +19,10 @@
import android.app.job.JobWorkItem;
import android.content.Context;
import android.net.Uri;
+import android.support.annotation.MainThread;
import android.text.TextUtils;
import android.util.Pair;
+import com.android.dialer.common.Assert;
import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.compat.android.provider.VoicemailCompat;
import com.android.dialer.logging.DialerImpression;
@@ -64,6 +66,7 @@
protected final TranscriptionConfigProvider configProvider;
protected ByteString audioData;
protected AudioFormat encoding;
+ protected volatile boolean cancelled;
static final String AMR_PREFIX = "#!AMR\n";
@@ -87,6 +90,13 @@
databaseHelper = new TranscriptionDbHelper(context, voicemailUri);
}
+ @MainThread
+ void cancel() {
+ Assert.isMainThread();
+ VvmLog.i(TAG, "cancel");
+ cancelled = true;
+ }
+
@Override
public void run() {
VvmLog.i(TAG, "run");
@@ -144,7 +154,11 @@
.logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EXPIRED);
break;
default:
- updateTranscriptionAndState(transcript, VoicemailCompat.TRANSCRIPTION_FAILED);
+ updateTranscriptionAndState(
+ transcript,
+ cancelled
+ ? VoicemailCompat.TRANSCRIPTION_NOT_STARTED
+ : VoicemailCompat.TRANSCRIPTION_FAILED);
Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_EMPTY);
break;
}
@@ -155,6 +169,11 @@
VvmLog.i(TAG, "sendRequest");
TranscriptionClient client = clientFactory.getClient();
for (int i = 0; i < configProvider.getMaxTranscriptionRetries(); i++) {
+ if (cancelled) {
+ VvmLog.i(TAG, "sendRequest, cancelled");
+ return null;
+ }
+
VvmLog.i(TAG, "sendRequest, try: " + (i + 1));
if (i == 0) {
Logger.get(context).logImpression(getRequestSentImpression());
@@ -163,7 +182,10 @@
}
TranscriptionResponse response = request.getResponse(client);
- if (response.hasRecoverableError()) {
+ if (cancelled) {
+ VvmLog.i(TAG, "sendRequest, cancelled");
+ return null;
+ } else if (response.hasRecoverableError()) {
Logger.get(context)
.logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_RESPONSE_RECOVERABLE_ERROR);
backoff(i);
@@ -187,7 +209,7 @@
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
- VvmLog.w(TAG, "interrupted");
+ VvmLog.e(TAG, "interrupted", e);
Thread.currentThread().interrupt();
}
}
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
index 3c41aef..930d7f1 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
@@ -62,7 +62,10 @@
(TranscriptionResponseAsync)
sendRequest((client) -> client.sendUploadRequest(getUploadRequest()));
- if (uploadResponse == null) {
+ if (cancelled) {
+ VvmLog.i(TAG, "getTranscription, cancelled.");
+ return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
+ } else if (uploadResponse == null) {
VvmLog.i(TAG, "getTranscription, failed to upload voicemail.");
return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
} else {
@@ -87,10 +90,17 @@
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);
+ }
GetTranscriptResponseAsync response =
(GetTranscriptResponseAsync)
sendRequest((client) -> client.sendGetTranscriptRequest(request));
- if (response == null) {
+ 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));
diff --git a/java/com/android/voicemail/stub/StubVoicemailClient.java b/java/com/android/voicemail/stub/StubVoicemailClient.java
index 9929503..c2c7a6d 100644
--- a/java/com/android/voicemail/stub/StubVoicemailClient.java
+++ b/java/com/android/voicemail/stub/StubVoicemailClient.java
@@ -77,7 +77,9 @@
@Override
public Intent getSetPinIntent(Context context, PhoneAccountHandle phoneAccountHandle) {
- return new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
+ Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
+ intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ return intent;
}
@Override