summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java22
-rw-r--r--java/com/android/dialer/binary/aosp/AospDialerRootComponent.java2
-rw-r--r--java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java2
-rw-r--r--java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java2
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java2
-rw-r--r--java/com/android/dialer/notification/DialerNotificationManager.java10
-rw-r--r--java/com/android/dialer/precall/PreCallCoordinator.java7
-rw-r--r--java/com/android/dialer/precall/impl/CallingAccountSelector.java266
-rw-r--r--java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java25
-rw-r--r--java/com/android/dialer/precall/impl/PreCallModule.java5
-rw-r--r--java/com/android/dialer/precall/impl/res/values/strings.xml13
-rw-r--r--java/com/android/dialer/preferredsim/PreferredAccountUtil.java5
-rw-r--r--java/com/android/dialer/preferredsim/PreferredAccountWorker.java269
-rw-r--r--java/com/android/dialer/preferredsim/PreferredSimComponent.java37
-rw-r--r--java/com/android/dialer/preferredsim/PreferredSimModule.java29
-rw-r--r--java/com/android/dialer/preferredsim/impl/AndroidManifest.xml4
-rw-r--r--java/com/android/dialer/preferredsim/impl/PreferredAccountWorkerImpl.java435
-rw-r--r--java/com/android/dialer/preferredsim/impl/res/values/strings.xml33
-rw-r--r--java/com/android/dialer/speeddial/SpeedDialFragment.java37
-rw-r--r--java/com/android/dialer/speeddial/res/values/strings.xml3
-rw-r--r--java/com/android/incallui/ActiveCallsCallListListener.java4
-rw-r--r--java/com/android/incallui/InCallActivity.java84
-rw-r--r--java/com/android/incallui/InCallPresenter.java5
-rw-r--r--java/com/android/incallui/ReturnToCallController.java33
-rw-r--r--java/com/android/incallui/call/DialerCall.java34
-rw-r--r--java/com/android/incallui/callconfiguration/call_configuration.proto (renamed from java/com/android/incallui/call_configuration.proto)0
-rw-r--r--java/com/android/incallui/rtt/impl/RttChatAdapter.java5
-rw-r--r--java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java1
-rw-r--r--java/com/android/incallui/rtt/impl/res/values/strings.xml4
-rw-r--r--java/com/android/incallui/rtt/protocol/RttChatMessage.java (renamed from java/com/android/incallui/rtt/impl/RttChatMessage.java)39
30 files changed, 867 insertions, 550 deletions
diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
index f37d52d68..f142399d7 100644
--- a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
+++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
@@ -145,10 +145,10 @@ public final class LegacyVoicemailNotifier {
return phoneAccount.getShortDescription().toString();
}
}
- return String.format(
- context.getString(R.string.notification_voicemail_text_format),
- PhoneNumberHelper.formatNumber(
- context, voicemailNumber, GeoUtil.getCurrentCountryIso(context)));
+ return String.format(
+ context.getString(R.string.notification_voicemail_text_format),
+ PhoneNumberHelper.formatNumber(
+ context, voicemailNumber, GeoUtil.getCurrentCountryIso(context)));
}
public static void cancelNotification(
@@ -156,8 +156,18 @@ public final class LegacyVoicemailNotifier {
LogUtil.enterBlock("LegacyVoicemailNotifier.cancelNotification");
Assert.checkArgument(BuildCompat.isAtLeastO());
Assert.isNotNull(phoneAccountHandle);
- DialerNotificationManager.cancel(
- context, getNotificationTag(context, phoneAccountHandle), NOTIFICATION_ID);
+ if ("null".equals(phoneAccountHandle.getId())) {
+ // while PhoneAccountHandle itself will never be null, telephony may still construct a "null"
+ // handle if the SIM is no longer available. Usually both SIM will be removed at the sames
+ // time, so just clear all notifications.
+ LogUtil.i(
+ "LegacyVoicemailNotifier.cancelNotification",
+ "'null' id, canceling all legacy voicemail notifications");
+ DialerNotificationManager.cancelAll(context, NOTIFICATION_TAG);
+ } else {
+ DialerNotificationManager.cancel(
+ context, getNotificationTag(context, phoneAccountHandle), NOTIFICATION_ID);
+ }
}
@NonNull
diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
index e1021894f..6cbaf4fe7 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
@@ -33,6 +33,7 @@ import com.android.dialer.metrics.StubMetricsModule;
import com.android.dialer.phonelookup.PhoneLookupModule;
import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
import com.android.dialer.precall.impl.PreCallModule;
+import com.android.dialer.preferredsim.PreferredSimModule;
import com.android.dialer.preferredsim.suggestion.stub.StubSimSuggestionModule;
import com.android.dialer.simulator.impl.SimulatorModule;
import com.android.dialer.simulator.stub.StubSimulatorEnrichedCallModule;
@@ -60,6 +61,7 @@ import javax.inject.Singleton;
PhoneLookupModule.class,
PhoneNumberGeoUtilModule.class,
PreCallModule.class,
+ PreferredSimModule.class,
SharedPrefConfigProviderModule.class,
SimulatorModule.class,
StubSimulatorEnrichedCallModule.class,
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index 75ddaf7f0..5fed683f6 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -34,6 +34,7 @@ import com.android.dialer.phonelookup.PhoneLookupComponent;
import com.android.dialer.phonelookup.database.PhoneLookupDatabaseComponent;
import com.android.dialer.phonenumbergeoutil.PhoneNumberGeoUtilComponent;
import com.android.dialer.precall.PreCallComponent;
+import com.android.dialer.preferredsim.PreferredSimComponent;
import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
import com.android.dialer.simulator.SimulatorComponent;
import com.android.dialer.spam.SpamComponent;
@@ -72,6 +73,7 @@ public interface BaseDialerRootComponent
PhoneLookupDatabaseComponent.HasComponent,
PhoneNumberGeoUtilComponent.HasComponent,
PreCallComponent.HasComponent,
+ PreferredSimComponent.HasComponent,
UiItemLoaderComponent.HasComponent,
SimSuggestionComponent.HasComponent,
SimulatorComponent.HasComponent,
diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
index bdbdeb9dd..f4f7a0a3b 100644
--- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
+++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
@@ -33,6 +33,7 @@ import com.android.dialer.metrics.StubMetricsModule;
import com.android.dialer.phonelookup.PhoneLookupModule;
import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
import com.android.dialer.precall.impl.PreCallModule;
+import com.android.dialer.preferredsim.PreferredSimModule;
import com.android.dialer.preferredsim.suggestion.stub.StubSimSuggestionModule;
import com.android.dialer.simulator.impl.SimulatorModule;
import com.android.dialer.simulator.stub.StubSimulatorEnrichedCallModule;
@@ -65,6 +66,7 @@ import javax.inject.Singleton;
PhoneLookupModule.class, // TODO(zachh): Module which uses APDL?
PhoneNumberGeoUtilModule.class,
PreCallModule.class,
+ PreferredSimModule.class,
SharedPrefConfigProviderModule.class,
SimulatorModule.class,
StorageModule.class,
diff --git a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java
index 37b212a63..4de7af796 100644
--- a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java
@@ -156,6 +156,8 @@ public class CallDetailsEntryViewHolder extends ViewHolder {
rttTranscript.setClickable(false);
}
rttTranscript.setVisibility(View.VISIBLE);
+ } else {
+ rttTranscript.setVisibility(View.GONE);
}
}
diff --git a/java/com/android/dialer/notification/DialerNotificationManager.java b/java/com/android/dialer/notification/DialerNotificationManager.java
index 0e3420169..2a66cd5ac 100644
--- a/java/com/android/dialer/notification/DialerNotificationManager.java
+++ b/java/com/android/dialer/notification/DialerNotificationManager.java
@@ -83,6 +83,16 @@ public final class DialerNotificationManager {
notificationManager.cancel(tag, id);
}
+ public static void cancelAll(Context context, String prefix) {
+ NotificationManager notificationManager = getNotificationManager(context);
+ StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
+ for (StatusBarNotification notification : notifications) {
+ if (notification.getTag() != null && notification.getTag().startsWith(prefix)) {
+ notificationManager.cancel(notification.getTag(), notification.getId());
+ }
+ }
+ }
+
public static StatusBarNotification[] getActiveNotifications(@NonNull Context context) {
Assert.isNotNull(context);
return getNotificationManager(context).getActiveNotifications();
diff --git a/java/com/android/dialer/precall/PreCallCoordinator.java b/java/com/android/dialer/precall/PreCallCoordinator.java
index cb3221afd..9c24e0d69 100644
--- a/java/com/android/dialer/precall/PreCallCoordinator.java
+++ b/java/com/android/dialer/precall/PreCallCoordinator.java
@@ -21,6 +21,8 @@ import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.function.Consumer;
+import com.google.common.util.concurrent.ListenableFuture;
/**
* Runs {@link PreCallAction} one by one to prepare a {@link
@@ -65,4 +67,9 @@ public interface PreCallCoordinator {
@MainThread
@NonNull
PendingAction startPendingAction();
+
+ <Output> void listen(
+ ListenableFuture<Output> future,
+ Consumer<Output> successListener,
+ Consumer<Throwable> failureListener);
}
diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
index 8f63fa0db..b22e4d973 100644
--- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java
+++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
@@ -29,13 +29,9 @@ import android.telephony.PhoneNumberUtils;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions;
-import com.android.contacts.common.widget.SelectPhoneAccountDialogOptionsUtil;
-import com.android.dialer.activecalls.ActiveCallInfo;
-import com.android.dialer.activecalls.ActiveCallsComponent;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.configprovider.ConfigProviderBindings;
import com.android.dialer.logging.DialerImpression.Type;
import com.android.dialer.logging.Logger;
@@ -44,13 +40,10 @@ import com.android.dialer.precall.PreCallCoordinator;
import com.android.dialer.precall.PreCallCoordinator.PendingAction;
import com.android.dialer.preferredsim.PreferredAccountRecorder;
import com.android.dialer.preferredsim.PreferredAccountWorker;
-import com.android.dialer.preferredsim.PreferredAccountWorker.Result;
import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
import java.util.List;
-import java.util.Objects;
+import javax.inject.Inject;
/** PreCallAction to select which phone account to call with. Ignored if there's only one account */
@SuppressWarnings("MissingPermission")
@@ -62,6 +55,13 @@ public class CallingAccountSelector implements PreCallAction {
private boolean isDiscarding;
+ private final PreferredAccountWorker preferredAccountWorker;
+
+ @Inject
+ CallingAccountSelector(PreferredAccountWorker preferredAccountWorker) {
+ this.preferredAccountWorker = preferredAccountWorker;
+ }
+
@Override
public boolean requiresUi(Context context, CallIntentBuilder builder) {
if (!ConfigProviderBindings.get(context)
@@ -97,7 +97,13 @@ public class CallingAccountSelector implements PreCallAction {
}
switch (builder.getUri().getScheme()) {
case PhoneAccount.SCHEME_VOICEMAIL:
- showDialog(coordinator, coordinator.startPendingAction(), null, null, null);
+ showDialog(
+ coordinator,
+ coordinator.startPendingAction(),
+ preferredAccountWorker.getVoicemailDialogOptions(),
+ null,
+ null,
+ null);
Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_VOICEMAIL);
break;
case PhoneAccount.SCHEME_TEL:
@@ -120,224 +126,58 @@ public class CallingAccountSelector implements PreCallAction {
Activity activity = coordinator.getActivity();
String phoneNumber = builder.getUri().getSchemeSpecificPart();
PendingAction pendingAction = coordinator.startPendingAction();
- DialerExecutorComponent.get(coordinator.getActivity())
- .dialerExecutorFactory()
- .createNonUiTaskBuilder(new PreferredAccountWorker(phoneNumber))
- .onSuccess(
- (result -> {
- if (isDiscarding) {
- return;
- }
- if (result.getPhoneAccountHandle().isPresent()) {
- usePreferredAccount(coordinator, pendingAction, result);
- return;
- }
- PhoneAccountHandle defaultPhoneAccount =
- activity
- .getSystemService(TelecomManager.class)
- .getDefaultOutgoingPhoneAccount(builder.getUri().getScheme());
- if (defaultPhoneAccount != null) {
- useDefaultAccount(coordinator, pendingAction, result, defaultPhoneAccount);
- return;
- }
- if (result.getSuggestion().isPresent()) {
- LogUtil.i(
- "CallingAccountSelector.processPreferredAccount",
- "SIM suggested: " + result.getSuggestion().get().reason);
- if (result.getSuggestion().get().shouldAutoSelect) {
- useSuggestedAccount(coordinator, pendingAction, result);
- return;
- }
- }
- showDialog(
- coordinator,
- pendingAction,
- result.getDataId().orNull(),
- phoneNumber,
- result.getSuggestion().orNull());
- }))
- .build()
- .executeParallel(activity);
- }
- private void usePreferredAccount(
- PreCallCoordinator coordinator, PendingAction pendingAction, Result result) {
- String phoneNumber = coordinator.getBuilder().getUri().getSchemeSpecificPart();
- if (isSelectable(coordinator.getActivity(), result.getPhoneAccountHandle().get())) {
- Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED);
- coordinator.getBuilder().setPhoneAccountHandle(result.getPhoneAccountHandle().get());
- pendingAction.finish();
- } else {
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_NOT_SELECTABLE);
- LogUtil.i("CallingAccountSelector.usePreferredAccount", "preferred account not selectable");
- showDialog(
- coordinator,
- pendingAction,
- result.getDataId().orNull(),
- phoneNumber,
- result.getSuggestion().orNull());
- }
- }
-
- private void useDefaultAccount(
- PreCallCoordinator coordinator,
- PendingAction pendingAction,
- Result result,
- PhoneAccountHandle defaultPhoneAccount) {
- CallIntentBuilder builder = coordinator.getBuilder();
- String phoneNumber = builder.getUri().getSchemeSpecificPart();
- if (isSelectable(coordinator.getActivity(), defaultPhoneAccount)) {
- Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_GLOBAL_USED);
- builder.setPhoneAccountHandle(defaultPhoneAccount);
- pendingAction.finish();
- } else {
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_GLOBAL_NOT_SELECTABLE);
- LogUtil.i("CallingAccountSelector.useDefaultAccount", "default account not selectable");
- showDialog(
- coordinator,
- pendingAction,
- result.getDataId().orNull(),
- phoneNumber,
- result.getSuggestion().orNull());
- }
- }
-
- private void useSuggestedAccount(
- PreCallCoordinator coordinator, PendingAction pendingAction, Result result) {
- CallIntentBuilder builder = coordinator.getBuilder();
- String phoneNumber = builder.getUri().getSchemeSpecificPart();
- if (isSelectable(coordinator.getActivity(), result.getSuggestion().get().phoneAccountHandle)) {
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED);
- LogUtil.i("CallingAccountSelector.processPreferredAccount", "Auto selected suggestion");
- builder.setPhoneAccountHandle(result.getSuggestion().get().phoneAccountHandle);
- builder
- .getInCallUiIntentExtras()
- .putString(
- SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON,
- result.getSuggestion().get().reason.name());
- pendingAction.finish();
- } else {
- LogUtil.i("CallingAccountSelector.useSuggestedAccount", "suggested account not selectable");
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_NOT_SELECTABLE);
- showDialog(
- coordinator,
- pendingAction,
- result.getDataId().orNull(),
- phoneNumber,
- result.getSuggestion().orNull());
- }
- }
-
- /**
- * Most devices are DSDS (dual SIM dual standby) which only one SIM can have active calls at a
- * time. TODO(twyen): support other dual SIM modes when the API is exposed.
- */
- private static boolean isSelectable(Context context, PhoneAccountHandle phoneAccountHandle) {
- ImmutableList<ActiveCallInfo> activeCalls =
- ActiveCallsComponent.get(context).activeCalls().getActiveCalls();
- if (activeCalls.isEmpty()) {
- return true;
- }
- for (ActiveCallInfo activeCall : activeCalls) {
- if (Objects.equals(phoneAccountHandle, activeCall.phoneAccountHandle().orNull())) {
- return true;
- }
- }
- return false;
- }
-
- private static Optional<String> getActiveCallLabel(Context context) {
- ImmutableList<ActiveCallInfo> activeCalls =
- ActiveCallsComponent.get(context).activeCalls().getActiveCalls();
-
- if (activeCalls.isEmpty()) {
- LogUtil.e("CallingAccountSelector.getActiveCallLabel", "active calls no longer exist");
- return Optional.absent();
- }
- ActiveCallInfo activeCall = activeCalls.get(0);
- if (!activeCall.phoneAccountHandle().isPresent()) {
- LogUtil.e("CallingAccountSelector.getActiveCallLabel", "active call has no phone account");
- return Optional.absent();
- }
- PhoneAccount phoneAccount =
- context
- .getSystemService(TelecomManager.class)
- .getPhoneAccount(activeCall.phoneAccountHandle().get());
- if (phoneAccount == null) {
- LogUtil.e("CallingAccountSelector.getActiveCallLabel", "phone account not found");
- return Optional.absent();
- }
- return Optional.of(phoneAccount.getLabel().toString());
+ coordinator.listen(
+ preferredAccountWorker.selectAccount(
+ phoneNumber,
+ activity.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()),
+ result -> {
+ if (result.getSelectedPhoneAccountHandle().isPresent()) {
+
+ if (result.getSuggestion().isPresent()
+ && result
+ .getSelectedPhoneAccountHandle()
+ .get()
+ .equals(result.getSuggestion().get().phoneAccountHandle)) {
+ builder
+ .getInCallUiIntentExtras()
+ .putString(
+ SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON,
+ result.getSuggestion().get().reason.name());
+ }
+
+ coordinator
+ .getBuilder()
+ .setPhoneAccountHandle(result.getSelectedPhoneAccountHandle().get());
+ pendingAction.finish();
+ return;
+ }
+ showDialog(
+ coordinator,
+ pendingAction,
+ result.getDialogOptionsBuilder().get().build(),
+ result.getDataId().orNull(),
+ phoneNumber,
+ result.getSuggestion().orNull());
+ },
+ (throwable) -> {
+ throw new RuntimeException(throwable);
+ });
}
@MainThread
private void showDialog(
PreCallCoordinator coordinator,
PendingAction pendingAction,
+ SelectPhoneAccountDialogOptions dialogOptions,
@Nullable String dataId,
@Nullable String number,
@Nullable Suggestion suggestion) {
Assert.isMainThread();
- Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_SHOWN);
- if (dataId != null) {
- Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_IN_CONTACTS);
- }
- if (suggestion != null) {
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AVAILABLE);
- switch (suggestion.reason) {
- case INTRA_CARRIER:
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTED_CARRIER);
- break;
- case FREQUENT:
- Logger.get(coordinator.getActivity())
- .logImpression(Type.DUAL_SIM_SELECTION_SUGGESTED_FREQUENCY);
- break;
- default:
- }
- }
- SelectPhoneAccountDialogOptions.Builder optionsBuilder =
- SelectPhoneAccountDialogOptions.newBuilder()
- .setTitle(R.string.pre_call_select_phone_account)
- .setCanSetDefault(dataId != null)
- .setSetDefaultLabel(R.string.pre_call_select_phone_account_remember);
- for (PhoneAccountHandle phoneAccountHandle :
- coordinator
- .getActivity()
- .getSystemService(TelecomManager.class)
- .getCallCapablePhoneAccounts()) {
- SelectPhoneAccountDialogOptions.Entry.Builder entryBuilder =
- SelectPhoneAccountDialogOptions.Entry.newBuilder();
- SelectPhoneAccountDialogOptionsUtil.setPhoneAccountHandle(entryBuilder, phoneAccountHandle);
- if (isSelectable(coordinator.getActivity(), phoneAccountHandle)) {
- Optional<String> hint =
- SuggestionProvider.getHint(coordinator.getActivity(), phoneAccountHandle, suggestion);
- if (hint.isPresent()) {
- entryBuilder.setHint(hint.get());
- }
- } else {
- entryBuilder.setEnabled(false);
- Optional<String> activeCallLabel = getActiveCallLabel(coordinator.getActivity());
- if (activeCallLabel.isPresent()) {
- entryBuilder.setHint(
- coordinator
- .getActivity()
- .getString(
- R.string.pre_call_select_phone_account_hint_other_sim_in_use,
- activeCallLabel.get()));
- }
- }
- optionsBuilder.addEntries(entryBuilder);
- }
selectPhoneAccountDialogFragment =
SelectPhoneAccountDialogFragment.newInstance(
- optionsBuilder.build(),
+ dialogOptions,
new SelectedListener(
coordinator,
pendingAction,
diff --git a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java
index 36af62245..f2ff0e3e3 100644
--- a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java
+++ b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java
@@ -24,6 +24,9 @@ import android.support.annotation.Nullable;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.common.concurrent.UiListener;
+import com.android.dialer.function.Consumer;
import com.android.dialer.logging.DialerImpression.Type;
import com.android.dialer.logging.Logger;
import com.android.dialer.precall.PreCallAction;
@@ -31,6 +34,9 @@ import com.android.dialer.precall.PreCallComponent;
import com.android.dialer.precall.PreCallCoordinator;
import com.android.dialer.telecom.TelecomUtil;
import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
/**
* Implements {@link PreCallCoordinator}. Listens to the life cycle of {@link PreCallActivity} to
@@ -49,6 +55,8 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator {
private PendingAction pendingAction;
private boolean aborted = false;
+ private UiListener<Object> uiListener;
+
PreCallCoordinatorImpl(@NonNull Activity activity) {
this.activity = Assert.isNotNull(activity);
}
@@ -61,6 +69,9 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator {
} else {
builder = Assert.isNotNull(intent.getParcelableExtra(EXTRA_CALL_INTENT_BUILDER));
}
+ uiListener =
+ DialerExecutorComponent.get(activity)
+ .createUiListener(activity.getFragmentManager(), "PreCallCoordinatorImpl.uiListener");
}
void onRestoreInstanceState(Bundle savedInstanceState) {
@@ -152,4 +163,18 @@ public class PreCallCoordinatorImpl implements PreCallCoordinator {
onActionFinished();
}
}
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <OutputT> void listen(
+ ListenableFuture<OutputT> future,
+ Consumer<OutputT> successListener,
+ Consumer<Throwable> failureListener) {
+
+ uiListener.listen(
+ activity,
+ Futures.transform(future, (output) -> (Object) output, MoreExecutors.directExecutor()),
+ output -> successListener.accept((OutputT) output),
+ failureListener::accept);
+ }
}
diff --git a/java/com/android/dialer/precall/impl/PreCallModule.java b/java/com/android/dialer/precall/impl/PreCallModule.java
index 4643b1976..9820e2b66 100644
--- a/java/com/android/dialer/precall/impl/PreCallModule.java
+++ b/java/com/android/dialer/precall/impl/PreCallModule.java
@@ -36,12 +36,13 @@ public abstract class PreCallModule {
@Provides
@Singleton
- public static ImmutableList<PreCallAction> provideActions() {
+ public static ImmutableList<PreCallAction> provideActions(
+ CallingAccountSelector callingAccountSelector) {
return ImmutableList.of(
new PermissionCheckAction(),
new MalformedNumberRectifier(
ImmutableList.of(new UkRegionPrefixInInternationalFormatHandler())),
- new CallingAccountSelector(),
+ callingAccountSelector,
new AssistedDialAction());
}
}
diff --git a/java/com/android/dialer/precall/impl/res/values/strings.xml b/java/com/android/dialer/precall/impl/res/values/strings.xml
index 5e7ddd36c..a920f0720 100644
--- a/java/com/android/dialer/precall/impl/res/values/strings.xml
+++ b/java/com/android/dialer/precall/impl/res/values/strings.xml
@@ -17,17 +17,4 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Toast when the user tried to place a call but has revoked phone permission [CHAR_LIMIT=none] -->
<string name="pre_call_permission_check_no_phone_permission">Cannot make call without phone permission</string>
-
- <!-- Title of the dialog to select which SIM to call with before making a call, if the device has
- multiple SIMs [CHAR LIMIT=40]-->
- <string name="pre_call_select_phone_account">Choose SIM for this call</string>
-
- <!-- Checkbox label when selecting a SIM when calling a contact, to use the selected SIM for the
- same contact and never ask again [CHAR LIMIT=40]-->
- <string name="pre_call_select_phone_account_remember">Remember this choice</string>
-
- <!-- Hint to show under a SIM entry when selecting SIM for call on a multi-SIM device, when the
- call cannot be placed with the SIM because there is already a call on the other SIM,
- [CHAR LIMIT=NONE]-->
- <string name="pre_call_select_phone_account_hint_other_sim_in_use">Not available while using <xliff:g example="SIM 1" id="other_sim">%1$s</xliff:g></string>
</resources> \ No newline at end of file
diff --git a/java/com/android/dialer/preferredsim/PreferredAccountUtil.java b/java/com/android/dialer/preferredsim/PreferredAccountUtil.java
index 1cfdbb161..b546dc0b8 100644
--- a/java/com/android/dialer/preferredsim/PreferredAccountUtil.java
+++ b/java/com/android/dialer/preferredsim/PreferredAccountUtil.java
@@ -30,7 +30,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.dialer.common.LogUtil;
-import com.android.dialer.configprovider.ConfigProviderComponent;
+import com.android.dialer.configprovider.ConfigProviderBindings;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
@@ -102,8 +102,7 @@ public class PreferredAccountUtil {
*/
public static ImmutableSet<String> getValidAccountTypes(Context context) {
return ImmutableSet.copyOf(
- ConfigProviderComponent.get(context)
- .getConfigProvider()
+ ConfigProviderBindings.get(context)
.getString(
"preferred_sim_valid_account_types",
"com.google;"
diff --git a/java/com/android/dialer/preferredsim/PreferredAccountWorker.java b/java/com/android/dialer/preferredsim/PreferredAccountWorker.java
index df743c342..965d08843 100644
--- a/java/com/android/dialer/preferredsim/PreferredAccountWorker.java
+++ b/java/com/android/dialer/preferredsim/PreferredAccountWorker.java
@@ -16,47 +16,35 @@
package com.android.dialer.preferredsim;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.QuickContact;
-import android.provider.ContactsContract.RawContacts;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
import android.telecom.PhoneAccountHandle;
-import android.text.TextUtils;
-import com.android.dialer.common.Assert;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.DialerExecutor.Worker;
-import com.android.dialer.configprovider.ConfigProviderBindings;
-import com.android.dialer.preferredsim.PreferredAccountWorker.Result;
-import com.android.dialer.preferredsim.PreferredSimFallbackContract.PreferredSim;
-import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
+import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions;
import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion;
-import com.android.dialer.util.PermissionsUtil;
import com.google.auto.value.AutoValue;
import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.List;
/** Query a preferred SIM to make a call with. */
-public class PreferredAccountWorker implements Worker<Context, Result> {
+@SuppressWarnings({"missingPermission", "Guava"})
+public interface PreferredAccountWorker {
- /** The result of the worker. */
+ /** Result of the query. */
@AutoValue
- public abstract static class Result {
+ abstract class Result {
- /** The preferred phone account for the number. Absent if not set or invalid. */
- public abstract Optional<PhoneAccountHandle> getPhoneAccountHandle();
+ /**
+ * The phone account to dial with for the number. Absent if no account can be auto selected. If
+ * absent, {@link #getSelectedPhoneAccountHandle()} will be present to show a dialog for the
+ * user to manually select.
+ */
+ public abstract Optional<PhoneAccountHandle> getSelectedPhoneAccountHandle();
+
+ /**
+ * The {@link SelectPhoneAccountDialogOptions} that should be used to show the selection dialog.
+ * Absent if {@link #getSelectedPhoneAccountHandle()} is present, which should be used directly
+ * instead of asking the user.
+ */
+ public abstract Optional<SelectPhoneAccountDialogOptions.Builder> getDialogOptionsBuilder();
/**
* {@link android.provider.ContactsContract.Data#_ID} of the row matching the number. If the
@@ -66,207 +54,42 @@ public class PreferredAccountWorker implements Worker<Context, Result> {
public abstract Optional<Suggestion> getSuggestion();
- static Builder builder() {
- return new AutoValue_PreferredAccountWorker_Result.Builder();
+ public static Builder builder(PhoneAccountHandle selectedPhoneAccountHandle) {
+ return new AutoValue_PreferredAccountWorker_Result.Builder()
+ .setSelectedPhoneAccountHandle(selectedPhoneAccountHandle);
}
- @AutoValue.Builder
- abstract static class Builder {
-
- public abstract Builder setPhoneAccountHandle(
- Optional<PhoneAccountHandle> phoneAccountHandle);
-
- public abstract Builder setDataId(Optional<String> dataId);
-
- public abstract Builder setSuggestion(Optional<Suggestion> suggestion);
-
- public abstract Result build();
+ public static Builder builder(SelectPhoneAccountDialogOptions.Builder optionsBuilder) {
+ return new AutoValue_PreferredAccountWorker_Result.Builder()
+ .setDialogOptionsBuilder(optionsBuilder);
}
- }
-
- @VisibleForTesting
- public static final String METADATA_SUPPORTS_PREFERRED_SIM =
- "supports_per_number_preferred_account";
- private final String phoneNumber;
-
- public PreferredAccountWorker(String phoneNumber) {
- this.phoneNumber = phoneNumber;
- }
-
- @NonNull
- @Override
- @WorkerThread
- public Result doInBackground(Context context) throws Throwable {
- Result.Builder resultBuilder = Result.builder();
- if (!isPreferredSimEnabled(context)) {
- return resultBuilder.build();
- }
- if (!PermissionsUtil.hasContactsReadPermissions(context)) {
- LogUtil.i("PreferredAccountWorker.doInBackground", "missing READ_CONTACTS permission");
- return resultBuilder.build();
- }
- Optional<String> dataId = getDataId(context, phoneNumber);
- Optional<PhoneAccountHandle> phoneAccountHandle = Optional.absent();
- if (dataId.isPresent()) {
- resultBuilder.setDataId(dataId);
- phoneAccountHandle = getPreferredAccount(context, dataId.get());
- }
- resultBuilder.setPhoneAccountHandle(phoneAccountHandle);
- Optional<Suggestion> suggestion = Optional.absent();
- if (!phoneAccountHandle.isPresent()) {
- suggestion =
- SimSuggestionComponent.get(context)
- .getSuggestionProvider()
- .getSuggestion(context, phoneNumber);
- resultBuilder.setSuggestion(suggestion);
- }
- return resultBuilder.build();
- }
-
- @WorkerThread
- @NonNull
- private static Optional<String> getDataId(
- @NonNull Context context, @Nullable String phoneNumber) {
- Assert.isWorkerThread();
- if (TextUtils.isEmpty(phoneNumber)) {
- return Optional.absent();
- }
- try (Cursor cursor =
- context
- .getContentResolver()
- .query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)),
- new String[] {PhoneLookup.DATA_ID},
- null,
- null,
- null)) {
- if (cursor == null) {
- return Optional.absent();
- }
- ImmutableSet<String> validAccountTypes = PreferredAccountUtil.getValidAccountTypes(context);
- String result = null;
- while (cursor.moveToNext()) {
- Optional<String> accountType =
- getAccountType(context.getContentResolver(), cursor.getLong(0));
- if (accountType.isPresent() && !validAccountTypes.contains(accountType.get())) {
- // Empty accountType is treated as writable
- LogUtil.i("CallingAccountSelector.getDataId", "ignoring non-writable " + accountType);
- continue;
- }
- if (result != null && !result.equals(cursor.getString(0))) {
- // TODO(twyen): if there are multiple entries attempt to grab from the contact that
- // initiated the call.
- LogUtil.i("CallingAccountSelector.getDataId", "lookup result not unique, ignoring");
- return Optional.absent();
- }
- result = cursor.getString(0);
- }
- return Optional.fromNullable(result);
- }
- }
+ /** For implementations of {@link PreferredAccountWorker} only. */
+ @AutoValue.Builder
+ public abstract static class Builder {
- @WorkerThread
- private static Optional<String> getAccountType(ContentResolver contentResolver, long dataId) {
- Assert.isWorkerThread();
- Optional<Long> rawContactId = getRawContactId(contentResolver, dataId);
- if (!rawContactId.isPresent()) {
- return Optional.absent();
- }
- try (Cursor cursor =
- contentResolver.query(
- ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId.get()),
- new String[] {RawContacts.ACCOUNT_TYPE},
- null,
- null,
- null)) {
- if (cursor == null || !cursor.moveToFirst()) {
- return Optional.absent();
- }
- return Optional.fromNullable(cursor.getString(0));
- }
- }
+ abstract Builder setSelectedPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle);
- @WorkerThread
- private static Optional<Long> getRawContactId(ContentResolver contentResolver, long dataId) {
- Assert.isWorkerThread();
- try (Cursor cursor =
- contentResolver.query(
- ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
- new String[] {Data.RAW_CONTACT_ID},
- null,
- null,
- null)) {
- if (cursor == null || !cursor.moveToFirst()) {
- return Optional.absent();
- }
- return Optional.of(cursor.getLong(0));
- }
- }
+ public abstract Builder setDataId(String dataId);
- @WorkerThread
- @NonNull
- private static Optional<PhoneAccountHandle> getPreferredAccount(
- @NonNull Context context, @NonNull String dataId) {
- Assert.isWorkerThread();
- Assert.isNotNull(dataId);
- try (Cursor cursor =
- context
- .getContentResolver()
- .query(
- PreferredSimFallbackContract.CONTENT_URI,
- new String[] {
- PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
- PreferredSim.PREFERRED_PHONE_ACCOUNT_ID
- },
- PreferredSim.DATA_ID + " = ?",
- new String[] {dataId},
- null)) {
- if (cursor == null) {
- return Optional.absent();
- }
- if (!cursor.moveToFirst()) {
- return Optional.absent();
- }
- return PreferredAccountUtil.getValidPhoneAccount(
- context, cursor.getString(0), cursor.getString(1));
- }
- }
+ abstract Builder setDialogOptionsBuilder(
+ SelectPhoneAccountDialogOptions.Builder optionsBuilder);
- @WorkerThread
- private static boolean isPreferredSimEnabled(Context context) {
- Assert.isWorkerThread();
- if (!ConfigProviderBindings.get(context).getBoolean("preferred_sim_enabled", true)) {
- return false;
- }
+ public abstract Builder setSuggestion(Suggestion suggestion);
- Intent quickContactIntent = getQuickContactIntent();
- ResolveInfo resolveInfo =
- context
- .getPackageManager()
- .resolveActivity(quickContactIntent, PackageManager.GET_META_DATA);
- if (resolveInfo == null
- || resolveInfo.activityInfo == null
- || resolveInfo.activityInfo.applicationInfo == null
- || resolveInfo.activityInfo.applicationInfo.metaData == null) {
- LogUtil.e("CallingAccountSelector.isPreferredSimEnabled", "cannot resolve quick contact app");
- return false;
- }
- if (!resolveInfo.activityInfo.applicationInfo.metaData.getBoolean(
- METADATA_SUPPORTS_PREFERRED_SIM, false)) {
- LogUtil.i(
- "CallingAccountSelector.isPreferredSimEnabled",
- "system contacts does not support preferred SIM");
- return false;
+ public abstract Result build();
}
- return true;
}
- @VisibleForTesting
- public static Intent getQuickContactIntent() {
- Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setData(Contacts.CONTENT_URI.buildUpon().appendPath("1").build());
- return intent;
- }
+ /**
+ * @return a {@link SelectPhoneAccountDialogOptions} for a dialog to select SIM for voicemail call
+ */
+ SelectPhoneAccountDialogOptions getVoicemailDialogOptions();
+
+ /**
+ * Return {@link Result} for the best {@link PhoneAccountHandle} among {@code candidates} to call
+ * the number with. If none are eligible, a {@link SelectPhoneAccountDialogOptions} will be
+ * provided to show a dialog for the user to manually select.
+ */
+ ListenableFuture<Result> selectAccount(String phoneNumber, List<PhoneAccountHandle> candidates);
}
diff --git a/java/com/android/dialer/preferredsim/PreferredSimComponent.java b/java/com/android/dialer/preferredsim/PreferredSimComponent.java
new file mode 100644
index 000000000..b8d7fa199
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/PreferredSimComponent.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 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.preferredsim;
+
+import android.content.Context;
+import com.android.dialer.inject.HasRootComponent;
+import dagger.Subcomponent;
+
+/** Component for preferred SIM */
+@Subcomponent
+public abstract class PreferredSimComponent {
+ public abstract PreferredAccountWorker preferredAccountWorker();
+
+ public static PreferredSimComponent get(Context context) {
+ return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
+ .preferredSimComponent();
+ }
+
+ /** Used to refer to the root application component. */
+ public interface HasComponent {
+ PreferredSimComponent preferredSimComponent();
+ }
+}
diff --git a/java/com/android/dialer/preferredsim/PreferredSimModule.java b/java/com/android/dialer/preferredsim/PreferredSimModule.java
new file mode 100644
index 000000000..bdc6a5a13
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/PreferredSimModule.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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.preferredsim;
+
+import com.android.dialer.preferredsim.impl.PreferredAccountWorkerImpl;
+import dagger.Binds;
+import dagger.Module;
+
+/** Module for preferred SIM */
+@Module
+public abstract class PreferredSimModule {
+
+ @Binds
+ public abstract PreferredAccountWorker to(PreferredAccountWorkerImpl impl);
+}
diff --git a/java/com/android/dialer/preferredsim/impl/AndroidManifest.xml b/java/com/android/dialer/preferredsim/impl/AndroidManifest.xml
index e21598fc3..e6f932ef0 100644
--- a/java/com/android/dialer/preferredsim/impl/AndroidManifest.xml
+++ b/java/com/android/dialer/preferredsim/impl/AndroidManifest.xml
@@ -14,7 +14,7 @@
~ limitations under the License
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.dialer.preferredsim">
+ package="com.android.dialer.preferredsim.impl">
<application>
@@ -22,7 +22,7 @@
android:authorities="com.android.dialer.preferredsimfallback"
android:exported="true"
android:multiprocess="false"
- android:name=".impl.PreferredSimFallbackProvider"
+ android:name=".PreferredSimFallbackProvider"
android:readPermission="android.permission.READ_CONTACTS"
android:writePermission="android.permission.WRITE_CONTACTS"/>
diff --git a/java/com/android/dialer/preferredsim/impl/PreferredAccountWorkerImpl.java b/java/com/android/dialer/preferredsim/impl/PreferredAccountWorkerImpl.java
new file mode 100644
index 000000000..086cd7a10
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/impl/PreferredAccountWorkerImpl.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2018 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.preferredsim.impl;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions;
+import com.android.contacts.common.widget.SelectPhoneAccountDialogOptionsUtil;
+import com.android.dialer.activecalls.ActiveCallInfo;
+import com.android.dialer.activecalls.ActiveCallsComponent;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.configprovider.ConfigProviderBindings;
+import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.logging.DialerImpression.Type;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.preferredsim.PreferredAccountUtil;
+import com.android.dialer.preferredsim.PreferredAccountWorker;
+import com.android.dialer.preferredsim.PreferredAccountWorker.Result.Builder;
+import com.android.dialer.preferredsim.PreferredSimFallbackContract;
+import com.android.dialer.preferredsim.PreferredSimFallbackContract.PreferredSim;
+import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
+import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
+import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion;
+import com.android.dialer.util.PermissionsUtil;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import java.util.List;
+import java.util.Objects;
+import javax.inject.Inject;
+
+/** Implements {@link PreferredAccountWorker}. */
+@SuppressWarnings({"missingPermission", "Guava"})
+public class PreferredAccountWorkerImpl implements PreferredAccountWorker {
+
+ private final Context appContext;
+ private final ListeningExecutorService backgroundExecutor;
+
+ @VisibleForTesting
+ public static final String METADATA_SUPPORTS_PREFERRED_SIM =
+ "supports_per_number_preferred_account";
+
+ @Inject
+ public PreferredAccountWorkerImpl(
+ @ApplicationContext Context appContext,
+ @BackgroundExecutor ListeningExecutorService backgroundExecutor) {
+ this.appContext = appContext;
+ this.backgroundExecutor = backgroundExecutor;
+ }
+
+ @Override
+ public SelectPhoneAccountDialogOptions getVoicemailDialogOptions() {
+ return SelectPhoneAccountDialogOptionsUtil.builderWithAccounts(
+ appContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts())
+ .setTitle(R.string.pre_call_select_phone_account)
+ .setCanSetDefault(false)
+ .build();
+ }
+
+ @Override
+ public ListenableFuture<Result> selectAccount(
+ String phoneNumber, List<PhoneAccountHandle> candidates) {
+ return backgroundExecutor.submit(() -> doInBackground(phoneNumber, candidates));
+ }
+
+ private Result doInBackground(String phoneNumber, List<PhoneAccountHandle> candidates) {
+
+ Optional<String> dataId = getDataId(phoneNumber);
+ if (dataId.isPresent()) {
+ Optional<PhoneAccountHandle> preferred = getPreferredAccount(appContext, dataId.get());
+ if (preferred.isPresent()) {
+ return usePreferredSim(preferred.get(), candidates, dataId.get());
+ }
+ }
+
+ PhoneAccountHandle defaultPhoneAccount =
+ appContext
+ .getSystemService(TelecomManager.class)
+ .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
+ if (defaultPhoneAccount != null) {
+ return useDefaultSim(defaultPhoneAccount, candidates, dataId.orNull());
+ }
+
+ Optional<Suggestion> suggestion =
+ SimSuggestionComponent.get(appContext)
+ .getSuggestionProvider()
+ .getSuggestion(appContext, phoneNumber);
+ if (suggestion.isPresent() && suggestion.get().shouldAutoSelect) {
+ return useSuggestedSim(suggestion.get(), candidates, dataId.orNull());
+ }
+
+ Builder resultBuilder =
+ Result.builder(
+ createDialogOptionsBuilder(candidates, dataId.orNull(), suggestion.orNull()));
+ if (suggestion.isPresent()) {
+ resultBuilder.setSuggestion(suggestion.get());
+ }
+ if (dataId.isPresent()) {
+ resultBuilder.setDataId(dataId.get());
+ }
+ return resultBuilder.build();
+ }
+
+ private Result usePreferredSim(
+ PhoneAccountHandle preferred, List<PhoneAccountHandle> candidates, String dataId) {
+ Builder resultBuilder;
+ if (isSelectable(preferred)) {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED);
+ resultBuilder = Result.builder(preferred);
+ } else {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_NOT_SELECTABLE);
+ LogUtil.i("CallingAccountSelector.usePreferredAccount", "preferred account not selectable");
+ resultBuilder = Result.builder(createDialogOptionsBuilder(candidates, dataId, null));
+ }
+ resultBuilder.setDataId(dataId);
+ return resultBuilder.build();
+ }
+
+ private Result useDefaultSim(
+ PhoneAccountHandle defaultPhoneAccount,
+ List<PhoneAccountHandle> candidates,
+ @Nullable String dataId) {
+ if (isSelectable(defaultPhoneAccount)) {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_GLOBAL_USED);
+ return Result.builder(defaultPhoneAccount).build();
+ } else {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_GLOBAL_NOT_SELECTABLE);
+ LogUtil.i("CallingAccountSelector.usePreferredAccount", "global account not selectable");
+ return Result.builder(createDialogOptionsBuilder(candidates, dataId, null)).build();
+ }
+ }
+
+ private Result useSuggestedSim(
+ Suggestion suggestion, List<PhoneAccountHandle> candidates, @Nullable String dataId) {
+ Builder resultBuilder;
+ PhoneAccountHandle suggestedPhoneAccount = suggestion.phoneAccountHandle;
+ if (isSelectable(suggestedPhoneAccount)) {
+ resultBuilder = Result.builder(suggestedPhoneAccount);
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED);
+ } else {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_NOT_SELECTABLE);
+ LogUtil.i("CallingAccountSelector.usePreferredAccount", "global account not selectable");
+ resultBuilder = Result.builder(createDialogOptionsBuilder(candidates, dataId, suggestion));
+ return resultBuilder.build();
+ }
+ resultBuilder.setSuggestion(suggestion);
+ return resultBuilder.build();
+ }
+
+ SelectPhoneAccountDialogOptions.Builder createDialogOptionsBuilder(
+ List<PhoneAccountHandle> candidates,
+ @Nullable String dataId,
+ @Nullable Suggestion suggestion) {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_SHOWN);
+ if (dataId != null) {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_IN_CONTACTS);
+ }
+ if (suggestion != null) {
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AVAILABLE);
+ switch (suggestion.reason) {
+ case INTRA_CARRIER:
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTED_CARRIER);
+ break;
+ case FREQUENT:
+ Logger.get(appContext).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTED_FREQUENCY);
+ break;
+ default:
+ }
+ }
+ SelectPhoneAccountDialogOptions.Builder optionsBuilder =
+ SelectPhoneAccountDialogOptions.newBuilder()
+ .setTitle(R.string.pre_call_select_phone_account)
+ .setCanSetDefault(dataId != null)
+ .setSetDefaultLabel(R.string.pre_call_select_phone_account_remember);
+
+ for (PhoneAccountHandle phoneAccountHandle : candidates) {
+ SelectPhoneAccountDialogOptions.Entry.Builder entryBuilder =
+ SelectPhoneAccountDialogOptions.Entry.newBuilder();
+ SelectPhoneAccountDialogOptionsUtil.setPhoneAccountHandle(entryBuilder, phoneAccountHandle);
+ if (isSelectable(phoneAccountHandle)) {
+ Optional<String> hint =
+ SuggestionProvider.getHint(appContext, phoneAccountHandle, suggestion);
+ if (hint.isPresent()) {
+ entryBuilder.setHint(hint.get());
+ }
+ } else {
+ entryBuilder.setEnabled(false);
+ Optional<String> activeCallLabel = getActiveCallLabel();
+ if (activeCallLabel.isPresent()) {
+ entryBuilder.setHint(
+ appContext.getString(
+ R.string.pre_call_select_phone_account_hint_other_sim_in_use,
+ activeCallLabel.get()));
+ }
+ }
+ optionsBuilder.addEntries(entryBuilder);
+ }
+
+ return optionsBuilder;
+ }
+
+ @WorkerThread
+ @NonNull
+ private Optional<String> getDataId(@Nullable String phoneNumber) {
+ Assert.isWorkerThread();
+
+ if (!isPreferredSimEnabled(appContext)) {
+ return Optional.absent();
+ }
+ if (!PermissionsUtil.hasContactsReadPermissions(appContext)) {
+ LogUtil.i("PreferredAccountWorker.doInBackground", "missing READ_CONTACTS permission");
+ return Optional.absent();
+ }
+
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return Optional.absent();
+ }
+ try (Cursor cursor =
+ appContext
+ .getContentResolver()
+ .query(
+ Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)),
+ new String[] {PhoneLookup.DATA_ID},
+ null,
+ null,
+ null)) {
+ if (cursor == null) {
+ return Optional.absent();
+ }
+ ImmutableSet<String> validAccountTypes =
+ PreferredAccountUtil.getValidAccountTypes(appContext);
+ String result = null;
+ while (cursor.moveToNext()) {
+ Optional<String> accountType =
+ getAccountType(appContext.getContentResolver(), cursor.getLong(0));
+ if (accountType.isPresent() && !validAccountTypes.contains(accountType.get())) {
+ // Empty accountType is treated as writable
+ LogUtil.i("CallingAccountSelector.getDataId", "ignoring non-writable " + accountType);
+ continue;
+ }
+ if (result != null && !result.equals(cursor.getString(0))) {
+ // TODO(twyen): if there are multiple entries attempt to grab from the contact that
+ // initiated the call.
+ LogUtil.i("CallingAccountSelector.getDataId", "lookup result not unique, ignoring");
+ return Optional.absent();
+ }
+ result = cursor.getString(0);
+ }
+ return Optional.fromNullable(result);
+ }
+ }
+
+ @WorkerThread
+ private static Optional<String> getAccountType(ContentResolver contentResolver, long dataId) {
+ Assert.isWorkerThread();
+ Optional<Long> rawContactId = getRawContactId(contentResolver, dataId);
+ if (!rawContactId.isPresent()) {
+ return Optional.absent();
+ }
+ try (Cursor cursor =
+ contentResolver.query(
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId.get()),
+ new String[] {RawContacts.ACCOUNT_TYPE},
+ null,
+ null,
+ null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return Optional.fromNullable(cursor.getString(0));
+ }
+ }
+
+ @WorkerThread
+ private static Optional<Long> getRawContactId(ContentResolver contentResolver, long dataId) {
+ Assert.isWorkerThread();
+ try (Cursor cursor =
+ contentResolver.query(
+ ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
+ new String[] {Data.RAW_CONTACT_ID},
+ null,
+ null,
+ null)) {
+ if (cursor == null || !cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return Optional.of(cursor.getLong(0));
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private static Optional<PhoneAccountHandle> getPreferredAccount(
+ @NonNull Context context, @NonNull String dataId) {
+ Assert.isWorkerThread();
+ Assert.isNotNull(dataId);
+ try (Cursor cursor =
+ context
+ .getContentResolver()
+ .query(
+ PreferredSimFallbackContract.CONTENT_URI,
+ new String[] {
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ PreferredSim.PREFERRED_PHONE_ACCOUNT_ID
+ },
+ PreferredSim.DATA_ID + " = ?",
+ new String[] {dataId},
+ null)) {
+ if (cursor == null) {
+ return Optional.absent();
+ }
+ if (!cursor.moveToFirst()) {
+ return Optional.absent();
+ }
+ return PreferredAccountUtil.getValidPhoneAccount(
+ context, cursor.getString(0), cursor.getString(1));
+ }
+ }
+
+ @WorkerThread
+ private static boolean isPreferredSimEnabled(Context context) {
+ Assert.isWorkerThread();
+ if (!ConfigProviderBindings.get(context).getBoolean("preferred_sim_enabled", true)) {
+ return false;
+ }
+
+ Intent quickContactIntent = getQuickContactIntent();
+ ResolveInfo resolveInfo =
+ context
+ .getPackageManager()
+ .resolveActivity(quickContactIntent, PackageManager.GET_META_DATA);
+ if (resolveInfo == null
+ || resolveInfo.activityInfo == null
+ || resolveInfo.activityInfo.applicationInfo == null
+ || resolveInfo.activityInfo.applicationInfo.metaData == null) {
+ LogUtil.e("CallingAccountSelector.isPreferredSimEnabled", "cannot resolve quick contact app");
+ return false;
+ }
+ if (!resolveInfo.activityInfo.applicationInfo.metaData.getBoolean(
+ METADATA_SUPPORTS_PREFERRED_SIM, false)) {
+ LogUtil.i(
+ "CallingAccountSelector.isPreferredSimEnabled",
+ "system contacts does not support preferred SIM");
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ public static Intent getQuickContactIntent() {
+ Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setData(Contacts.CONTENT_URI.buildUpon().appendPath("1").build());
+ return intent;
+ }
+
+ /**
+ * Most devices are DSDS (dual SIM dual standby) which only one SIM can have active calls at a
+ * time. TODO(twyen): support other dual SIM modes when the API is exposed.
+ */
+ private boolean isSelectable(PhoneAccountHandle phoneAccountHandle) {
+ ImmutableList<ActiveCallInfo> activeCalls =
+ ActiveCallsComponent.get(appContext).activeCalls().getActiveCalls();
+ if (activeCalls.isEmpty()) {
+ return true;
+ }
+ for (ActiveCallInfo activeCall : activeCalls) {
+ if (Objects.equals(phoneAccountHandle, activeCall.phoneAccountHandle().orNull())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Optional<String> getActiveCallLabel() {
+ ImmutableList<ActiveCallInfo> activeCalls =
+ ActiveCallsComponent.get(appContext).activeCalls().getActiveCalls();
+
+ if (activeCalls.isEmpty()) {
+ LogUtil.e("CallingAccountSelector.getActiveCallLabel", "active calls no longer exist");
+ return Optional.absent();
+ }
+ ActiveCallInfo activeCall = activeCalls.get(0);
+ if (!activeCall.phoneAccountHandle().isPresent()) {
+ LogUtil.e("CallingAccountSelector.getActiveCallLabel", "active call has no phone account");
+ return Optional.absent();
+ }
+ PhoneAccount phoneAccount =
+ appContext
+ .getSystemService(TelecomManager.class)
+ .getPhoneAccount(activeCall.phoneAccountHandle().get());
+ if (phoneAccount == null) {
+ LogUtil.e("CallingAccountSelector.getActiveCallLabel", "phone account not found");
+ return Optional.absent();
+ }
+ return Optional.of(phoneAccount.getLabel().toString());
+ }
+}
diff --git a/java/com/android/dialer/preferredsim/impl/res/values/strings.xml b/java/com/android/dialer/preferredsim/impl/res/values/strings.xml
new file mode 100644
index 000000000..5e7ddd36c
--- /dev/null
+++ b/java/com/android/dialer/preferredsim/impl/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Toast when the user tried to place a call but has revoked phone permission [CHAR_LIMIT=none] -->
+ <string name="pre_call_permission_check_no_phone_permission">Cannot make call without phone permission</string>
+
+ <!-- Title of the dialog to select which SIM to call with before making a call, if the device has
+ multiple SIMs [CHAR LIMIT=40]-->
+ <string name="pre_call_select_phone_account">Choose SIM for this call</string>
+
+ <!-- Checkbox label when selecting a SIM when calling a contact, to use the selected SIM for the
+ same contact and never ask again [CHAR LIMIT=40]-->
+ <string name="pre_call_select_phone_account_remember">Remember this choice</string>
+
+ <!-- Hint to show under a SIM entry when selecting SIM for call on a multi-SIM device, when the
+ call cannot be placed with the SIM because there is already a call on the other SIM,
+ [CHAR LIMIT=NONE]-->
+ <string name="pre_call_select_phone_account_hint_other_sim_in_use">Not available while using <xliff:g example="SIM 1" id="other_sim">%1$s</xliff:g></string>
+</resources> \ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java
index 97a5facab..17591aa6c 100644
--- a/java/com/android/dialer/speeddial/SpeedDialFragment.java
+++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java
@@ -350,7 +350,7 @@ public class SpeedDialFragment extends Fragment {
modules.add(new DividerModule());
- // TODO(calderwoodra): add to favorites module
+ modules.add(new StarContactModule(speedDialUiItem));
// TODO(calderwoodra): remove from strequent module
// Contact info module
@@ -384,6 +384,41 @@ public class SpeedDialFragment extends Fragment {
.setIsVideoCall(channel.isVideoTechnology()));
}
+ private final class StarContactModule implements HistoryItemActionModule {
+
+ private final SpeedDialUiItem speedDialUiItem;
+
+ StarContactModule(SpeedDialUiItem speedDialUiItem) {
+ this.speedDialUiItem = speedDialUiItem;
+ }
+
+ @Override
+ public int getStringId() {
+ return R.string.suggested_contact_bottom_sheet_add_favorite_option;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.context_menu_contact_icon;
+ }
+
+ @Override
+ public boolean onClick() {
+ speedDialLoaderListener.listen(
+ getContext(),
+ UiItemLoaderComponent.get(getContext())
+ .speedDialUiItemMutator()
+ .starContact(
+ Uri.withAppendedPath(
+ Phone.CONTENT_FILTER_URI, speedDialUiItem.defaultChannel().number())),
+ SpeedDialFragment.this::onSpeedDialUiItemListLoaded,
+ throwable -> {
+ throw new RuntimeException(throwable);
+ });
+ return true;
+ }
+ }
+
private final class ContactInfoModule extends IntentModule {
public ContactInfoModule(Context context, Intent intent, int text, int image) {
diff --git a/java/com/android/dialer/speeddial/res/values/strings.xml b/java/com/android/dialer/speeddial/res/values/strings.xml
index 59a0ab5d6..028f6a90c 100644
--- a/java/com/android/dialer/speeddial/res/values/strings.xml
+++ b/java/com/android/dialer/speeddial/res/values/strings.xml
@@ -55,4 +55,7 @@
<!-- Text for a button that opens the contact's info [CHAR LIMIT=15]-->
<string name="contact_menu_contact_info">Contact info</string>
+
+ <!-- Text shown to the user in a button to prompt them to mark a contact as a favorite contact [CHAR LIMIT=30]. -->
+ <string name="suggested_contact_bottom_sheet_add_favorite_option">Add to favorites</string>
</resources> \ No newline at end of file
diff --git a/java/com/android/incallui/ActiveCallsCallListListener.java b/java/com/android/incallui/ActiveCallsCallListListener.java
index 3e4cb9375..d94db8836 100644
--- a/java/com/android/incallui/ActiveCallsCallListListener.java
+++ b/java/com/android/incallui/ActiveCallsCallListListener.java
@@ -49,10 +49,10 @@ public class ActiveCallsCallListListener implements CallList.Listener {
public void onCallListChange(CallList callList) {
ImmutableList.Builder<ActiveCallInfo> activeCalls = ImmutableList.builder();
for (DialerCall call : callList.getAllCalls()) {
- if (call.getState() != DialerCallState.DISCONNECTED) {
+ if (call.getState() != DialerCallState.DISCONNECTED && call.getAccountHandle() != null) {
activeCalls.add(
ActiveCallInfo.builder()
- .setPhoneAccountHandle(Optional.fromNullable(call.getAccountHandle()))
+ .setPhoneAccountHandle(Optional.of(call.getAccountHandle()))
.build());
}
}
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 0f0e9d9f2..5ac6b5029 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -55,8 +55,6 @@ import android.view.animation.AnimationUtils;
import android.widget.CheckBox;
import android.widget.Toast;
import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
-import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions;
-import com.android.contacts.common.widget.SelectPhoneAccountDialogOptionsUtil;
import com.android.dialer.animation.AnimUtils;
import com.android.dialer.animation.AnimationListenerAdapter;
import com.android.dialer.common.Assert;
@@ -65,7 +63,6 @@ import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.common.concurrent.UiListener;
import com.android.dialer.configprovider.ConfigProviderBindings;
-import com.android.dialer.logging.DialerImpression.Type;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
import com.android.dialer.metrics.Metrics;
@@ -73,7 +70,7 @@ import com.android.dialer.metrics.MetricsComponent;
import com.android.dialer.preferredsim.PreferredAccountRecorder;
import com.android.dialer.preferredsim.PreferredAccountWorker;
import com.android.dialer.preferredsim.PreferredAccountWorker.Result;
-import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
+import com.android.dialer.preferredsim.PreferredSimComponent;
import com.android.dialer.util.ViewUtil;
import com.android.incallui.answer.bindings.AnswerBindings;
import com.android.incallui.answer.protocol.AnswerScreen;
@@ -365,79 +362,44 @@ public class InCallActivity extends TransactionSafeFragmentActivity
return false;
}
- ListenableFuture<PreferredAccountWorker.Result> preferredAccountFuture =
- DialerExecutorComponent.get(this)
- .backgroundExecutor()
- .submit(
- () -> {
- try {
- return new PreferredAccountWorker(waitingForAccountCall.getNumber())
- .doInBackground(getApplicationContext());
- } catch (Throwable throwable) {
- throw new Exception(throwable);
- }
- });
+ PreferredAccountWorker preferredAccountWorker =
+ PreferredSimComponent.get(this).preferredAccountWorker();
+
+ Bundle extras = waitingForAccountCall.getIntentExtras();
+ List<PhoneAccountHandle> phoneAccountHandles =
+ extras == null
+ ? new ArrayList<>()
+ : extras.getParcelableArrayList(Call.AVAILABLE_PHONE_ACCOUNTS);
+ ListenableFuture<PreferredAccountWorker.Result> preferredAccountFuture =
+ preferredAccountWorker.selectAccount(
+ waitingForAccountCall.getNumber(), phoneAccountHandles);
preferredAccountWorkerResultListener.listen(
this,
preferredAccountFuture,
result -> {
- if (result.getPhoneAccountHandle().isPresent()) {
- Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED);
- selectPhoneAccountListener.onPhoneAccountSelected(
- result.getPhoneAccountHandle().get(), false, waitingForAccountCall.getId());
+ if (!isVisible()) {
+ LogUtil.i(
+ "CallingAccountSelector.showPhoneAccountSelectionDialog",
+ "activity ended before result returned");
return;
}
- if (result.getSuggestion().isPresent()) {
- LogUtil.i(
- "CallingAccountSelector.processPreferredAccount",
- "SIM suggested: " + result.getSuggestion().get().reason);
- if (result.getSuggestion().get().shouldAutoSelect) {
- Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED);
- LogUtil.i(
- "CallingAccountSelector.processPreferredAccount", "Auto selected suggestion");
- selectPhoneAccountListener.onPhoneAccountSelected(
- result.getSuggestion().get().phoneAccountHandle,
- false,
- waitingForAccountCall.getId());
- return;
- }
+ String callId = waitingForAccountCall.getId();
+ if (result.getSelectedPhoneAccountHandle().isPresent()) {
+ selectPhoneAccountListener.onPhoneAccountSelected(
+ result.getSelectedPhoneAccountHandle().get(), false, callId);
+ return;
}
- Bundle extras = waitingForAccountCall.getIntentExtras();
- List<PhoneAccountHandle> phoneAccountHandles =
- extras == null
- ? new ArrayList<>()
- : extras.getParcelableArrayList(Call.AVAILABLE_PHONE_ACCOUNTS);
waitingForAccountCall.setPreferredAccountRecorder(
new PreferredAccountRecorder(
waitingForAccountCall.getNumber(),
result.getSuggestion().orNull(),
result.getDataId().orNull()));
- SelectPhoneAccountDialogOptions.Builder optionsBuilder =
- SelectPhoneAccountDialogOptions.newBuilder()
- .setTitle(R.string.select_phone_account_for_calls)
- .setCanSetDefault(result.getDataId().isPresent())
- .setSetDefaultLabel(R.string.select_phone_account_for_calls_remember)
- .setCallId(waitingForAccountCall.getId());
-
- for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
- SelectPhoneAccountDialogOptions.Entry.Builder entryBuilder =
- SelectPhoneAccountDialogOptions.Entry.newBuilder();
- SelectPhoneAccountDialogOptionsUtil.setPhoneAccountHandle(
- entryBuilder, phoneAccountHandle);
- Optional<String> hint =
- SuggestionProvider.getHint(
- this, phoneAccountHandle, result.getSuggestion().orNull());
- if (hint.isPresent()) {
- entryBuilder.setHint(hint.get());
- }
- optionsBuilder.addEntries(entryBuilder);
- }
-
selectPhoneAccountDialogFragment =
SelectPhoneAccountDialogFragment.newInstance(
- optionsBuilder.build(), selectPhoneAccountListener);
+ result.getDialogOptionsBuilder().get().setCallId(callId).build(),
+ selectPhoneAccountListener);
selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
},
throwable -> {
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index 8193c6e05..ccc564806 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -414,7 +414,7 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud
* Return whether we should start call in bubble mode and not show InCallActivity. The call mode
* should be set in CallConfiguration in EXTRA_OUTGOING_CALL_EXTRAS when starting a call intent.
*/
- private boolean shouldStartInBubbleMode() {
+ public boolean shouldStartInBubbleMode() {
if (!ReturnToCallController.isEnabled(context)) {
return false;
}
@@ -427,6 +427,9 @@ public class InCallPresenter implements CallList.Listener, AudioModeProvider.Aud
if (dialerCall == null) {
return false;
}
+ if (dialerCall.isEmergencyCall()) {
+ return false;
+ }
Bundle extras = dialerCall.getIntentExtras();
boolean result = shouldStartInBubbleModeWithExtras(extras);
diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java
index 4a7b3fbce..96bdda1ba 100644
--- a/java/com/android/incallui/ReturnToCallController.java
+++ b/java/com/android/incallui/ReturnToCallController.java
@@ -37,6 +37,7 @@ import com.android.dialer.lettertile.LetterTileDrawable;
import com.android.dialer.telecom.TelecomUtil;
import com.android.incallui.ContactInfoCache.ContactCacheEntry;
import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
+import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallUiListener;
import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.audiomode.AudioModeProvider.AudioModeListener;
@@ -77,6 +78,8 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio
private final ContactInfoCache contactInfoCache;
+ private InCallState inCallState;
+
public static boolean isEnabled(Context context) {
return ConfigProviderBindings.get(context).getBoolean("enable_return_to_call_bubble_v2", false);
}
@@ -186,7 +189,19 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio
return;
}
- if ((bubble == null || !(bubble.isVisible() || bubble.isDismissed()))
+ boolean shouldStartInBubbleMode = InCallPresenter.getInstance().shouldStartInBubbleMode();
+ InCallState newInCallState =
+ InCallPresenter.getInstance().getPotentialStateFromCallList(callList);
+ boolean isNewBackgroundCall =
+ newInCallState != inCallState
+ && newInCallState == InCallState.OUTGOING
+ && shouldStartInBubbleMode;
+ if (bubble != null && isNewBackgroundCall) {
+ // If new outgoing call is in bubble mode, update bubble info.
+ // We don't update if new call is not in bubble mode even if the existing call is.
+ bubble.setBubbleInfo(generateBubbleInfoForBackgroundCalling());
+ }
+ if ((bubble == null || !(bubble.isVisible() || bubble.isDismissed()) || isNewBackgroundCall)
&& getCall() != null
&& !InCallPresenter.getInstance().isShowingInCallUi()) {
LogUtil.i("ReturnToCallController.onCallListChange", "going to show bubble");
@@ -195,6 +210,7 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio
// The call to display might be different for the existing bubble
startContactInfoSearch();
}
+ inCallState = newInCallState;
}
@Override
@@ -274,7 +290,20 @@ public class ReturnToCallController implements InCallUiListener, Listener, Audio
.setPrimaryColor(context.getResources().getColor(R.color.dialer_theme_color, null))
.setPrimaryIcon(Icon.createWithResource(context, R.drawable.on_going_call))
.setStartingYPosition(
- context.getResources().getDimensionPixelOffset(R.dimen.return_to_call_initial_offset_y))
+ InCallPresenter.getInstance().shouldStartInBubbleMode()
+ ? context.getResources().getDisplayMetrics().heightPixels / 2
+ : context
+ .getResources()
+ .getDimensionPixelOffset(R.dimen.return_to_call_initial_offset_y))
+ .setActions(generateActions())
+ .build();
+ }
+
+ private BubbleInfo generateBubbleInfoForBackgroundCalling() {
+ return BubbleInfo.builder()
+ .setPrimaryColor(context.getResources().getColor(R.color.dialer_theme_color, null))
+ .setPrimaryIcon(Icon.createWithResource(context, R.drawable.on_going_call))
+ .setStartingYPosition(context.getResources().getDisplayMetrics().heightPixels / 2)
.setActions(generateActions())
.build();
}
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index d57de15e5..1c2744644 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -83,6 +83,7 @@ import com.android.dialer.util.PermissionsUtil;
import com.android.incallui.audiomode.AudioModeProvider;
import com.android.incallui.call.state.DialerCallState;
import com.android.incallui.latencyreport.LatencyReport;
+import com.android.incallui.rtt.protocol.RttChatMessage;
import com.android.incallui.speakeasy.runtime.Constraints;
import com.android.incallui.videotech.VideoTech;
import com.android.incallui.videotech.VideoTech.VideoTechListener;
@@ -92,6 +93,7 @@ import com.android.incallui.videotech.ims.ImsVideoTech;
import com.android.incallui.videotech.utils.VideoUtils;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -1084,6 +1086,32 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
getTelecomCall().respondToRttRequest(rttRequestId, accept);
}
+ @TargetApi(28)
+ private void saveRttTranscript() {
+ if (!BuildCompat.isAtLeastP()) {
+ return;
+ }
+ // Save any remaining text in the buffer that's not shown by UI yet.
+ // This may happen when the call is switched to background before disconnect.
+ try {
+ String messageLeft = getRttCall().readImmediately();
+ if (!TextUtils.isEmpty(messageLeft)) {
+ rttTranscript =
+ RttChatMessage.getRttTranscriptWithNewRemoteMessage(rttTranscript, messageLeft);
+ }
+ } catch (IOException e) {
+ LogUtil.e("DialerCall.saveRttTranscript", "error when reading remaining message", e);
+ }
+ // Don't save transcript if it's empty.
+ if (rttTranscript.getMessagesCount() == 0) {
+ return;
+ }
+ Futures.addCallback(
+ RttTranscriptUtil.saveRttTranscript(context, rttTranscript),
+ new DefaultFutureCallback<>(),
+ MoreExecutors.directExecutor());
+ }
+
public boolean hasReceivedVideoUpgradeRequest() {
return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
}
@@ -1615,11 +1643,9 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
videoTechManager.dispatchRemovedFromCallList();
}
// TODO(a bug): Add tests for it to make sure no crash on subsequent call to this method.
+ // TODO(wangqi): Consider moving this to a DialerCallListener.
if (rttTranscript != null && !isCallRemoved) {
- Futures.addCallback(
- RttTranscriptUtil.saveRttTranscript(context, rttTranscript),
- new DefaultFutureCallback<>(),
- MoreExecutors.directExecutor());
+ saveRttTranscript();
}
isCallRemoved = true;
}
diff --git a/java/com/android/incallui/call_configuration.proto b/java/com/android/incallui/callconfiguration/call_configuration.proto
index 2cf3c065c..2cf3c065c 100644
--- a/java/com/android/incallui/call_configuration.proto
+++ b/java/com/android/incallui/callconfiguration/call_configuration.proto
diff --git a/java/com/android/incallui/rtt/impl/RttChatAdapter.java b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
index f1cde759c..160377889 100644
--- a/java/com/android/incallui/rtt/impl/RttChatAdapter.java
+++ b/java/com/android/incallui/rtt/impl/RttChatAdapter.java
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import com.android.dialer.common.LogUtil;
import com.android.dialer.rtt.RttTranscript;
import com.android.dialer.rtt.RttTranscriptMessage;
+import com.android.incallui.rtt.protocol.RttChatMessage;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -133,13 +134,13 @@ public class RttChatAdapter extends RecyclerView.Adapter<ViewHolder> {
rttChatMessage.append(newMessage);
rttMessages.add(rttChatMessage);
lastIndexOfLocalMessage = rttMessages.size() - 1;
- notifyItemInserted(lastIndexOfLocalMessage);
+ notifyItemInserted(toItemPosition(lastIndexOfLocalMessage));
} else {
rttChatMessage.append(newMessage);
// Clear empty message bubble.
if (TextUtils.isEmpty(rttChatMessage.getContent())) {
rttMessages.remove(lastIndexOfLocalMessage);
- notifyItemRemoved(lastIndexOfLocalMessage);
+ notifyItemRemoved(toItemPosition(lastIndexOfLocalMessage));
lastIndexOfLocalMessage = -1;
} else {
notifyItemChanged(toItemPosition(lastIndexOfLocalMessage));
diff --git a/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java b/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java
index 2beea9530..56161eccc 100644
--- a/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java
+++ b/java/com/android/incallui/rtt/impl/RttChatMessageViewHolder.java
@@ -25,6 +25,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
+import com.android.incallui.rtt.protocol.RttChatMessage;
/** ViewHolder class for RTT chat message bubble. */
public class RttChatMessageViewHolder extends ViewHolder {
diff --git a/java/com/android/incallui/rtt/impl/res/values/strings.xml b/java/com/android/incallui/rtt/impl/res/values/strings.xml
index 462eea563..fc60eceed 100644
--- a/java/com/android/incallui/rtt/impl/res/values/strings.xml
+++ b/java/com/android/incallui/rtt/impl/res/values/strings.xml
@@ -31,6 +31,6 @@
<string name="rtt_status_banner_text">Waiting for <xliff:g id="name">%s</xliff:g> to join RTT call&#8230;</string>
<!-- Text for RTT transcript advisory. [CHAR LIMIT=NONE] -->
- <string name="rtt_transcript_advisory">The other party can see you typing. Transcripts stored on your device in the call history.</string>
+ <string name="rtt_transcript_advisory">The other party can see you typing. Transcripts are stored on your device in the call history.</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/java/com/android/incallui/rtt/impl/RttChatMessage.java b/java/com/android/incallui/rtt/protocol/RttChatMessage.java
index 2f3933a50..5680529ee 100644
--- a/java/com/android/incallui/rtt/impl/RttChatMessage.java
+++ b/java/com/android/incallui/rtt/protocol/RttChatMessage.java
@@ -14,25 +14,24 @@
* limitations under the License
*/
-package com.android.incallui.rtt.impl;
+package com.android.incallui.rtt.protocol;
import android.support.annotation.NonNull;
import com.android.dialer.common.Assert;
import com.android.dialer.rtt.RttTranscript;
import com.android.dialer.rtt.RttTranscriptMessage;
-import com.android.incallui.rtt.protocol.Constants;
import com.google.common.base.Splitter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/** Message class that holds one RTT chat content. */
-final class RttChatMessage {
+public final class RttChatMessage {
private static final Splitter SPLITTER = Splitter.on(Constants.BUBBLE_BREAKER);
- boolean isRemote;
- long timstamp;
+ public boolean isRemote;
+ private long timstamp;
private final StringBuilder content = new StringBuilder();
private boolean isFinished;
@@ -44,7 +43,7 @@ final class RttChatMessage {
isFinished = true;
}
- void unfinish() {
+ public void unfinish() {
isFinished = false;
}
@@ -74,7 +73,7 @@ final class RttChatMessage {
*
* <p>"hello world" -> "hello new world" : "\b\b\b\b\bnew world"
*/
- static String computeChangedString(String oldMessage, String newMesssage) {
+ public static String computeChangedString(String oldMessage, String newMesssage) {
StringBuilder modify = new StringBuilder();
int indexChangeStart = 0;
while (indexChangeStart < oldMessage.length()
@@ -91,8 +90,21 @@ final class RttChatMessage {
return modify.toString();
}
+ public static RttTranscript getRttTranscriptWithNewRemoteMessage(
+ RttTranscript rttTranscript, @NonNull String text) {
+ List<RttChatMessage> messageList = fromTranscript(rttTranscript);
+ updateRemoteRttChatMessage(messageList, text);
+ return RttTranscript.newBuilder()
+ .setId(rttTranscript.getId())
+ .setNumber(rttTranscript.getNumber())
+ .setTimestamp(rttTranscript.getTimestamp())
+ .addAllMessages(toTranscriptMessageList(messageList))
+ .build();
+ }
+
/** Update list of {@code RttChatMessage} based on given remote text. */
- static void updateRemoteRttChatMessage(List<RttChatMessage> messageList, @NonNull String text) {
+ public static void updateRemoteRttChatMessage(
+ List<RttChatMessage> messageList, @NonNull String text) {
Assert.isNotNull(messageList);
Iterator<String> splitText = SPLITTER.split(text).iterator();
@@ -163,7 +175,7 @@ final class RttChatMessage {
return i;
}
- static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) {
+ public static int getLastIndexRemoteMessage(List<RttChatMessage> messageList) {
int i = messageList.size() - 1;
while (i >= 0 && !messageList.get(i).isRemote) {
i--;
@@ -171,7 +183,7 @@ final class RttChatMessage {
return i;
}
- static int getLastIndexLocalMessage(List<RttChatMessage> messageList) {
+ public static int getLastIndexLocalMessage(List<RttChatMessage> messageList) {
int i = messageList.size() - 1;
while (i >= 0 && messageList.get(i).isRemote) {
i--;
@@ -179,7 +191,8 @@ final class RttChatMessage {
return i;
}
- static List<RttTranscriptMessage> toTranscriptMessageList(List<RttChatMessage> messageList) {
+ public static List<RttTranscriptMessage> toTranscriptMessageList(
+ List<RttChatMessage> messageList) {
List<RttTranscriptMessage> transcriptMessageList = new ArrayList<>();
for (RttChatMessage message : messageList) {
transcriptMessageList.add(
@@ -193,7 +206,7 @@ final class RttChatMessage {
return transcriptMessageList;
}
- static List<RttChatMessage> fromTranscript(RttTranscript rttTranscript) {
+ public static List<RttChatMessage> fromTranscript(RttTranscript rttTranscript) {
List<RttChatMessage> messageList = new ArrayList<>();
if (rttTranscript == null) {
return messageList;
@@ -211,7 +224,7 @@ final class RttChatMessage {
return messageList;
}
- RttChatMessage() {
+ public RttChatMessage() {
timstamp = System.currentTimeMillis();
}
}