summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2017-10-11 15:31:19 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2017-10-11 15:31:19 +0000
commit38e05b7d25e58760104917035d42ab41b8483e35 (patch)
tree35bbae5a10d673a11eec6f20954305bc2eb9aa4f
parent5a0a312336fad7761f942d6934825cd6701ec009 (diff)
parent00623aa60a7908b0709df38632cfa576cb15e33e (diff)
Merge changes Idb3f486e,I534e1d22
* changes: Implement SIM swapping Set an appropriate icon for the call detail UI's callback button.
-rw-r--r--java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java10
-rw-r--r--java/com/android/dialer/app/calllog/IntentProvider.java9
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsActivity.java69
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsAdapter.java26
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java77
-rw-r--r--java/com/android/dialer/logging/dialer_impression.proto7
-rw-r--r--java/com/android/dialer/telecom/TelecomUtil.java23
-rw-r--r--java/com/android/incallui/CallButtonPresenter.java28
-rw-r--r--java/com/android/incallui/callpending/CallPendingActivity.java3
-rw-r--r--java/com/android/incallui/commontheme/res/values/strings.xml2
-rw-r--r--java/com/android/incallui/incall/impl/ButtonChooserFactory.java1
-rw-r--r--java/com/android/incallui/incall/impl/ButtonController.java17
-rw-r--r--java/com/android/incallui/incall/impl/InCallFragment.java4
-rw-r--r--java/com/android/incallui/incall/impl/res/values/strings.xml4
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonIds.java4
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java2
-rw-r--r--java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java2
-rw-r--r--java/com/android/incallui/multisim/SwapSimWorker.java202
18 files changed, 455 insertions, 35 deletions
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index adf7f1b4e..6067c4239 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -551,7 +551,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
if (mCallLogCache.isVoicemailNumber(accountHandle, number)) {
// Call to generic voicemail number, in case there are multiple accounts
primaryActionButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider());
- } else if (this.info != null && this.info.lookupKey != null) {
+ } else if (canSupportAssistedDialing()) {
primaryActionButtonView.setTag(
IntentProvider.getAssistedDialIntentProvider(
number + postDialDigits,
@@ -618,7 +618,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
((TextView) callButtonView.findViewById(R.id.call_type_or_location_text));
if (canPlaceCallToNumber) {
- if (this.info != null && this.info.lookupKey != null) {
+ if (canSupportAssistedDialing()) {
callButtonView.setTag(
IntentProvider.getAssistedDialIntentProvider(
number, mContext, mContext.getSystemService(TelephonyManager.class)));
@@ -685,7 +685,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
&& mCachedNumberLookupService.canReportAsInvalid(info.sourceType, info.objectId);
detailsButtonView.setTag(
IntentProvider.getCallDetailIntentProvider(
- callDetailsEntries, buildContact(), canReportCallerId));
+ callDetailsEntries, buildContact(), canReportCallerId, canSupportAssistedDialing()));
}
boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam);
@@ -764,6 +764,10 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
return accountHandle.getComponentName().equals(mDefaultPhoneAccountHandle.getComponentName());
}
+ private boolean canSupportAssistedDialing() {
+ return info != null && info.lookupKey != null;
+ }
+
private boolean canSupportCarrierVideoCall() {
return mCallLogCache.canRelyOnVideoPresence()
&& info != null
diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java
index 52a7b0faf..5030de5f5 100644
--- a/java/com/android/dialer/app/calllog/IntentProvider.java
+++ b/java/com/android/dialer/app/calllog/IntentProvider.java
@@ -124,15 +124,20 @@ public abstract class IntentProvider {
*
* @param callDetailsEntries The call details of the other calls grouped together with the call.
* @param contact The contact with which this call details intent pertains to.
+ * @param canReportCallerId Whether reporting a caller ID is supported.
+ * @param canSupportAssistedDialing Whether assisted dialing is supported.
* @return The call details intent provider.
*/
public static IntentProvider getCallDetailIntentProvider(
- CallDetailsEntries callDetailsEntries, DialerContact contact, boolean canReportCallerId) {
+ CallDetailsEntries callDetailsEntries,
+ DialerContact contact,
+ boolean canReportCallerId,
+ boolean canSupportAssistedDialing) {
return new IntentProvider() {
@Override
public Intent getIntent(Context context) {
return CallDetailsActivity.newInstance(
- context, callDetailsEntries, contact, canReportCallerId);
+ context, callDetailsEntries, contact, canReportCallerId, canSupportAssistedDialing);
}
};
}
diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java
index 569aaa5b5..d7f414bb2 100644
--- a/java/com/android/dialer/calldetails/CallDetailsActivity.java
+++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java
@@ -16,6 +16,7 @@
package com.android.dialer.calldetails;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
@@ -29,21 +30,30 @@ import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
+import android.telephony.TelephonyManager;
import android.view.MenuItem;
+import android.widget.Toast;
+import com.android.dialer.assisteddialing.ConcreteCreator;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.AsyncTaskExecutors;
+import com.android.dialer.constants.ActivityRequestCodes;
import com.android.dialer.dialercontact.DialerContact;
import com.android.dialer.enrichedcall.EnrichedCallComponent;
import com.android.dialer.enrichedcall.EnrichedCallManager.HistoricalDataChangedListener;
import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult;
+import com.android.dialer.lightbringer.Lightbringer;
+import com.android.dialer.lightbringer.LightbringerComponent;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.UiAction;
import com.android.dialer.performancereport.PerformanceReport;
import com.android.dialer.postcall.PostCall;
import com.android.dialer.protos.ProtoParsers;
+import com.android.dialer.util.DialerUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -51,6 +61,7 @@ import java.util.Map;
/** Displays the details of a specific call log entry. */
public class CallDetailsActivity extends AppCompatActivity
implements OnMenuItemClickListener,
+ CallDetailsHeaderViewHolder.CallbackActionListener,
CallDetailsFooterViewHolder.ReportCallIdListener,
HistoricalDataChangedListener {
@@ -59,6 +70,7 @@ public class CallDetailsActivity extends AppCompatActivity
private static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries";
private static final String EXTRA_CONTACT = "contact";
private static final String EXTRA_CAN_REPORT_CALLER_ID = "can_report_caller_id";
+ private static final String EXTRA_CAN_SUPPORT_ASSISTED_DIALING = "can_support_assisted_dialing";
private static final String TASK_DELETE = "task_delete";
private CallDetailsEntries entries;
@@ -74,7 +86,8 @@ public class CallDetailsActivity extends AppCompatActivity
Context context,
@NonNull CallDetailsEntries details,
@NonNull DialerContact contact,
- boolean canReportCallerId) {
+ boolean canReportCallerId,
+ boolean canSupportAssistedDialing) {
Assert.isNotNull(details);
Assert.isNotNull(contact);
@@ -82,6 +95,7 @@ public class CallDetailsActivity extends AppCompatActivity
ProtoParsers.put(intent, EXTRA_CONTACT, contact);
ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, details);
intent.putExtra(EXTRA_CAN_REPORT_CALLER_ID, canReportCallerId);
+ intent.putExtra(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, canSupportAssistedDialing);
return intent;
}
@@ -142,7 +156,13 @@ public class CallDetailsActivity extends AppCompatActivity
entries =
ProtoParsers.getTrusted(
intent, EXTRA_CALL_DETAILS_ENTRIES, CallDetailsEntries.getDefaultInstance());
- adapter = new CallDetailsAdapter(this, contact, entries.getEntriesList(), this);
+ adapter =
+ new CallDetailsAdapter(
+ this /* context */,
+ contact,
+ entries.getEntriesList(),
+ this /* callbackListener */,
+ this /* reportCallIdListener */);
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
@@ -188,6 +208,51 @@ public class CallDetailsActivity extends AppCompatActivity
.getEntriesList());
}
+ @Override
+ public void placeImsVideoCall(String phoneNumber) {
+ Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_IMS_VIDEO_CALL_BACK);
+ DialerUtils.startActivityWithErrorToast(
+ this,
+ new CallIntentBuilder(phoneNumber, CallInitiationType.Type.CALL_DETAILS)
+ .setIsVideoCall(true)
+ .build());
+ }
+
+ @Override
+ public void placeLightbringerCall(String phoneNumber) {
+ Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK);
+ Lightbringer lightbringer = LightbringerComponent.get(this).getLightbringer();
+ if (!lightbringer.isReachable(this, phoneNumber)) {
+ placeImsVideoCall(phoneNumber);
+ return;
+ }
+
+ try {
+ startActivityForResult(
+ lightbringer.getIntent(this, phoneNumber), ActivityRequestCodes.DIALTACTS_LIGHTBRINGER);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.activity_not_available, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public void placeVoiceCall(String phoneNumber, String postDialDigits) {
+ Logger.get(this).logImpression(DialerImpression.Type.CALL_DETAILS_VOICE_CALL_BACK);
+
+ boolean canSupportedAssistedDialing =
+ getIntent().getExtras().getBoolean(EXTRA_CAN_SUPPORT_ASSISTED_DIALING, false);
+ CallIntentBuilder callIntentBuilder =
+ new CallIntentBuilder(phoneNumber + postDialDigits, CallInitiationType.Type.CALL_DETAILS);
+ if (canSupportedAssistedDialing) {
+ callIntentBuilder.setAllowAssistedDial(
+ true,
+ ConcreteCreator.createNewAssistedDialingMediator(
+ getSystemService(TelephonyManager.class), this));
+ }
+
+ DialerUtils.startActivityWithErrorToast(this, callIntentBuilder.build());
+ }
+
@NonNull
private Map<CallDetailsEntry, List<HistoryResult>> getAllHistoricalData(
@Nullable String number, @NonNull CallDetailsEntries entries) {
diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java
index 645587461..1f00d9d9a 100644
--- a/java/com/android/dialer/calldetails/CallDetailsAdapter.java
+++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java
@@ -23,7 +23,10 @@ import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
+import com.android.dialer.calldetails.CallDetailsHeaderViewHolder.CallbackActionListener;
import com.android.dialer.calllogutils.CallTypeHelper;
+import com.android.dialer.calllogutils.CallbackActionHelper;
+import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction;
import com.android.dialer.common.Assert;
import com.android.dialer.dialercontact.DialerContact;
import com.android.dialer.lightbringer.LightbringerComponent;
@@ -37,7 +40,8 @@ final class CallDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
private static final int FOOTER_VIEW_TYPE = 3;
private final DialerContact contact;
- private final CallDetailsFooterViewHolder.ReportCallIdListener listener;
+ private final CallbackActionListener callbackActionListener;
+ private final CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener;
private final CallTypeHelper callTypeHelper;
private List<CallDetailsEntry> callDetailsEntries;
@@ -45,10 +49,12 @@ final class CallDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
Context context,
@NonNull DialerContact contact,
@NonNull List<CallDetailsEntry> callDetailsEntries,
- CallDetailsFooterViewHolder.ReportCallIdListener listener) {
+ CallbackActionListener callbackActionListener,
+ CallDetailsFooterViewHolder.ReportCallIdListener reportCallIdListener) {
this.contact = Assert.isNotNull(contact);
this.callDetailsEntries = callDetailsEntries;
- this.listener = listener;
+ this.callbackActionListener = callbackActionListener;
+ this.reportCallIdListener = reportCallIdListener;
callTypeHelper =
new CallTypeHelper(
context.getResources(), LightbringerComponent.get(context).getLightbringer());
@@ -60,13 +66,13 @@ final class CallDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
switch (viewType) {
case HEADER_VIEW_TYPE:
return new CallDetailsHeaderViewHolder(
- inflater.inflate(R.layout.contact_container, parent, false));
+ inflater.inflate(R.layout.contact_container, parent, false), callbackActionListener);
case CALL_ENTRY_VIEW_TYPE:
return new CallDetailsEntryViewHolder(
inflater.inflate(R.layout.call_details_entry, parent, false));
case FOOTER_VIEW_TYPE:
return new CallDetailsFooterViewHolder(
- inflater.inflate(R.layout.call_details_footer, parent, false), listener);
+ inflater.inflate(R.layout.call_details_footer, parent, false), reportCallIdListener);
default:
throw Assert.createIllegalStateFailException(
"No ViewHolder available for viewType: " + viewType);
@@ -76,7 +82,7 @@ final class CallDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (position == 0) { // Header
- ((CallDetailsHeaderViewHolder) holder).updateContactInfo(contact);
+ ((CallDetailsHeaderViewHolder) holder).updateContactInfo(contact, getCallbackAction());
} else if (position == getItemCount() - 1) {
((CallDetailsFooterViewHolder) holder).setPhoneNumber(contact.getNumber());
} else {
@@ -110,4 +116,12 @@ final class CallDetailsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
callDetailsEntries = entries;
notifyDataSetChanged();
}
+
+ private @CallbackAction int getCallbackAction() {
+ Assert.checkState(!callDetailsEntries.isEmpty());
+
+ CallDetailsEntry entry = callDetailsEntries.get(0);
+ return CallbackActionHelper.getCallbackAction(
+ contact.getNumber(), entry.getFeatures(), entry.getIsLightbringerCall());
+ }
}
diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
index dcd8e3537..7c3892a33 100644
--- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
@@ -23,23 +23,22 @@ import android.telecom.PhoneAccount;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
+import android.widget.ImageView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
-import com.android.dialer.callintent.CallInitiationType;
-import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction;
import com.android.dialer.common.Assert;
import com.android.dialer.contactphoto.ContactPhotoManager;
import com.android.dialer.dialercontact.DialerContact;
-import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
-import com.android.dialer.util.DialerUtils;
/** ViewHolder for Header/Contact in {@link CallDetailsActivity}. */
public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder
implements OnClickListener {
- private final View callBackButton;
+ private final CallbackActionListener callbackActionListener;
+ private final ImageView callbackButton;
private final TextView nameView;
private final TextView numberView;
private final TextView networkView;
@@ -47,24 +46,26 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder
private final Context context;
private DialerContact contact;
+ private @CallbackAction int callbackAction;
- CallDetailsHeaderViewHolder(View container) {
+ CallDetailsHeaderViewHolder(View container, CallbackActionListener callbackActionListener) {
super(container);
context = container.getContext();
- callBackButton = container.findViewById(R.id.call_back_button);
+ callbackButton = container.findViewById(R.id.call_back_button);
nameView = container.findViewById(R.id.contact_name);
numberView = container.findViewById(R.id.phone_number);
networkView = container.findViewById(R.id.network);
contactPhoto = container.findViewById(R.id.quick_contact_photo);
- callBackButton.setOnClickListener(this);
+ callbackButton.setOnClickListener(this);
+ this.callbackActionListener = callbackActionListener;
Logger.get(context)
.logQuickContactOnTouch(
contactPhoto, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_DETAILS, true);
}
/** Populates the contact info fields based on the current contact information. */
- void updateContactInfo(DialerContact contact) {
+ void updateContactInfo(DialerContact contact, @CallbackAction int callbackAction) {
this.contact = contact;
ContactPhotoManager.getInstance(context)
.loadDialerThumbnailOrPhoto(
@@ -99,23 +100,61 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder
}
}
- if (TextUtils.isEmpty(contact.getNumber())) {
- callBackButton.setVisibility(View.GONE);
+ setCallbackAction(callbackAction);
+ }
+
+ private void setCallbackAction(@CallbackAction int callbackAction) {
+ this.callbackAction = callbackAction;
+ switch (callbackAction) {
+ case CallbackAction.LIGHTBRINGER:
+ case CallbackAction.IMS_VIDEO:
+ callbackButton.setVisibility(View.VISIBLE);
+ callbackButton.setImageResource(R.drawable.quantum_ic_videocam_vd_theme_24);
+ break;
+ case CallbackAction.VOICE:
+ callbackButton.setVisibility(View.VISIBLE);
+ callbackButton.setImageResource(R.drawable.quantum_ic_call_vd_theme_24);
+ break;
+ case CallbackAction.NONE:
+ callbackButton.setVisibility(View.GONE);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException("Invalid action: " + callbackAction);
}
}
@Override
public void onClick(View view) {
- if (view == callBackButton) {
- Logger.get(view.getContext()).logImpression(DialerImpression.Type.CALL_DETAILS_CALL_BACK);
- DialerUtils.startActivityWithErrorToast(
- view.getContext(),
- new CallIntentBuilder(
- contact.getNumber() + contact.getPostDialDigits(),
- CallInitiationType.Type.CALL_DETAILS)
- .build());
+ if (view == callbackButton) {
+ switch (callbackAction) {
+ case CallbackAction.IMS_VIDEO:
+ callbackActionListener.placeImsVideoCall(contact.getNumber());
+ break;
+ case CallbackAction.LIGHTBRINGER:
+ callbackActionListener.placeLightbringerCall(contact.getNumber());
+ break;
+ case CallbackAction.VOICE:
+ callbackActionListener.placeVoiceCall(contact.getNumber(), contact.getPostDialDigits());
+ break;
+ case CallbackAction.NONE:
+ default:
+ throw Assert.createIllegalStateFailException("Invalid action: " + callbackAction);
+ }
} else {
throw Assert.createIllegalStateFailException("View OnClickListener not implemented: " + view);
}
}
+
+ /** Listener for making a callback */
+ interface CallbackActionListener {
+
+ /** Places an IMS video call. */
+ void placeImsVideoCall(String phoneNumber);
+
+ /** Places a Lightbringer call. */
+ void placeLightbringerCall(String phoneNumber);
+
+ /** Place a traditional voice call. */
+ void placeVoiceCall(String phoneNumber, String postDialDigits);
+ }
}
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index fe1c5e9ba..098391877 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -323,7 +323,7 @@ message DialerImpression {
CALL_DETAILS_COPY_NUMBER = 1121;
CALL_DETAILS_EDIT_BEFORE_CALL = 1122;
- CALL_DETAILS_CALL_BACK = 1123;
+ CALL_DETAILS_CALL_BACK = 1123 [deprecated = true];
VVM_USER_DISMISSED_VM_ALMOST_FULL_PROMO = 1124;
VVM_USER_DISMISSED_VM_FULL_PROMO = 1125;
@@ -554,5 +554,10 @@ message DialerImpression {
// More voicemail transcription impressions
VVM_TRANSCRIPTION_POLL_REQUEST = 1283;
+
+ // Callback impressions (with more granularity) from the call details UI
+ CALL_DETAILS_IMS_VIDEO_CALL_BACK = 1284;
+ CALL_DETAILS_LIGHTBRINGER_CALL_BACK = 1285;
+ CALL_DETAILS_VOICE_CALL_BACK = 1286;
}
}
diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java
index 8ff4b3967..3bf9b4666 100644
--- a/java/com/android/dialer/telecom/TelecomUtil.java
+++ b/java/com/android/dialer/telecom/TelecomUtil.java
@@ -17,6 +17,7 @@
package com.android.dialer.telecom;
import android.Manifest;
+import android.Manifest.permission;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -24,7 +25,9 @@ import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.CallLog.Calls;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RequiresPermission;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.ContextCompat;
import android.telecom.PhoneAccount;
@@ -234,6 +237,26 @@ public abstract class TelecomUtil {
return instance.isDefaultDialer(context);
}
+ /** @return the other SIM based PhoneAccountHandle that is not {@code currentAccount} */
+ @Nullable
+ @RequiresPermission(permission.READ_PHONE_STATE)
+ @SuppressWarnings("MissingPermission")
+ public static PhoneAccountHandle getOtherAccount(
+ @NonNull Context context, @Nullable PhoneAccountHandle currentAccount) {
+ if (currentAccount == null) {
+ return null;
+ }
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ for (PhoneAccountHandle phoneAccountHandle : telecomManager.getCallCapablePhoneAccounts()) {
+ PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccountHandle);
+ if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+ && !phoneAccountHandle.equals(currentAccount)) {
+ return phoneAccountHandle;
+ }
+ }
+ return null;
+ }
+
/** Contains an implementation for {@link TelecomUtil} methods */
@VisibleForTesting()
public static class TelecomUtilImpl {
diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java
index bd5bb78c9..b3fb97fad 100644
--- a/java/com/android/incallui/CallButtonPresenter.java
+++ b/java/com/android/incallui/CallButtonPresenter.java
@@ -22,11 +22,14 @@ import android.os.Trace;
import android.support.v4.app.Fragment;
import android.support.v4.os.UserManagerCompat;
import android.telecom.CallAudioState;
+import android.telecom.PhoneAccountHandle;
import com.android.contacts.common.compat.CallCompat;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
+import com.android.dialer.telecom.TelecomUtil;
import com.android.incallui.InCallCameraManager.Listener;
import com.android.incallui.InCallPresenter.CanAddCallListener;
import com.android.incallui.InCallPresenter.InCallDetailsListener;
@@ -42,6 +45,7 @@ import com.android.incallui.call.TelecomAdapter;
import com.android.incallui.incall.protocol.InCallButtonIds;
import com.android.incallui.incall.protocol.InCallButtonUi;
import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
+import com.android.incallui.multisim.SwapSimWorker;
import com.android.incallui.videotech.utils.VideoUtils;
/** Logic for call buttons. */
@@ -63,6 +67,7 @@ public class CallButtonPresenter
private boolean mAutomaticallyMuted = false;
private boolean mPreviousMuteState = false;
private boolean isInCallButtonUiReady;
+ private PhoneAccountHandle mOtherAccount;
public CallButtonPresenter(Context context) {
mContext = context.getApplicationContext();
@@ -310,6 +315,23 @@ public class CallButtonPresenter
mInCallButtonUi.showAudioRouteSelector();
}
+ @Override
+ public void swapSimClicked() {
+ LogUtil.enterBlock("CallButtonPresenter.swapSimClicked");
+ SwapSimWorker worker =
+ new SwapSimWorker(
+ getContext(),
+ mCall,
+ InCallPresenter.getInstance().getCallList(),
+ mOtherAccount,
+ InCallPresenter.getInstance().acquireInCallUiLock("swapSim"));
+ DialerExecutorComponent.get(getContext())
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(worker)
+ .build()
+ .executeParallel(null);
+ }
+
/**
* Switches the camera between the front-facing and back-facing camera.
*
@@ -409,6 +431,7 @@ public class CallButtonPresenter
*
* @param call The active call.
*/
+ @SuppressWarnings("MissingPermission")
private void updateButtonsState(DialerCall call) {
LogUtil.v("CallButtonPresenter.updateButtonsState", "");
final boolean isVideo = call.isVideoCall();
@@ -439,11 +462,15 @@ public class CallButtonPresenter
&& call.getState() != DialerCall.State.DIALING
&& call.getState() != DialerCall.State.CONNECTING;
+ mOtherAccount = TelecomUtil.getOtherAccount(getContext(), call.getAccountHandle());
+ boolean showSwapSim = mOtherAccount != null && DialerCall.State.isDialing(call.getState());
+
mInCallButtonUi.showButton(InCallButtonIds.BUTTON_AUDIO, true);
mInCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP, showSwap);
mInCallButtonUi.showButton(InCallButtonIds.BUTTON_HOLD, showHold);
mInCallButtonUi.setHold(isCallOnHold);
mInCallButtonUi.showButton(InCallButtonIds.BUTTON_MUTE, showMute);
+ mInCallButtonUi.showButton(InCallButtonIds.BUTTON_SWAP_SIM, showSwapSim);
mInCallButtonUi.showButton(InCallButtonIds.BUTTON_ADD_CALL, true);
mInCallButtonUi.enableButton(InCallButtonIds.BUTTON_ADD_CALL, showAddCall);
mInCallButtonUi.showButton(InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
@@ -532,4 +559,5 @@ public class CallButtonPresenter
}
return null;
}
+
}
diff --git a/java/com/android/incallui/callpending/CallPendingActivity.java b/java/com/android/incallui/callpending/CallPendingActivity.java
index 7fc4caf5a..47325d823 100644
--- a/java/com/android/incallui/callpending/CallPendingActivity.java
+++ b/java/com/android/incallui/callpending/CallPendingActivity.java
@@ -279,6 +279,9 @@ public class CallPendingActivity extends FragmentActivity
public void showAudioRouteSelector() {}
@Override
+ public void swapSimClicked() {}
+
+ @Override
public Context getContext() {
return CallPendingActivity.this;
}
diff --git a/java/com/android/incallui/commontheme/res/values/strings.xml b/java/com/android/incallui/commontheme/res/values/strings.xml
index 94a8c901b..f366a867f 100644
--- a/java/com/android/incallui/commontheme/res/values/strings.xml
+++ b/java/com/android/incallui/commontheme/res/values/strings.xml
@@ -24,6 +24,8 @@
<string name="incall_content_description_swap_calls">Swap calls</string>
+ <string name="incall_content_description_swap_sim">Change SIM</string>
+
<string name="incall_content_description_merge_calls">Merge calls</string>
<string name="incall_content_description_earpiece">Handset earpiece</string>
diff --git a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
index 0f4a95d38..0d0a93256 100644
--- a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
+++ b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
@@ -112,6 +112,7 @@ class ButtonChooserFactory {
mapping.put(InCallButtonIds.BUTTON_AUDIO, MappingInfo.builder(2).build());
mapping.put(InCallButtonIds.BUTTON_MERGE, MappingInfo.builder(3).setSlotOrder(0).build());
mapping.put(InCallButtonIds.BUTTON_ADD_CALL, MappingInfo.builder(3).build());
+ mapping.put(InCallButtonIds.BUTTON_SWAP_SIM, MappingInfo.builder(4).build());
return mapping;
}
}
diff --git a/java/com/android/incallui/incall/impl/ButtonController.java b/java/com/android/incallui/incall/impl/ButtonController.java
index b7a47f08e..cefbd723b 100644
--- a/java/com/android/incallui/incall/impl/ButtonController.java
+++ b/java/com/android/incallui/incall/impl/ButtonController.java
@@ -560,4 +560,21 @@ interface ButtonController {
inCallScreenDelegate.onSecondaryInfoClicked();
}
}
+
+ class SwapSimButtonController extends SimpleNonCheckableButtonController {
+
+ public SwapSimButtonController(InCallButtonUiDelegate delegate) {
+ super(
+ delegate,
+ InCallButtonIds.BUTTON_SWAP_SIM,
+ R.string.incall_content_description_swap_sim,
+ R.string.incall_label_swap_sim,
+ R.drawable.quantum_ic_swap_calls_white_36);
+ }
+
+ @Override
+ public void onClick(View view) {
+ delegate.swapSimClicked();
+ }
+ }
}
diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java
index 13175656d..f0504bc56 100644
--- a/java/com/android/incallui/incall/impl/InCallFragment.java
+++ b/java/com/android/incallui/incall/impl/InCallFragment.java
@@ -110,7 +110,8 @@ public class InCallFragment extends Fragment
|| id == InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO
|| id == InCallButtonIds.BUTTON_ADD_CALL
|| id == InCallButtonIds.BUTTON_MERGE
- || id == InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE;
+ || id == InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE
+ || id == InCallButtonIds.BUTTON_SWAP_SIM;
}
@Override
@@ -198,6 +199,7 @@ public class InCallFragment extends Fragment
buttonControllers.add(new ButtonController.AddCallButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.SwapButtonController(inCallButtonUiDelegate));
buttonControllers.add(new ButtonController.MergeButtonController(inCallButtonUiDelegate));
+ buttonControllers.add(new ButtonController.SwapSimButtonController(inCallButtonUiDelegate));
buttonControllers.add(
new ButtonController.UpgradeToVideoButtonController(inCallButtonUiDelegate));
buttonControllers.add(
diff --git a/java/com/android/incallui/incall/impl/res/values/strings.xml b/java/com/android/incallui/incall/impl/res/values/strings.xml
index 2b30dfa53..d0217566a 100644
--- a/java/com/android/incallui/incall/impl/res/values/strings.xml
+++ b/java/com/android/incallui/incall/impl/res/values/strings.xml
@@ -61,6 +61,10 @@
<string name="incall_label_swap">Swap</string>
+ <!-- Button shown during a call with a multi-SIM device to hang up and call with the other SIM
+ instead. [CHAR LIMIT=12] -->
+ <string name="incall_label_swap_sim">Change SIM</string>
+
<!-- Used to inform the user that the note associated with an outgoing call has been sent.
[CHAR LIMIT=32] -->
<string name="incall_note_sent">Note sent</string>
diff --git a/java/com/android/incallui/incall/protocol/InCallButtonIds.java b/java/com/android/incallui/incall/protocol/InCallButtonIds.java
index 50ebc6413..3de533519 100644
--- a/java/com/android/incallui/incall/protocol/InCallButtonIds.java
+++ b/java/com/android/incallui/incall/protocol/InCallButtonIds.java
@@ -37,6 +37,7 @@ import java.lang.annotation.RetentionPolicy;
InCallButtonIds.BUTTON_MANAGE_VIDEO_CONFERENCE,
InCallButtonIds.BUTTON_MANAGE_VOICE_CONFERENCE,
InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY,
+ InCallButtonIds.BUTTON_SWAP_SIM,
InCallButtonIds.BUTTON_COUNT,
})
public @interface InCallButtonIds {
@@ -55,5 +56,6 @@ public @interface InCallButtonIds {
int BUTTON_MANAGE_VIDEO_CONFERENCE = 11;
int BUTTON_MANAGE_VOICE_CONFERENCE = 12;
int BUTTON_SWITCH_TO_SECONDARY = 13;
- int BUTTON_COUNT = 14;
+ int BUTTON_SWAP_SIM = 14;
+ int BUTTON_COUNT = 15;
}
diff --git a/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java b/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java
index 6d802e346..db6e9009c 100644
--- a/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java
+++ b/java/com/android/incallui/incall/protocol/InCallButtonIdsExtension.java
@@ -54,6 +54,8 @@ public class InCallButtonIdsExtension {
return "MANAGE_VOICE_CONFERENCE";
} else if (id == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) {
return "SWITCH_TO_SECONDARY";
+ } else if (id == InCallButtonIds.BUTTON_SWAP_SIM) {
+ return "SWAP_SIM";
} else {
return "INVALID_BUTTON: " + id;
}
diff --git a/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java b/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java
index e02ada96d..9f9c5fb03 100644
--- a/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java
+++ b/java/com/android/incallui/incall/protocol/InCallButtonUiDelegate.java
@@ -63,5 +63,7 @@ public interface InCallButtonUiDelegate {
void showAudioRouteSelector();
+ void swapSimClicked();
+
Context getContext();
}
diff --git a/java/com/android/incallui/multisim/SwapSimWorker.java b/java/com/android/incallui/multisim/SwapSimWorker.java
new file mode 100644
index 000000000..73c18c442
--- /dev/null
+++ b/java/com/android/incallui/multisim/SwapSimWorker.java
@@ -0,0 +1,202 @@
+/*
+ * 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.incallui.multisim;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.MainThread;
+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 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.ThreadUtil;
+import com.android.dialer.util.PermissionsUtil;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall;
+import com.android.incallui.call.DialerCallListener;
+import com.android.incallui.incalluilock.InCallUiLock;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Hangs up the current call and redial the call using the {@code otherAccount} instead. the in call
+ * ui will be prevented from closing until the process has finished.
+ */
+public class SwapSimWorker implements Worker<Void, Void>, DialerCallListener, CallList.Listener {
+
+ // Timeout waiting for the call to hangup or redial.
+ private static final int DEFAULT_TIMEOUT_MILLIS = 5_000;
+
+ private final Context context;
+ private final DialerCall call;
+ private final CallList callList;
+ private final InCallUiLock inCallUiLock;
+
+ private final CountDownLatch disconnectLatch = new CountDownLatch(1);
+ private final CountDownLatch dialingLatch = new CountDownLatch(1);
+
+ private final PhoneAccountHandle otherAccount;
+ private final String number;
+
+ private final int timeoutMillis;
+
+ private CountDownLatch latchForTest;
+
+ @MainThread
+ public SwapSimWorker(
+ Context context,
+ DialerCall call,
+ CallList callList,
+ PhoneAccountHandle otherAccount,
+ InCallUiLock lock) {
+ this(context, call, callList, otherAccount, lock, DEFAULT_TIMEOUT_MILLIS);
+ }
+
+ @VisibleForTesting
+ SwapSimWorker(
+ Context context,
+ DialerCall call,
+ CallList callList,
+ PhoneAccountHandle otherAccount,
+ InCallUiLock lock,
+ int timeoutMillis) {
+ Assert.isMainThread();
+ this.context = context;
+ this.call = call;
+ this.callList = callList;
+ this.otherAccount = otherAccount;
+ inCallUiLock = lock;
+ this.timeoutMillis = timeoutMillis;
+ number = call.getNumber();
+ call.addListener(this);
+ call.disconnect();
+ }
+
+ @WorkerThread
+ @Nullable
+ @Override
+ @SuppressWarnings("MissingPermission")
+ public Void doInBackground(Void unused) {
+ try {
+ if (!PermissionsUtil.hasPhonePermissions(context)) {
+ LogUtil.e("SwapSimWorker.doInBackground", "missing phone permission");
+ return null;
+ }
+ if (!disconnectLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
+ LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to disconnect");
+ return null;
+ }
+ LogUtil.i("SwapSimWorker.doInBackground", "call disconnected, redialing");
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ Bundle extras = new Bundle();
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, otherAccount);
+ callList.addListener(this);
+ telecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras);
+ if (latchForTest != null) {
+ latchForTest.countDown();
+ }
+ if (!dialingLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
+ LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to dial");
+ }
+ return null;
+ } catch (InterruptedException e) {
+ LogUtil.e("SwapSimWorker.doInBackground", "interrupted", e);
+ Thread.currentThread().interrupt();
+ return null;
+ } finally {
+ ThreadUtil.postOnUiThread(
+ () -> {
+ call.removeListener(this);
+ callList.removeListener(this);
+ inCallUiLock.release();
+ });
+ }
+ }
+
+ @MainThread
+ @Override
+ public void onDialerCallDisconnect() {
+ disconnectLatch.countDown();
+ }
+
+ @Override
+ public void onCallListChange(CallList callList) {
+ if (callList.getOutgoingCall() != null) {
+ dialingLatch.countDown();
+ }
+ }
+
+ @VisibleForTesting
+ void setLatchForTest(CountDownLatch latch) {
+ latchForTest = latch;
+ }
+
+ @Override
+ public void onDialerCallUpdate() {}
+
+ @Override
+ public void onDialerCallChildNumberChange() {}
+
+ @Override
+ public void onDialerCallLastForwardedNumberChange() {}
+
+ @Override
+ public void onDialerCallUpgradeToVideo() {}
+
+ @Override
+ public void onDialerCallSessionModificationStateChange() {}
+
+ @Override
+ public void onWiFiToLteHandover() {}
+
+ @Override
+ public void onHandoverToWifiFailure() {}
+
+ @Override
+ public void onInternationalCallOnWifi() {}
+
+ @Override
+ public void onEnrichedCallSessionUpdate() {}
+
+ @Override
+ public void onIncomingCall(DialerCall call) {}
+
+ @Override
+ public void onUpgradeToVideo(DialerCall call) {}
+
+ @Override
+ public void onSessionModificationStateChange(DialerCall call) {}
+
+ @Override
+ public void onDisconnect(DialerCall call) {}
+
+ @Override
+ public void onWiFiToLteHandover(DialerCall call) {}
+
+ @Override
+ public void onHandoverToWifiFailed(DialerCall call) {}
+
+ @Override
+ public void onInternationalCallOnWifi(@NonNull DialerCall call) {}
+}