summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java5
-rw-r--r--java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java2
-rw-r--r--java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java2
-rw-r--r--java/com/android/dialer/calldetails/proto/call_details_header_info.proto2
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java1
-rw-r--r--java/com/android/dialer/calllog/ui/menu/BottomSheetHeader.java1
-rw-r--r--java/com/android/dialer/calllog/ui/menu/Modules.java188
-rw-r--r--java/com/android/dialer/configprovider/SharedPrefConfigProvider.java4
-rw-r--r--java/com/android/dialer/historyitemactions/BlockReportSpamModules.java120
-rw-r--r--java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java422
-rw-r--r--java/com/android/dialer/historyitemactions/IntentModule.java9
-rw-r--r--java/com/android/dialer/historyitemactions/SharedModules.java247
-rw-r--r--java/com/android/dialer/historyitemactions/history_item_action_module_info.proto69
-rw-r--r--java/com/android/dialer/historyitemactions/history_item_bottom_sheet_header_info.proto2
-rw-r--r--java/com/android/dialer/speeddial/SpeedDialFragment.java11
-rw-r--r--java/com/android/dialer/voicemail/listui/menu/Modules.java96
-rw-r--r--java/com/android/incallui/InCallActivity.java3
-rw-r--r--java/com/android/incallui/InCallServiceImpl.java3
-rw-r--r--java/com/android/incallui/audiomode/BluetoothDeviceProvider.java203
-rw-r--r--java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java39
-rw-r--r--java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java43
-rw-r--r--java/com/android/incallui/call/DialerCall.java38
-rw-r--r--java/com/android/incallui/call/TelecomAdapter.java11
-rw-r--r--java/com/android/incallui/rtt/impl/AudioSelectMenu.java36
-rw-r--r--java/com/android/incallui/rtt/impl/RttChatFragment.java6
-rw-r--r--java/com/android/incallui/speakeasy/SpeakEasyCallManager.java12
-rw-r--r--java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java15
-rw-r--r--java/com/android/incallui/speakeasy/runtime/Constraints.java74
28 files changed, 817 insertions, 847 deletions
diff --git a/java/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java b/java/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
index bb0d3c625..ca8ed29ec 100644
--- a/java/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
+++ b/java/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
@@ -23,11 +23,6 @@ import java.lang.reflect.Field;
/** Compatibility class for {@link android.telecom.TelecomManager}. */
public class TelecomManagerCompat {
- // TODO(mdooley): remove once this is available in android.telecom.Call
- // a bug
- public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
- "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
-
// Constants from http://cs/android/frameworks/base/telecomm/java/android/telecom/Call.java.
public static final String EVENT_REQUEST_HANDOVER = "android.telecom.event.REQUEST_HANDOVER";
public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE =
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index 5fed683f6..cad2eb7e0 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -41,7 +41,6 @@ import com.android.dialer.spam.SpamComponent;
import com.android.dialer.speeddial.loader.UiItemLoaderComponent;
import com.android.dialer.storage.StorageComponent;
import com.android.dialer.strictmode.StrictModeComponent;
-import com.android.incallui.audiomode.BluetoothDeviceProviderComponent;
import com.android.incallui.calllocation.CallLocationComponent;
import com.android.incallui.maps.MapsComponent;
import com.android.incallui.speakeasy.SpeakEasyComponent;
@@ -53,7 +52,6 @@ import com.android.voicemail.VoicemailComponent;
*/
public interface BaseDialerRootComponent
extends ActiveCallsComponent.HasComponent,
- BluetoothDeviceProviderComponent.HasComponent,
BubbleComponent.HasComponent,
CallLocationComponent.HasComponent,
CallLogComponent.HasComponent,
diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
index cb84a28c2..cd1752d74 100644
--- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
@@ -192,8 +192,6 @@ public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder
nameView.setText(headerInfo.getPrimaryText());
numberView.setText(headerInfo.getSecondaryText());
- // TODO(a bug): show SIM info in the TextView returned by getNetworkView().
-
setCallbackAction(callbackAction);
}
diff --git a/java/com/android/dialer/calldetails/proto/call_details_header_info.proto b/java/com/android/dialer/calldetails/proto/call_details_header_info.proto
index ea7ba1e72..e2532d504 100644
--- a/java/com/android/dialer/calldetails/proto/call_details_header_info.proto
+++ b/java/com/android/dialer/calldetails/proto/call_details_header_info.proto
@@ -31,6 +31,4 @@ message CallDetailsHeaderInfo {
// "Blocked • Mobile • 555-1234", and
// "Spam • Mobile • 555-1234".
optional string secondary_text = 4;
-
- // TODO(a bug): Add SIM info.
} \ No newline at end of file
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index 44a08c75e..c02d80ede 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -268,6 +268,7 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder {
private void setOnClickListenerForRow(CoalescedRow row) {
if (!PhoneNumberHelper.canPlaceCallsTo(
row.getNumber().getNormalizedNumber(), row.getNumberPresentation())) {
+ itemView.setOnClickListener(null);
return;
}
itemView.setOnClickListener(view -> CallLogRowActions.startCallForRow(activity, row));
diff --git a/java/com/android/dialer/calllog/ui/menu/BottomSheetHeader.java b/java/com/android/dialer/calllog/ui/menu/BottomSheetHeader.java
index d87888d34..4e25cedf4 100644
--- a/java/com/android/dialer/calllog/ui/menu/BottomSheetHeader.java
+++ b/java/com/android/dialer/calllog/ui/menu/BottomSheetHeader.java
@@ -34,6 +34,7 @@ final class BottomSheetHeader {
NumberAttributesConverter.toPhotoInfoBuilder(row.getNumberAttributes())
.setFormattedNumber(row.getFormattedNumber())
.setIsVideo((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)
+ .setIsVoicemail(row.getIsVoicemailCall())
.setIsRtt(
BuildCompat.isAtLeastP()
&& (row.getFeatures() & Calls.FEATURES_RTT) == Calls.FEATURES_RTT)
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index a56d6d5e2..b06e0fb1a 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -20,152 +20,53 @@ import android.content.Context;
import android.provider.CallLog.Calls;
import android.support.v4.os.BuildCompat;
import android.text.TextUtils;
-import com.android.dialer.blockreportspam.BlockReportSpamDialogInfo;
import com.android.dialer.calldetails.CallDetailsActivity;
import com.android.dialer.calldetails.CallDetailsHeaderInfo;
-import com.android.dialer.callintent.CallInitiationType;
-import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.calllog.model.CoalescedRow;
import com.android.dialer.calllogutils.CallLogEntryText;
import com.android.dialer.calllogutils.NumberAttributesConverter;
-import com.android.dialer.duo.Duo;
-import com.android.dialer.duo.DuoComponent;
import com.android.dialer.glidephotomanager.PhotoInfo;
-import com.android.dialer.historyitemactions.DividerModule;
-import com.android.dialer.historyitemactions.DuoCallModule;
import com.android.dialer.historyitemactions.HistoryItemActionModule;
+import com.android.dialer.historyitemactions.HistoryItemActionModuleInfo;
+import com.android.dialer.historyitemactions.HistoryItemActionModulesBuilder;
import com.android.dialer.historyitemactions.IntentModule;
-import com.android.dialer.historyitemactions.SharedModules;
-import com.android.dialer.logging.ReportingLocation;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
-import com.android.dialer.util.CallUtil;
-import com.google.common.base.Optional;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
- * Configures the modules for the bottom sheet; these are the rows below the top row (primary
- * action) in the bottom sheet.
+ * Configures the modules for the bottom sheet; these are the rows below the top row (contact info)
+ * in the bottom sheet.
*/
-@SuppressWarnings("Guava")
final class Modules {
+ /**
+ * Returns a list of {@link HistoryItemActionModule HistoryItemActionModules}, which are items in
+ * the bottom sheet.
+ */
static List<HistoryItemActionModule> fromRow(Context context, CoalescedRow row) {
- // Conditionally add each module, which are items in the bottom sheet's menu.
- List<HistoryItemActionModule> modules = new ArrayList<>();
-
- String normalizedNumber = row.getNumber().getNormalizedNumber();
- boolean canPlaceCalls =
- PhoneNumberHelper.canPlaceCallsTo(normalizedNumber, row.getNumberPresentation());
-
- if (canPlaceCalls) {
- modules.addAll(createModulesForCalls(context, row, normalizedNumber));
- Optional<HistoryItemActionModule> moduleForSendingTextMessage =
- SharedModules.createModuleForSendingTextMessage(
- context, normalizedNumber, row.getNumberAttributes().getIsBlocked());
- if (moduleForSendingTextMessage.isPresent()) {
- modules.add(moduleForSendingTextMessage.get());
- }
- }
-
- if (!modules.isEmpty()) {
- modules.add(new DividerModule());
- }
+ HistoryItemActionModulesBuilder modulesBuilder =
+ new HistoryItemActionModulesBuilder(context, buildModuleInfo(row));
// TODO(zachh): Module for CallComposer.
- if (canPlaceCalls) {
- Optional<HistoryItemActionModule> moduleForAddingToContacts =
- SharedModules.createModuleForAddingToContacts(
- context,
- row.getNumber(),
- row.getNumberAttributes().getName(),
- row.getNumberAttributes().getLookupUri(),
- row.getNumberAttributes().getIsBlocked(),
- row.getNumberAttributes().getIsSpam());
- if (moduleForAddingToContacts.isPresent()) {
- modules.add(moduleForAddingToContacts.get());
- }
-
- BlockReportSpamDialogInfo blockReportSpamDialogInfo =
- BlockReportSpamDialogInfo.newBuilder()
- .setNormalizedNumber(row.getNumber().getNormalizedNumber())
- .setCountryIso(row.getNumber().getCountryIso())
- .setCallType(row.getCallType())
- .setReportingLocation(ReportingLocation.Type.CALL_LOG_HISTORY)
- .setContactSource(row.getNumberAttributes().getContactSource())
- .build();
- modules.addAll(
- SharedModules.createModulesHandlingBlockedOrSpamNumber(
- context,
- blockReportSpamDialogInfo,
- row.getNumberAttributes().getIsBlocked(),
- row.getNumberAttributes().getIsSpam()));
-
- Optional<HistoryItemActionModule> moduleForCopyingNumber =
- SharedModules.createModuleForCopyingNumber(context, normalizedNumber);
- if (moduleForCopyingNumber.isPresent()) {
- modules.add(moduleForCopyingNumber.get());
- }
+ if (PhoneNumberHelper.canPlaceCallsTo(
+ row.getNumber().getNormalizedNumber(), row.getNumberPresentation())) {
+ modulesBuilder
+ .addModuleForVoiceCall()
+ .addModuleForVideoCall()
+ .addModuleForSendingTextMessage()
+ .addModuleForDivider()
+ .addModuleForAddingToContacts()
+ .addModuleForBlockedOrSpamNumber()
+ .addModuleForCopyingNumber();
}
- modules.add(createModuleForAccessingCallDetails(context, row));
+ List<HistoryItemActionModule> modules = modulesBuilder.build();
+ // Add modules only available in the call log.
+ modules.add(createModuleForAccessingCallDetails(context, row));
modules.add(new DeleteCallLogItemModule(context, row.getCoalescedIds()));
-
- return modules;
- }
-
- private static List<HistoryItemActionModule> createModulesForCalls(
- Context context, CoalescedRow row, String normalizedNumber) {
- // Don't add call options if a number is blocked.
- if (row.getNumberAttributes().getIsBlocked()) {
- return Collections.emptyList();
- }
-
- boolean isDuoCall =
- DuoComponent.get(context).getDuo().isDuoAccount(row.getPhoneAccountComponentName());
-
- List<HistoryItemActionModule> modules = new ArrayList<>();
-
- // Add an audio call item
- // TODO(zachh): Support post-dial digits; consider using DialerPhoneNumber.
- CallIntentBuilder callIntentBuilder =
- new CallIntentBuilder(normalizedNumber, CallInitiationType.Type.CALL_LOG)
- .setAllowAssistedDial(canSupportAssistedDialing(row));
- // Leave PhoneAccountHandle blank so regular PreCall logic will be used. The account the call
- // was made/received in should be ignored for audio and carrier video calls.
- // TODO(a bug): figure out the correct video call behavior
- modules.add(IntentModule.newCallModule(context, callIntentBuilder));
-
- // If the call log entry is for a spam call, nothing more to be done.
- if (row.getNumberAttributes().getIsSpam()) {
- return modules;
- }
-
- // If the call log entry is for a video call, add the corresponding video call options.
- // Note that if the entry is for a Duo video call but Duo is not available, we will fall back to
- // a carrier video call.
- if ((row.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
- modules.add(
- isDuoCall && canPlaceDuoCall(context, normalizedNumber)
- ? new DuoCallModule(context, normalizedNumber)
- : IntentModule.newCallModule(context, callIntentBuilder.setIsVideoCall(true)));
- return modules;
- }
-
- // At this point, the call log entry is for an audio call. We will also show a video call option
- // if the video capability is present.
- //
- // The carrier video call option takes precedence over Duo.
- if (canPlaceCarrierVideoCall(context, row)) {
- modules.add(IntentModule.newCallModule(context, callIntentBuilder.setIsVideoCall(true)));
- } else if (canPlaceDuoCall(context, normalizedNumber)) {
- modules.add(new DuoCallModule(context, normalizedNumber));
- }
-
return modules;
}
@@ -208,30 +109,27 @@ final class Modules {
.build();
}
- private static boolean canPlaceDuoCall(Context context, String phoneNumber) {
- Duo duo = DuoComponent.get(context).getDuo();
-
- return duo.isInstalled(context)
- && duo.isEnabled(context)
- && duo.isActivated(context)
- && duo.isReachable(context, phoneNumber);
- }
-
- private static boolean canPlaceCarrierVideoCall(Context context, CoalescedRow row) {
- int carrierVideoAvailability = CallUtil.getVideoCallingAvailability(context);
- boolean isCarrierVideoCallingEnabled =
- ((carrierVideoAvailability & CallUtil.VIDEO_CALLING_ENABLED)
- == CallUtil.VIDEO_CALLING_ENABLED);
- boolean canRelyOnCarrierVideoPresence =
- ((carrierVideoAvailability & CallUtil.VIDEO_CALLING_PRESENCE)
- == CallUtil.VIDEO_CALLING_PRESENCE);
-
- return isCarrierVideoCallingEnabled
- && canRelyOnCarrierVideoPresence
- && row.getNumberAttributes().getCanSupportCarrierVideoCall();
- }
-
private static boolean canSupportAssistedDialing(CoalescedRow row) {
return !TextUtils.isEmpty(row.getNumberAttributes().getLookupUri());
}
+
+ private static HistoryItemActionModuleInfo buildModuleInfo(CoalescedRow row) {
+ return HistoryItemActionModuleInfo.newBuilder()
+ .setNormalizedNumber(row.getNumber().getNormalizedNumber())
+ .setCountryIso(row.getNumber().getCountryIso())
+ .setName(row.getNumberAttributes().getName())
+ .setCallType(row.getCallType())
+ .setFeatures(row.getFeatures())
+ .setLookupUri(row.getNumberAttributes().getLookupUri())
+ .setPhoneAccountComponentName(row.getPhoneAccountComponentName())
+ .setCanReportAsInvalidNumber(row.getNumberAttributes().getCanReportAsInvalidNumber())
+ .setCanSupportAssistedDialing(canSupportAssistedDialing(row))
+ .setCanSupportCarrierVideoCall(row.getNumberAttributes().getCanSupportCarrierVideoCall())
+ .setIsBlocked(row.getNumberAttributes().getIsBlocked())
+ .setIsSpam(row.getNumberAttributes().getIsSpam())
+ .setIsVoicemailCall(row.getIsVoicemailCall())
+ .setContactSource(row.getNumberAttributes().getContactSource())
+ .setHost(HistoryItemActionModuleInfo.Host.CALL_LOG)
+ .build();
+ }
}
diff --git a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
index ce95c5700..54e9c9aff 100644
--- a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
+++ b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
@@ -98,6 +98,10 @@ public class SharedPrefConfigProvider implements ConfigProvider {
sharedPreferences.edit().putBoolean(PREF_PREFIX + key, value).apply();
}
+ public void putLong(String key, long value) {
+ sharedPreferences.edit().putLong(PREF_PREFIX + key, value).apply();
+ }
+
@Override
public String getString(String key, String defaultValue) {
// Reading shared prefs on the main thread is generally safe since a single instance is cached.
diff --git a/java/com/android/dialer/historyitemactions/BlockReportSpamModules.java b/java/com/android/dialer/historyitemactions/BlockReportSpamModules.java
new file mode 100644
index 000000000..396c03376
--- /dev/null
+++ b/java/com/android/dialer/historyitemactions/BlockReportSpamModules.java
@@ -0,0 +1,120 @@
+/*
+ * 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.historyitemactions;
+
+import android.content.Context;
+import com.android.dialer.blockreportspam.BlockReportSpamDialogInfo;
+import com.android.dialer.blockreportspam.ShowBlockReportSpamDialogNotifier;
+
+/** Modules for blocking/unblocking a number and/or reporting it as spam/not spam. */
+final class BlockReportSpamModules {
+
+ private BlockReportSpamModules() {}
+
+ static HistoryItemActionModule moduleForMarkingNumberAsNotSpam(
+ Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo) {
+
+ return new HistoryItemActionModule() {
+ @Override
+ public int getStringId() {
+ return R.string.not_spam;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.quantum_ic_report_off_vd_theme_24;
+ }
+
+ @Override
+ public boolean onClick() {
+ ShowBlockReportSpamDialogNotifier.notifyShowDialogToReportNotSpam(
+ context, blockReportSpamDialogInfo);
+ return true; // Close the bottom sheet.
+ }
+ };
+ }
+
+ static HistoryItemActionModule moduleForBlockingNumber(
+ Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo) {
+
+ return new HistoryItemActionModule() {
+ @Override
+ public int getStringId() {
+ return R.string.block_number;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.quantum_ic_block_vd_theme_24;
+ }
+
+ @Override
+ public boolean onClick() {
+ ShowBlockReportSpamDialogNotifier.notifyShowDialogToBlockNumber(
+ context, blockReportSpamDialogInfo);
+ return true; // Close the bottom sheet.
+ }
+ };
+ }
+
+ static HistoryItemActionModule moduleForUnblockingNumber(
+ Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo) {
+
+ return new HistoryItemActionModule() {
+ @Override
+ public int getStringId() {
+ return R.string.unblock_number;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.quantum_ic_unblock_vd_theme_24;
+ }
+
+ @Override
+ public boolean onClick() {
+ ShowBlockReportSpamDialogNotifier.notifyShowDialogToUnblockNumber(
+ context, blockReportSpamDialogInfo);
+
+ return true; // Close the bottom sheet.
+ }
+ };
+ }
+
+ static HistoryItemActionModule moduleForBlockingNumberAndOptionallyReportingSpam(
+ Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo) {
+
+ return new HistoryItemActionModule() {
+ @Override
+ public int getStringId() {
+ return R.string.block_and_optionally_report_spam;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.quantum_ic_block_vd_theme_24;
+ }
+
+ @Override
+ public boolean onClick() {
+ ShowBlockReportSpamDialogNotifier.notifyShowDialogToBlockNumberAndOptionallyReportSpam(
+ context, blockReportSpamDialogInfo);
+ return true; // Close the bottom sheet.
+ }
+ };
+ }
+}
diff --git a/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java b/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java
new file mode 100644
index 000000000..9af08be50
--- /dev/null
+++ b/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java
@@ -0,0 +1,422 @@
+/*
+ * 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.historyitemactions;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import com.android.dialer.blockreportspam.BlockReportSpamDialogInfo;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.clipboard.ClipboardUtils;
+import com.android.dialer.common.Assert;
+import com.android.dialer.duo.Duo;
+import com.android.dialer.duo.DuoComponent;
+import com.android.dialer.logging.ReportingLocation;
+import com.android.dialer.util.CallUtil;
+import com.android.dialer.util.UriUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builds a list of {@link HistoryItemActionModule HistoryItemActionModules}.
+ *
+ * <p>Example usage:
+ *
+ * <pre><code>
+ * // Create a HistoryItemActionModuleInfo proto with the information you have.
+ * // You can simply skip a field if there is no information for it.
+ * HistoryItemActionModuleInfo moduleInfo =
+ * HistoryItemActionModuleInfo.newBuilder()
+ * .setNormalizedNumber("+16502530000")
+ * .setCountryIso("US")
+ * .setName("Google")
+ * .build();
+ *
+ * // Initialize the builder using the module info above.
+ * // Note that some modules require an activity context to work so it is preferred to pass one
+ * // instead of an application context to the builder.
+ * HistoryItemActionModulesBuilder modulesBuilder =
+ * new HistoryItemActionModulesBuilder(activityContext, moduleInfo);
+ *
+ * // Add all modules you want in the order you like.
+ * // If a module shouldn't be added according to the module info, it won't be.
+ * // For example, if the module info is not for a video call and doesn't indicate the presence
+ * // of video calling capabilities, calling addModuleForVideoCall() is a no-op.
+ * modulesBuilder
+ * .addModuleForVoiceCall()
+ * .addModuleForVideoCall()
+ * .addModuleForSendingTextMessage()
+ * .addModuleForDivider()
+ * .addModuleForAddingToContacts()
+ * .addModuleForBlockedOrSpamNumber()
+ * .addModuleForCopyingNumber();
+ *
+ * List<HistoryItemActionModule> modules = modulesBuilder.build();
+ * </code></pre>
+ */
+public final class HistoryItemActionModulesBuilder {
+
+ private final Context context;
+ private final HistoryItemActionModuleInfo moduleInfo;
+ private final List<HistoryItemActionModule> modules;
+
+ public HistoryItemActionModulesBuilder(Context context, HistoryItemActionModuleInfo moduleInfo) {
+ Assert.checkArgument(
+ moduleInfo.getHost() != HistoryItemActionModuleInfo.Host.UNKNOWN,
+ "A host must be specified.");
+
+ this.context = context;
+ this.moduleInfo = moduleInfo;
+ this.modules = new ArrayList<>();
+ }
+
+ public List<HistoryItemActionModule> build() {
+ return new ArrayList<>(modules);
+ }
+
+ /**
+ * Adds a module for placing a voice call.
+ *
+ * <p>The method is a no-op if the number is blocked.
+ */
+ public HistoryItemActionModulesBuilder addModuleForVoiceCall() {
+ if (moduleInfo.getIsBlocked()) {
+ return this;
+ }
+
+ // TODO(zachh): Support post-dial digits; consider using DialerPhoneNumber.
+ // Do not set PhoneAccountHandle so that regular PreCall logic will be used. The account used to
+ // place or receive the call should be ignored for voice calls.
+ CallIntentBuilder callIntentBuilder =
+ new CallIntentBuilder(moduleInfo.getNormalizedNumber(), getCallInitiationType())
+ .setAllowAssistedDial(moduleInfo.getCanSupportAssistedDialing());
+ modules.add(IntentModule.newCallModule(context, callIntentBuilder));
+ return this;
+ }
+
+ /**
+ * Adds a module for a carrier video call *or* a Duo video call.
+ *
+ * <p>This method is a no-op if
+ *
+ * <ul>
+ * <li>the call is one made to a voicemail box,
+ * <li>the number is blocked, or
+ * <li>the number is marked as spam.
+ * </ul>
+ *
+ * <p>If the provided module info is for a Duo video call and Duo is available, add a Duo video
+ * call module.
+ *
+ * <p>If the provided module info is for a Duo video call but Duo is unavailable, add a carrier
+ * video call module.
+ *
+ * <p>If the provided module info is for a carrier video call, add a carrier video call module.
+ *
+ * <p>If the provided module info is for a voice call and the device has carrier video call
+ * capability, add a carrier video call module.
+ *
+ * <p>If the provided module info is for a voice call, the device doesn't have carrier video call
+ * capability, and Duo is available, add a Duo video call module.
+ */
+ public HistoryItemActionModulesBuilder addModuleForVideoCall() {
+ if (moduleInfo.getIsVoicemailCall() || moduleInfo.getIsBlocked() || moduleInfo.getIsSpam()) {
+ return this;
+ }
+
+ // Do not set PhoneAccountHandle so that regular PreCall logic will be used. The account used to
+ // place or receive the call should be ignored for carrier video calls.
+ // TODO(a bug): figure out the correct video call behavior
+ HistoryItemActionModule carrierVideoCallModule =
+ IntentModule.newCallModule(
+ context,
+ new CallIntentBuilder(moduleInfo.getNormalizedNumber(), getCallInitiationType())
+ .setAllowAssistedDial(moduleInfo.getCanSupportAssistedDialing())
+ .setIsVideoCall(true));
+ HistoryItemActionModule duoVideoCallModule =
+ new DuoCallModule(context, moduleInfo.getNormalizedNumber());
+
+ // If the module info is for a video call, add an appropriate video call module.
+ if ((moduleInfo.getFeatures() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
+ modules.add(isDuoCall() && canPlaceDuoCall() ? duoVideoCallModule : carrierVideoCallModule);
+ return this;
+ }
+
+ // At this point, the module info is for an audio call. We will also add a video call module if
+ // the video capability is present.
+ //
+ // The carrier video call module takes precedence over the Duo module.
+ if (canPlaceCarrierVideoCall()) {
+ modules.add(carrierVideoCallModule);
+ } else if (canPlaceDuoCall()) {
+ modules.add(duoVideoCallModule);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a module for sending text messages.
+ *
+ * <p>The method is a no-op if
+ *
+ * <ul>
+ * <li>the call is one made to a voicemail box,
+ * <li>the number is blocked, or
+ * <li>the number is empty.
+ * </ul>
+ */
+ public HistoryItemActionModulesBuilder addModuleForSendingTextMessage() {
+ // TODO(zachh): There are other conditions where this module should not be shown
+ // (e.g., business numbers).
+ if (moduleInfo.getIsVoicemailCall()
+ || moduleInfo.getIsBlocked()
+ || TextUtils.isEmpty(moduleInfo.getNormalizedNumber())) {
+ return this;
+ }
+
+ modules.add(
+ IntentModule.newModuleForSendingTextMessage(context, moduleInfo.getNormalizedNumber()));
+ return this;
+ }
+
+ /**
+ * Adds a module for a divider.
+ *
+ * <p>The method is a no-op if the divider module will be the first module.
+ */
+ public HistoryItemActionModulesBuilder addModuleForDivider() {
+ if (modules.isEmpty()) {
+ return this;
+ }
+
+ modules.add(new DividerModule());
+ return this;
+ }
+
+ /**
+ * Adds a module for adding a number to Contacts.
+ *
+ * <p>The method is a no-op if
+ *
+ * <ul>
+ * <li>the call is one made to a voicemail box,
+ * <li>the number is blocked,
+ * <li>the number is marked as spam,
+ * <li>the number is empty, or
+ * <li>the number belongs to an existing contact.
+ * </ul>
+ */
+ public HistoryItemActionModulesBuilder addModuleForAddingToContacts() {
+ if (moduleInfo.getIsVoicemailCall()
+ || moduleInfo.getIsBlocked()
+ || moduleInfo.getIsSpam()
+ || isExistingContact()
+ || TextUtils.isEmpty(moduleInfo.getNormalizedNumber())) {
+ return this;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
+ intent.putExtra(ContactsContract.Intents.Insert.PHONE, moduleInfo.getNormalizedNumber());
+
+ if (!TextUtils.isEmpty(moduleInfo.getName())) {
+ intent.putExtra(ContactsContract.Intents.Insert.NAME, moduleInfo.getName());
+ }
+
+ modules.add(
+ new IntentModule(
+ context,
+ intent,
+ R.string.add_to_contacts,
+ R.drawable.quantum_ic_person_add_vd_theme_24));
+ return this;
+ }
+
+ /**
+ * Add modules for blocking/unblocking a number and/or marking it as spam/not spam.
+ *
+ * <p>The method is a no-op if the call is one made to a voicemail box.
+ *
+ * <p>If a number is marked as spam, add two modules:
+ *
+ * <ul>
+ * <li>"Not spam" and "Block", or
+ * <li>"Not spam" and "Unblock".
+ * </ul>
+ *
+ * <p>If a number is blocked but not marked as spam, add the "Unblock" module.
+ *
+ * <p>If a number is not blocked or marked as spam, add the "Block/Report spam" module.
+ */
+ public HistoryItemActionModulesBuilder addModuleForBlockedOrSpamNumber() {
+ if (moduleInfo.getIsVoicemailCall()) {
+ return this;
+ }
+
+ BlockReportSpamDialogInfo blockReportSpamDialogInfo =
+ BlockReportSpamDialogInfo.newBuilder()
+ .setNormalizedNumber(moduleInfo.getNormalizedNumber())
+ .setCountryIso(moduleInfo.getCountryIso())
+ .setCallType(moduleInfo.getCallType())
+ .setReportingLocation(getReportingLocation())
+ .setContactSource(moduleInfo.getContactSource())
+ .build();
+
+ // For a spam number, add two modules:
+ // (1) "Not spam" and "Block", or
+ // (2) "Not spam" and "Unblock".
+ if (moduleInfo.getIsSpam()) {
+ modules.add(
+ BlockReportSpamModules.moduleForMarkingNumberAsNotSpam(
+ context, blockReportSpamDialogInfo));
+ modules.add(
+ moduleInfo.getIsBlocked()
+ ? BlockReportSpamModules.moduleForUnblockingNumber(context, blockReportSpamDialogInfo)
+ : BlockReportSpamModules.moduleForBlockingNumber(context, blockReportSpamDialogInfo));
+ return this;
+ }
+
+ // For a blocked non-spam number, add the "Unblock" module.
+ if (moduleInfo.getIsBlocked()) {
+ modules.add(
+ BlockReportSpamModules.moduleForUnblockingNumber(context, blockReportSpamDialogInfo));
+ return this;
+ }
+
+ // For a number that is neither a spam number nor blocked, add the "Block/Report spam" module.
+ modules.add(
+ BlockReportSpamModules.moduleForBlockingNumberAndOptionallyReportingSpam(
+ context, blockReportSpamDialogInfo));
+ return this;
+ }
+
+ /**
+ * Adds a module for copying a number.
+ *
+ * <p>The method is a no-op if the number is empty.
+ */
+ public HistoryItemActionModulesBuilder addModuleForCopyingNumber() {
+ if (TextUtils.isEmpty(moduleInfo.getNormalizedNumber())) {
+ return this;
+ }
+
+ modules.add(
+ new HistoryItemActionModule() {
+ @Override
+ public int getStringId() {
+ return R.string.copy_number;
+ }
+
+ @Override
+ public int getDrawableId() {
+ return R.drawable.quantum_ic_content_copy_vd_theme_24;
+ }
+
+ @Override
+ public boolean onClick() {
+ ClipboardUtils.copyText(
+ context,
+ /* label = */ null,
+ moduleInfo.getNormalizedNumber(),
+ /* showToast = */ true);
+ return false;
+ }
+ });
+ return this;
+ }
+
+ private boolean canPlaceCarrierVideoCall() {
+ int carrierVideoAvailability = CallUtil.getVideoCallingAvailability(context);
+ boolean isCarrierVideoCallingEnabled =
+ ((carrierVideoAvailability & CallUtil.VIDEO_CALLING_ENABLED)
+ == CallUtil.VIDEO_CALLING_ENABLED);
+ boolean canRelyOnCarrierVideoPresence =
+ ((carrierVideoAvailability & CallUtil.VIDEO_CALLING_PRESENCE)
+ == CallUtil.VIDEO_CALLING_PRESENCE);
+
+ return isCarrierVideoCallingEnabled
+ && canRelyOnCarrierVideoPresence
+ && moduleInfo.getCanSupportCarrierVideoCall();
+ }
+
+ private boolean isDuoCall() {
+ return DuoComponent.get(context)
+ .getDuo()
+ .isDuoAccount(moduleInfo.getPhoneAccountComponentName());
+ }
+
+ private boolean canPlaceDuoCall() {
+ Duo duo = DuoComponent.get(context).getDuo();
+
+ return duo.isInstalled(context)
+ && duo.isEnabled(context)
+ && duo.isActivated(context)
+ && duo.isReachable(context, moduleInfo.getNormalizedNumber());
+ }
+
+ /**
+ * Lookup URIs are currently fetched from the cached column of the system call log. This URI
+ * contains encoded information for non-contacts for the purposes of populating contact cards.
+ *
+ * <p>We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or
+ * not.
+ *
+ * <p>TODO(zachh): We should revisit this once the contact URI is no longer being read from the
+ * cached column in the system database, in case we decide not to overload the column.
+ */
+ private boolean isExistingContact() {
+ return !TextUtils.isEmpty(moduleInfo.getLookupUri())
+ && !UriUtils.isEncodedContactUri(Uri.parse(moduleInfo.getLookupUri()));
+ }
+
+ /**
+ * Maps the value of {@link HistoryItemActionModuleInfo#getHost()} to {@link
+ * CallInitiationType.Type}, which is required by {@link CallIntentBuilder} to build a call
+ * intent.
+ */
+ private CallInitiationType.Type getCallInitiationType() {
+ switch (moduleInfo.getHost()) {
+ case CALL_LOG:
+ return CallInitiationType.Type.CALL_LOG;
+ case VOICEMAIL:
+ return CallInitiationType.Type.VOICEMAIL_LOG;
+ default:
+ throw Assert.createUnsupportedOperationFailException(
+ String.format("Unsupported host: %s", moduleInfo.getHost()));
+ }
+ }
+
+ /**
+ * Maps the value of {@link HistoryItemActionModuleInfo#getHost()} to {@link
+ * ReportingLocation.Type}, which is for logging where a spam number is reported.
+ */
+ private ReportingLocation.Type getReportingLocation() {
+ switch (moduleInfo.getHost()) {
+ case CALL_LOG:
+ return ReportingLocation.Type.CALL_LOG_HISTORY;
+ case VOICEMAIL:
+ return ReportingLocation.Type.VOICEMAIL_HISTORY;
+ default:
+ throw Assert.createUnsupportedOperationFailException(
+ String.format("Unsupported host: %s", moduleInfo.getHost()));
+ }
+ }
+}
diff --git a/java/com/android/dialer/historyitemactions/IntentModule.java b/java/com/android/dialer/historyitemactions/IntentModule.java
index f73d4c951..dc53064af 100644
--- a/java/com/android/dialer/historyitemactions/IntentModule.java
+++ b/java/com/android/dialer/historyitemactions/IntentModule.java
@@ -23,6 +23,7 @@ import android.support.annotation.StringRes;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.precall.PreCall;
import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.IntentUtil;
/**
* {@link HistoryItemActionModule} useful for making easy to build modules based on starting an
@@ -73,4 +74,12 @@ public class IntentModule implements HistoryItemActionModule {
return new IntentModule(context, PreCall.getIntent(context, callIntentBuilder), text, image);
}
+
+ public static IntentModule newModuleForSendingTextMessage(Context context, String number) {
+ return new IntentModule(
+ context,
+ IntentUtil.getSendSmsIntent(number),
+ R.string.send_a_message,
+ R.drawable.quantum_ic_message_vd_theme_24);
+ }
}
diff --git a/java/com/android/dialer/historyitemactions/SharedModules.java b/java/com/android/dialer/historyitemactions/SharedModules.java
deleted file mode 100644
index 8604bed1d..000000000
--- a/java/com/android/dialer/historyitemactions/SharedModules.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.dialer.historyitemactions;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import com.android.dialer.DialerPhoneNumber;
-import com.android.dialer.blockreportspam.BlockReportSpamDialogInfo;
-import com.android.dialer.blockreportspam.ShowBlockReportSpamDialogNotifier;
-import com.android.dialer.clipboard.ClipboardUtils;
-import com.android.dialer.util.IntentUtil;
-import com.android.dialer.util.UriUtils;
-import com.google.common.base.Optional;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Modules for the bottom sheet that are shared between NewVoicemailFragment and NewCallLogFragment
- */
-@SuppressWarnings("Guava")
-public class SharedModules {
-
- public static Optional<HistoryItemActionModule> createModuleForAddingToContacts(
- Context context,
- DialerPhoneNumber dialerPhoneNumber,
- String name,
- String lookupUri,
- boolean isBlocked,
- boolean isSpam) {
- // Skip showing the menu item for a spam/blocked number.
- if (isBlocked || isSpam) {
- return Optional.absent();
- }
-
- // Skip showing the menu item for existing contacts.
- if (isExistingContact(lookupUri)) {
- return Optional.absent();
- }
-
- // Skip showing the menu item if there is no number.
- String normalizedNumber = dialerPhoneNumber.getNormalizedNumber();
- if (TextUtils.isEmpty(normalizedNumber)) {
- return Optional.absent();
- }
-
- Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
- intent.putExtra(ContactsContract.Intents.Insert.PHONE, normalizedNumber);
-
- if (!TextUtils.isEmpty(name)) {
- intent.putExtra(ContactsContract.Intents.Insert.NAME, name);
- }
-
- return Optional.of(
- new IntentModule(
- context,
- intent,
- R.string.add_to_contacts,
- R.drawable.quantum_ic_person_add_vd_theme_24));
- }
-
- /**
- * Lookup URIs are currently fetched from the cached column of the system call log. This URI
- * contains encoded information for non-contacts for the purposes of populating contact cards.
- *
- * <p>We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or
- * not.
- *
- * <p>TODO(zachh): We should revisit this once the contact URI is no longer being read from the
- * cached column in the system database, in case we decide not to overload the column.
- */
- private static boolean isExistingContact(@Nullable String lookupUri) {
- return !TextUtils.isEmpty(lookupUri) && !UriUtils.isEncodedContactUri(Uri.parse(lookupUri));
- }
-
- public static Optional<HistoryItemActionModule> createModuleForSendingTextMessage(
- Context context, String normalizedNumber, boolean isBlocked) {
- // Don't show the option to send a text message if the number is blocked.
- if (isBlocked) {
- return Optional.absent();
- }
-
- // TODO(zachh): There are some conditions where this module should not be shown; consider
- // voicemail, business numbers, etc.
-
- return !TextUtils.isEmpty(normalizedNumber)
- ? Optional.of(
- new IntentModule(
- context,
- IntentUtil.getSendSmsIntent(normalizedNumber),
- R.string.send_a_message,
- R.drawable.quantum_ic_message_vd_theme_24))
- : Optional.absent();
- }
-
- /**
- * Create modules related to blocking/unblocking a number and/or reporting it as spam/not spam.
- */
- public static List<HistoryItemActionModule> createModulesHandlingBlockedOrSpamNumber(
- Context context,
- BlockReportSpamDialogInfo blockReportSpamDialogInfo,
- boolean isBlocked,
- boolean isSpam) {
- List<HistoryItemActionModule> modules = new ArrayList<>();
-
- // For a spam number, add two options:
- // (1) "Not spam" and "Block", or
- // (2) "Not spam" and "Unblock".
- if (isSpam) {
- modules.add(createModuleForMarkingNumberAsNonSpam(context, blockReportSpamDialogInfo));
- modules.add(
- createModuleForBlockingOrUnblockingNumber(context, blockReportSpamDialogInfo, isBlocked));
- return modules;
- }
-
- // For a blocked non-spam number, add "Unblock" option.
- if (isBlocked) {
- modules.add(
- createModuleForBlockingOrUnblockingNumber(context, blockReportSpamDialogInfo, isBlocked));
- return modules;
- }
-
- // For a number that is neither a spam number nor blocked, add "Block/Report spam" option.
- modules.add(
- createModuleForBlockingNumberAndOptionallyReportingSpam(
- context, blockReportSpamDialogInfo));
- return modules;
- }
-
- /** Create "Not spam" module. */
- private static HistoryItemActionModule createModuleForMarkingNumberAsNonSpam(
- Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo) {
- return new HistoryItemActionModule() {
- @Override
- public int getStringId() {
- return R.string.not_spam;
- }
-
- @Override
- public int getDrawableId() {
- return R.drawable.quantum_ic_report_off_vd_theme_24;
- }
-
- @Override
- public boolean onClick() {
- ShowBlockReportSpamDialogNotifier.notifyShowDialogToReportNotSpam(
- context, blockReportSpamDialogInfo);
- return true; // Close the bottom sheet.
- }
- };
- }
-
- private static HistoryItemActionModule createModuleForBlockingOrUnblockingNumber(
- Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo, boolean isBlocked) {
- return new HistoryItemActionModule() {
- @Override
- public int getStringId() {
- return isBlocked ? R.string.unblock_number : R.string.block_number;
- }
-
- @Override
- public int getDrawableId() {
- return isBlocked
- ? R.drawable.quantum_ic_unblock_vd_theme_24
- : R.drawable.quantum_ic_block_vd_theme_24;
- }
-
- @Override
- public boolean onClick() {
- if (isBlocked) {
- ShowBlockReportSpamDialogNotifier.notifyShowDialogToUnblockNumber(
- context, blockReportSpamDialogInfo);
- } else {
- ShowBlockReportSpamDialogNotifier.notifyShowDialogToBlockNumber(
- context, blockReportSpamDialogInfo);
- }
- return true; // Close the bottom sheet.
- }
- };
- }
-
- /** Create "Block/Report spam" module */
- private static HistoryItemActionModule createModuleForBlockingNumberAndOptionallyReportingSpam(
- Context context, BlockReportSpamDialogInfo blockReportSpamDialogInfo) {
- return new HistoryItemActionModule() {
- @Override
- public int getStringId() {
- return R.string.block_and_optionally_report_spam;
- }
-
- @Override
- public int getDrawableId() {
- return R.drawable.quantum_ic_block_vd_theme_24;
- }
-
- @Override
- public boolean onClick() {
- ShowBlockReportSpamDialogNotifier.notifyShowDialogToBlockNumberAndOptionallyReportSpam(
- context, blockReportSpamDialogInfo);
- return true; // Close the bottom sheet.
- }
- };
- }
-
- public static Optional<HistoryItemActionModule> createModuleForCopyingNumber(
- Context context, String normalizedNumber) {
- if (TextUtils.isEmpty(normalizedNumber)) {
- return Optional.absent();
- }
- return Optional.of(
- new HistoryItemActionModule() {
- @Override
- public int getStringId() {
- return R.string.copy_number;
- }
-
- @Override
- public int getDrawableId() {
- return R.drawable.quantum_ic_content_copy_vd_theme_24;
- }
-
- @Override
- public boolean onClick() {
- ClipboardUtils.copyText(context, null, normalizedNumber, true);
- return false;
- }
- });
- }
-}
diff --git a/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto b/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto
new file mode 100644
index 000000000..99071a7cd
--- /dev/null
+++ b/java/com/android/dialer/historyitemactions/history_item_action_module_info.proto
@@ -0,0 +1,69 @@
+syntax = "proto2";
+
+option java_package = "com.android.dialer.historyitemactions";
+option java_multiple_files = true;
+option optimize_for = LITE_RUNTIME;
+
+
+package com.android.dialer.historyitemactions;
+
+import "java/com/android/dialer/logging/contact_source.proto";
+
+// Contains information needed to construct items (modules) in a bottom sheet.
+// Next ID: 16
+message HistoryItemActionModuleInfo {
+ // The dialer-normalized version of a phone number.
+ // See DialerPhoneNumber.normalized_number.
+ optional string normalized_number = 1;
+
+ // The ISO 3166-1 two letters country code of the number.
+ optional string country_iso = 2;
+
+ // The name associated with the number.
+ optional string name = 3;
+
+ // The type of the call.
+ // See android.provider.CallLog.Calls.TYPE.
+ optional int32 call_type = 4;
+
+ // Bit-mask describing features of the call.
+ // See android.provider.CallLog.Calls.FEATURES.
+ optional int32 features = 5;
+
+ // The Contacts Provider lookup URI for the contact associated with the
+ // number.
+ optional string lookup_uri = 6;
+
+ // The component name of the account used to place or receive the call.
+ // See android.provider.CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME.
+ optional string phone_account_component_name = 7;
+
+ // Whether the number can be reported as invalid through People API
+ optional bool can_report_as_invalid_number = 8;
+
+ // Whether assisted dialing is supported.
+ optional bool can_support_assisted_dialing = 9;
+
+ // Whether carrier video call is supported.
+ optional bool can_support_carrier_video_call = 10;
+
+ // Whether the number is blocked.
+ optional bool is_blocked = 11;
+
+ // Whether the number is spam.
+ optional bool is_spam = 12;
+
+ // Whether the call is to the voicemail inbox.
+ optional bool is_voicemail_call = 13;
+
+ // The source of the contact if there is one associated with the number.
+ optional com.android.dialer.logging.ContactSource.Type contact_source = 14;
+
+ // Places that can host items (modules) in a bottom sheet
+ enum Host {
+ UNKNOWN = 0;
+ CALL_LOG = 1;
+ VOICEMAIL = 2;
+ }
+ optional Host host = 15;
+}
diff --git a/java/com/android/dialer/historyitemactions/history_item_bottom_sheet_header_info.proto b/java/com/android/dialer/historyitemactions/history_item_bottom_sheet_header_info.proto
index ef71ecd7e..04d9f2259 100644
--- a/java/com/android/dialer/historyitemactions/history_item_bottom_sheet_header_info.proto
+++ b/java/com/android/dialer/historyitemactions/history_item_bottom_sheet_header_info.proto
@@ -36,6 +36,4 @@ message HistoryItemBottomSheetHeaderInfo {
// "Blocked • Mobile • 555-1234", and
// "Spam • Mobile • 555-1234".
optional string secondary_text = 4;
-
- // TODO(a bug): Add SIM info.
}
diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java
index db4c0245e..aa306d214 100644
--- a/java/com/android/dialer/speeddial/SpeedDialFragment.java
+++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java
@@ -29,6 +29,7 @@ import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -46,7 +47,6 @@ import com.android.dialer.historyitemactions.HistoryItemActionBottomSheet;
import com.android.dialer.historyitemactions.HistoryItemActionModule;
import com.android.dialer.historyitemactions.HistoryItemBottomSheetHeaderInfo;
import com.android.dialer.historyitemactions.IntentModule;
-import com.android.dialer.historyitemactions.SharedModules;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.precall.PreCall;
@@ -60,7 +60,6 @@ import com.android.dialer.speeddial.draghelper.SpeedDialLayoutManager;
import com.android.dialer.speeddial.loader.SpeedDialUiItem;
import com.android.dialer.speeddial.loader.UiItemLoaderComponent;
import com.android.dialer.util.IntentUtil;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import java.util.ArrayList;
@@ -312,11 +311,9 @@ public class SpeedDialFragment extends Fragment {
}
// Add sms module
- Optional<HistoryItemActionModule> smsModule =
- SharedModules.createModuleForSendingTextMessage(
- getContext(), defaultChannel.number(), false);
- if (smsModule.isPresent()) {
- modules.add(smsModule.get());
+ if (!TextUtils.isEmpty(defaultChannel.number())) {
+ modules.add(
+ IntentModule.newModuleForSendingTextMessage(getContext(), defaultChannel.number()));
}
modules.add(new DividerModule());
diff --git a/java/com/android/dialer/voicemail/listui/menu/Modules.java b/java/com/android/dialer/voicemail/listui/menu/Modules.java
index 226063c1b..dcd9116e9 100644
--- a/java/com/android/dialer/voicemail/listui/menu/Modules.java
+++ b/java/com/android/dialer/voicemail/listui/menu/Modules.java
@@ -17,77 +17,53 @@
package com.android.dialer.voicemail.listui.menu;
import android.content.Context;
-import com.android.dialer.blockreportspam.BlockReportSpamDialogInfo;
-import com.android.dialer.historyitemactions.DividerModule;
+import android.text.TextUtils;
import com.android.dialer.historyitemactions.HistoryItemActionModule;
-import com.android.dialer.historyitemactions.SharedModules;
-import com.android.dialer.logging.ReportingLocation;
+import com.android.dialer.historyitemactions.HistoryItemActionModuleInfo;
+import com.android.dialer.historyitemactions.HistoryItemActionModulesBuilder;
import com.android.dialer.voicemail.model.VoicemailEntry;
-import com.google.common.base.Optional;
-import java.util.ArrayList;
import java.util.List;
/**
* Configures the modules for the voicemail bottom sheet; these are the rows below the top row
- * (primary action) in the bottom sheet.
+ * (contact info) in the bottom sheet.
*/
-@SuppressWarnings("Guava")
final class Modules {
static List<HistoryItemActionModule> fromVoicemailEntry(
Context context, VoicemailEntry voicemailEntry) {
- // Conditionally add each module, which are items in the bottom sheet's menu.
- List<HistoryItemActionModule> modules = new ArrayList<>();
-
- // TODO(uabdullah): Handle maybeAddModuleForVideoOrAudioCall(context, modules, row);
- Optional<HistoryItemActionModule> moduleForAddingContacts =
- SharedModules.createModuleForAddingToContacts(
- context,
- voicemailEntry.getNumber(),
- voicemailEntry.getNumberAttributes().getName(),
- voicemailEntry.getNumberAttributes().getLookupUri(),
- voicemailEntry.getNumberAttributes().getIsBlocked(),
- voicemailEntry.getNumberAttributes().getIsSpam());
- if (moduleForAddingContacts.isPresent()) {
- modules.add(moduleForAddingContacts.get());
- }
-
- Optional<HistoryItemActionModule> moduleForSendingTextMessage =
- SharedModules.createModuleForSendingTextMessage(
- context,
- voicemailEntry.getNumber().getNormalizedNumber(),
- voicemailEntry.getNumberAttributes().getIsBlocked());
- if (moduleForSendingTextMessage.isPresent()) {
- modules.add(moduleForSendingTextMessage.get());
- }
-
- if (!modules.isEmpty()) {
- modules.add(new DividerModule());
- }
-
- BlockReportSpamDialogInfo blockReportSpamDialogInfo =
- BlockReportSpamDialogInfo.newBuilder()
- .setNormalizedNumber(voicemailEntry.getNumber().getNormalizedNumber())
- .setCountryIso(voicemailEntry.getNumber().getCountryIso())
- .setCallType(voicemailEntry.getCallType())
- .setReportingLocation(ReportingLocation.Type.VOICEMAIL_HISTORY)
- .setContactSource(voicemailEntry.getNumberAttributes().getContactSource())
- .build();
- modules.addAll(
- SharedModules.createModulesHandlingBlockedOrSpamNumber(
- context,
- blockReportSpamDialogInfo,
- voicemailEntry.getNumberAttributes().getIsBlocked(),
- voicemailEntry.getNumberAttributes().getIsSpam()));
-
- // TODO(zachh): Module for CallComposer.
- Optional<HistoryItemActionModule> moduleForCopyingNumber =
- SharedModules.createModuleForCopyingNumber(
- context, voicemailEntry.getNumber().getNormalizedNumber());
- if (moduleForCopyingNumber.isPresent()) {
- modules.add(moduleForCopyingNumber.get());
- }
+ return new HistoryItemActionModulesBuilder(context, buildModuleInfo(voicemailEntry))
+ // TODO(uabdullah): add module for calls.
+ .addModuleForAddingToContacts()
+ .addModuleForSendingTextMessage()
+ .addModuleForDivider()
+ .addModuleForBlockedOrSpamNumber()
+ .addModuleForCopyingNumber()
+ // TODO(zachh): Module for CallComposer.
+ .build();
+ }
- return modules;
+ private static HistoryItemActionModuleInfo buildModuleInfo(VoicemailEntry voicemailEntry) {
+ return HistoryItemActionModuleInfo.newBuilder()
+ .setNormalizedNumber(voicemailEntry.getNumber().getNormalizedNumber())
+ .setCountryIso(voicemailEntry.getNumber().getCountryIso())
+ .setName(voicemailEntry.getNumberAttributes().getName())
+ .setCallType(voicemailEntry.getCallType())
+ .setLookupUri(voicemailEntry.getNumberAttributes().getLookupUri())
+ .setPhoneAccountComponentName(voicemailEntry.getPhoneAccountComponentName())
+ .setCanReportAsInvalidNumber(
+ voicemailEntry.getNumberAttributes().getCanReportAsInvalidNumber())
+ .setCanSupportAssistedDialing(
+ !TextUtils.isEmpty(voicemailEntry.getNumberAttributes().getLookupUri()))
+ .setCanSupportCarrierVideoCall(
+ voicemailEntry.getNumberAttributes().getCanSupportCarrierVideoCall())
+ .setIsBlocked(voicemailEntry.getNumberAttributes().getIsBlocked())
+ .setIsSpam(voicemailEntry.getNumberAttributes().getIsSpam())
+ // A voicemail call is an outgoing call to the voicemail box.
+ // Voicemail entries are not voicemail calls.
+ .setIsVoicemailCall(false)
+ .setContactSource(voicemailEntry.getNumberAttributes().getContactSource())
+ .setHost(HistoryItemActionModuleInfo.Host.VOICEMAIL)
+ .build();
}
}
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index 5ac6b5029..98f001925 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -1504,7 +1504,8 @@ public class InCallActivity extends TransactionSafeFragmentActivity
call.getVideoTech().isSelfManagedCamera(),
shouldAllowAnswerAndRelease(call),
CallList.getInstance().getBackgroundCall() != null,
- call.isSpeakEasyEligible());
+ getSpeakEasyCallManager().isAvailable(getApplicationContext())
+ && call.isSpeakEasyEligible());
transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java
index d803956e6..b9d0eccba 100644
--- a/java/com/android/incallui/InCallServiceImpl.java
+++ b/java/com/android/incallui/InCallServiceImpl.java
@@ -26,7 +26,6 @@ import android.telecom.InCallService;
import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
import com.android.dialer.feedback.FeedbackComponent;
import com.android.incallui.audiomode.AudioModeProvider;
-import com.android.incallui.audiomode.BluetoothDeviceProviderComponent;
import com.android.incallui.call.CallList;
import com.android.incallui.call.ExternalCallList;
import com.android.incallui.call.TelecomAdapter;
@@ -98,7 +97,6 @@ public class InCallServiceImpl extends InCallService {
final Context context = getApplicationContext();
final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context);
AudioModeProvider.getInstance().initializeAudioState(this);
- BluetoothDeviceProviderComponent.get(context).bluetoothDeviceProvider().setUp();
InCallPresenter.getInstance()
.setUp(
context,
@@ -142,7 +140,6 @@ public class InCallServiceImpl extends InCallService {
// Tear down the InCall system
InCallPresenter.getInstance().tearDown();
TelecomAdapter.getInstance().clearInCallService();
- BluetoothDeviceProviderComponent.get(this).bluetoothDeviceProvider().tearDown();
if (returnToCallController != null) {
returnToCallController.tearDown();
returnToCallController = null;
diff --git a/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java b/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java
deleted file mode 100644
index 1aa1c20a8..000000000
--- a/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.incallui.audiomode;
-
-import android.annotation.SuppressLint;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.ArraySet;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.inject.ApplicationContext;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Set;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/** Proxy class for getting and setting connected/active Bluetooth devices. */
-@Singleton
-public final class BluetoothDeviceProvider extends BroadcastReceiver {
-
- // TODO(yueg): use BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED when possible
- private static final String ACTION_ACTIVE_DEVICE_CHANGED =
- "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
-
- private final Context appContext;
- private final BluetoothProfileServiceListener bluetoothProfileServiceListener =
- new BluetoothProfileServiceListener();
-
- private final Set<BluetoothDevice> connectedBluetoothDeviceSet = new ArraySet<>();
-
- private BluetoothDevice activeBluetoothDevice;
- private BluetoothHeadset bluetoothHeadset;
- private boolean isSetUp;
-
- @Inject
- public BluetoothDeviceProvider(@ApplicationContext Context appContext) {
- this.appContext = appContext;
- }
-
- public void setUp() {
- if (BluetoothAdapter.getDefaultAdapter() == null) {
- // Bluetooth is not supported on this hardware platform
- return;
- }
- // Get Bluetooth service including the initial connected device list (should only contain one
- // device)
- BluetoothAdapter.getDefaultAdapter()
- .getProfileProxy(appContext, bluetoothProfileServiceListener, BluetoothProfile.HEADSET);
- // Get notified of Bluetooth device update
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(ACTION_ACTIVE_DEVICE_CHANGED);
- appContext.registerReceiver(this, filter);
-
- isSetUp = true;
- }
-
- public void tearDown() {
- if (!isSetUp) {
- return;
- }
- appContext.unregisterReceiver(this);
- if (bluetoothHeadset != null) {
- BluetoothAdapter.getDefaultAdapter()
- .closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset);
- }
- }
-
- public Set<BluetoothDevice> getConnectedBluetoothDeviceSet() {
- return connectedBluetoothDeviceSet;
- }
-
- public BluetoothDevice getActiveBluetoothDevice() {
- return activeBluetoothDevice;
- }
-
- @SuppressLint("PrivateApi")
- public void setActiveBluetoothDevice(BluetoothDevice bluetoothDevice) {
- if (!connectedBluetoothDeviceSet.contains(bluetoothDevice)) {
- LogUtil.e("BluetoothProfileServiceListener.setActiveBluetoothDevice", "device is not in set");
- return;
- }
- // TODO(yueg): use BluetoothHeadset.setActiveDevice() when possible
- try {
- Method getActiveDeviceMethod =
- bluetoothHeadset.getClass().getDeclaredMethod("setActiveDevice", BluetoothDevice.class);
- getActiveDeviceMethod.setAccessible(true);
- getActiveDeviceMethod.invoke(bluetoothHeadset, bluetoothDevice);
- } catch (Exception e) {
- LogUtil.e(
- "BluetoothProfileServiceListener.setActiveBluetoothDevice",
- "failed to call setActiveDevice",
- e);
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
- handleActionConnectionStateChanged(intent);
- } else if (ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
- handleActionActiveDeviceChanged(intent);
- }
- }
-
- private void handleActionConnectionStateChanged(Intent intent) {
- if (!intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
- LogUtil.i(
- "BluetoothDeviceProvider.handleActionConnectionStateChanged",
- "extra BluetoothDevice.EXTRA_DEVICE not found");
- return;
- }
- BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (bluetoothDevice == null) {
- return;
- }
-
- int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- connectedBluetoothDeviceSet.remove(bluetoothDevice);
- LogUtil.i("BluetoothDeviceProvider.handleActionConnectionStateChanged", "device removed");
- } else if (state == BluetoothProfile.STATE_CONNECTED) {
- connectedBluetoothDeviceSet.add(bluetoothDevice);
- LogUtil.i("BluetoothDeviceProvider.handleActionConnectionStateChanged", "device added");
- }
- }
-
- private void handleActionActiveDeviceChanged(Intent intent) {
- if (!intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) {
- LogUtil.i(
- "BluetoothDeviceProvider.handleActionActiveDeviceChanged",
- "extra BluetoothDevice.EXTRA_DEVICE not found");
- return;
- }
- activeBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- LogUtil.i(
- "BluetoothDeviceProvider.handleActionActiveDeviceChanged",
- (activeBluetoothDevice == null ? "null" : ""));
- }
-
- private final class BluetoothProfileServiceListener implements BluetoothProfile.ServiceListener {
- @Override
- @SuppressLint("PrivateApi")
- public void onServiceConnected(int profile, BluetoothProfile bluetoothProfile) {
- if (profile != BluetoothProfile.HEADSET) {
- return;
- }
- // Get initial connected device list
- bluetoothHeadset = (BluetoothHeadset) bluetoothProfile;
- List<BluetoothDevice> devices = bluetoothProfile.getConnectedDevices();
- for (BluetoothDevice device : devices) {
- connectedBluetoothDeviceSet.add(device);
- LogUtil.i(
- "BluetoothProfileServiceListener.onServiceConnected", "get initial connected device");
- }
-
- // Get initial active device
- // TODO(yueg): use BluetoothHeadset.getActiveDevice() when possible
- try {
- Method getActiveDeviceMethod =
- bluetoothHeadset.getClass().getDeclaredMethod("getActiveDevice");
- getActiveDeviceMethod.setAccessible(true);
- activeBluetoothDevice = (BluetoothDevice) getActiveDeviceMethod.invoke(bluetoothHeadset);
- LogUtil.i(
- "BluetoothProfileServiceListener.onServiceConnected",
- "get initial active device" + ((activeBluetoothDevice == null) ? " null" : ""));
- } catch (Exception e) {
- LogUtil.e(
- "BluetoothProfileServiceListener.onServiceConnected",
- "failed to call getAcitveDevice",
- e);
- }
- }
-
- @Override
- public void onServiceDisconnected(int profile) {
- LogUtil.enterBlock("BluetoothProfileServiceListener.onServiceDisconnected");
- if (profile == BluetoothProfile.HEADSET) {
- bluetoothHeadset = null;
- }
- }
- }
-}
diff --git a/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java b/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java
deleted file mode 100644
index 9cd926835..000000000
--- a/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.incallui.audiomode;
-
-import android.content.Context;
-import com.android.dialer.inject.HasRootComponent;
-import dagger.Subcomponent;
-
-/** Dagger component for the Bluetooth device provider. */
-@Subcomponent
-public abstract class BluetoothDeviceProviderComponent {
-
- public abstract BluetoothDeviceProvider bluetoothDeviceProvider();
-
- public static BluetoothDeviceProviderComponent get(Context context) {
- return ((BluetoothDeviceProviderComponent.HasComponent)
- ((HasRootComponent) context.getApplicationContext()).component())
- .bluetoothDeviceProviderComponent();
- }
-
- /** Used to refer to the root application component. */
- public interface HasComponent {
- BluetoothDeviceProviderComponent bluetoothDeviceProviderComponent();
- }
-}
diff --git a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
index d6946d8de..a561b5ee5 100644
--- a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
+++ b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
@@ -39,12 +39,12 @@ import com.android.dialer.common.FragmentUtils;
import com.android.dialer.common.LogUtil;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
-import com.android.incallui.audiomode.BluetoothDeviceProviderComponent;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
+import com.android.incallui.call.TelecomAdapter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.Set;
+import java.util.Collection;
/** Shows picker for audio routes */
public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment {
@@ -91,24 +91,33 @@ public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment
@Nullable
@Override
+ @SuppressLint("NewApi")
public View onCreateView(
LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
View view = layoutInflater.inflate(R.layout.audioroute_selector, viewGroup, false);
CallAudioState audioState = getArguments().getParcelable(ARG_AUDIO_STATE);
- Set<BluetoothDevice> bluetoothDeviceSet =
- BluetoothDeviceProviderComponent.get(getContext())
- .bluetoothDeviceProvider()
- .getConnectedBluetoothDeviceSet();
- for (BluetoothDevice device : bluetoothDeviceSet) {
- boolean selected =
- (audioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH)
- && (bluetoothDeviceSet.size() == 1
- || device.equals(
- BluetoothDeviceProviderComponent.get(getContext())
- .bluetoothDeviceProvider()
- .getActiveBluetoothDevice()));
- TextView textView = createBluetoothItem(device, selected);
+ if (BuildCompat.isAtLeastP()) {
+ // Create items for all connected Bluetooth devices
+ Collection<BluetoothDevice> bluetoothDeviceSet = audioState.getSupportedBluetoothDevices();
+ for (BluetoothDevice device : bluetoothDeviceSet) {
+ boolean selected =
+ (audioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH)
+ && (bluetoothDeviceSet.size() == 1
+ || device.equals(audioState.getActiveBluetoothDevice()));
+ TextView textView = createBluetoothItem(device, selected);
+ ((LinearLayout) view).addView(textView, 0);
+ }
+ } else {
+ // Only create Bluetooth audio route
+ TextView textView =
+ (TextView) getLayoutInflater().inflate(R.layout.audioroute_item, null, false);
+ textView.setText(getString(R.string.audioroute_bluetooth));
+ initItem(
+ textView,
+ CallAudioState.ROUTE_BLUETOOTH,
+ audioState,
+ DialerImpression.Type.IN_CALL_SWITCH_AUDIO_ROUTE_BLUETOOTH);
((LinearLayout) view).addView(textView, 0);
}
@@ -183,9 +192,7 @@ public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment
AudioRouteSelectorDialogFragment.this, AudioRouteSelectorPresenter.class)
.onAudioRouteSelected(CallAudioState.ROUTE_BLUETOOTH);
// Set active Bluetooth device
- BluetoothDeviceProviderComponent.get(getContext())
- .bluetoothDeviceProvider()
- .setActiveBluetoothDevice(bluetoothDevice);
+ TelecomAdapter.getInstance().requestBluetoothAudio(bluetoothDevice);
dismiss();
});
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 1a0de1960..77e2ea30b 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -50,7 +50,6 @@ import android.telecom.VideoProfile;
import android.text.TextUtils;
import android.widget.Toast;
import com.android.contacts.common.compat.CallCompat;
-import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
import com.android.dialer.assisteddialing.ConcreteCreator;
import com.android.dialer.assisteddialing.TransformationInfo;
import com.android.dialer.callintent.CallInitiationType;
@@ -86,7 +85,6 @@ 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;
import com.android.incallui.videotech.duo.DuoVideoTech;
@@ -118,8 +116,11 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
private static final String ID_PREFIX = "DialerCall_";
- private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
+
+ @VisibleForTesting
+ public static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
"emergency_callback_window_millis";
+
private static int idCounter = 0;
/**
@@ -822,10 +823,9 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
// We want to treat any incoming call that arrives a short time after an outgoing emergency call
// as a potential emergency callback.
if (getExtras() != null
- && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
- > 0) {
+ && getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) > 0) {
long lastEmergencyCallMillis =
- getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
+ getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
return true;
}
@@ -1058,6 +1058,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
}
@TargetApi(28)
+ @Nullable
public RttCall getRttCall() {
if (!isActiveRttCall()) {
return null;
@@ -1111,16 +1112,18 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
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);
+ if (getRttCall() != null) {
+ // 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);
}
- } 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) {
@@ -1662,7 +1665,6 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
if (videoTechManager != null) {
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) {
saveRttTranscript();
@@ -1697,10 +1699,6 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa
/** Indicates the call is eligible for SpeakEasy */
public boolean isSpeakEasyEligible() {
- if (!Constraints.isAvailable(context)) {
- return false;
- }
-
return !isPotentialEmergencyCallback()
&& !isEmergencyCall()
&& !isActiveRttCall()
diff --git a/java/com/android/incallui/call/TelecomAdapter.java b/java/com/android/incallui/call/TelecomAdapter.java
index a7e10d37c..4ae1bc1db 100644
--- a/java/com/android/incallui/call/TelecomAdapter.java
+++ b/java/com/android/incallui/call/TelecomAdapter.java
@@ -16,7 +16,9 @@
package com.android.incallui.call;
+import android.annotation.TargetApi;
import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Looper;
@@ -193,4 +195,13 @@ public class TelecomAdapter implements InCallServiceListener {
"no inCallService available for stopping foreground notification");
}
}
+
+ @TargetApi(28)
+ public void requestBluetoothAudio(BluetoothDevice bluetoothDevice) {
+ if (inCallService != null) {
+ inCallService.requestBluetoothAudio(bluetoothDevice);
+ } else {
+ LogUtil.e("TelecomAdapter.requestBluetoothAudio", "inCallService is null");
+ }
+ }
}
diff --git a/java/com/android/incallui/rtt/impl/AudioSelectMenu.java b/java/com/android/incallui/rtt/impl/AudioSelectMenu.java
index 01c3950e9..1c83637ea 100644
--- a/java/com/android/incallui/rtt/impl/AudioSelectMenu.java
+++ b/java/com/android/incallui/rtt/impl/AudioSelectMenu.java
@@ -17,8 +17,6 @@
package com.android.incallui.rtt.impl;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff.Mode;
import android.telecom.CallAudioState;
import android.view.View;
import android.widget.PopupWindow;
@@ -28,8 +26,11 @@ import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
public class AudioSelectMenu extends PopupWindow {
private final InCallButtonUiDelegate inCallButtonUiDelegate;
- private final Context context;
private final OnButtonClickListener onButtonClickListener;
+ private final RttCheckableButton bluetoothButton;
+ private final RttCheckableButton speakerButton;
+ private final RttCheckableButton headsetButton;
+ private final RttCheckableButton earpieceButton;
interface OnButtonClickListener {
void onBackPressed();
@@ -40,7 +41,6 @@ public class AudioSelectMenu extends PopupWindow {
InCallButtonUiDelegate inCallButtonUiDelegate,
OnButtonClickListener onButtonClickListener) {
super(context, null, 0, R.style.OverflowMenu);
- this.context = context;
this.inCallButtonUiDelegate = inCallButtonUiDelegate;
this.onButtonClickListener = onButtonClickListener;
View view = View.inflate(context, R.layout.audio_route, null);
@@ -55,28 +55,32 @@ public class AudioSelectMenu extends PopupWindow {
this.onButtonClickListener.onBackPressed();
});
CallAudioState audioState = inCallButtonUiDelegate.getCurrentAudioState();
- initItem(
- view.findViewById(R.id.audioroute_bluetooth), CallAudioState.ROUTE_BLUETOOTH, audioState);
- initItem(view.findViewById(R.id.audioroute_speaker), CallAudioState.ROUTE_SPEAKER, audioState);
- initItem(
- view.findViewById(R.id.audioroute_headset), CallAudioState.ROUTE_WIRED_HEADSET, audioState);
- initItem(
- view.findViewById(R.id.audioroute_earpiece), CallAudioState.ROUTE_EARPIECE, audioState);
+ bluetoothButton = view.findViewById(R.id.audioroute_bluetooth);
+ speakerButton = view.findViewById(R.id.audioroute_speaker);
+ headsetButton = view.findViewById(R.id.audioroute_headset);
+ earpieceButton = view.findViewById(R.id.audioroute_earpiece);
+ initItem(bluetoothButton, CallAudioState.ROUTE_BLUETOOTH, audioState);
+ initItem(speakerButton, CallAudioState.ROUTE_SPEAKER, audioState);
+ initItem(headsetButton, CallAudioState.ROUTE_WIRED_HEADSET, audioState);
+ initItem(earpieceButton, CallAudioState.ROUTE_EARPIECE, audioState);
}
private void initItem(RttCheckableButton item, final int itemRoute, CallAudioState audioState) {
- int selectedColor =
- context.getColor(com.android.incallui.audioroute.R.color.dialer_theme_color);
if ((audioState.getSupportedRouteMask() & itemRoute) == 0) {
item.setVisibility(View.GONE);
} else if (audioState.getRoute() == itemRoute) {
- item.setTextColor(selectedColor);
- item.setCompoundDrawableTintList(ColorStateList.valueOf(selectedColor));
- item.setCompoundDrawableTintMode(Mode.SRC_ATOP);
+ item.setChecked(true);
}
item.setOnClickListener(
(v) -> {
inCallButtonUiDelegate.setAudioRoute(itemRoute);
});
}
+
+ void setAudioState(CallAudioState audioState) {
+ bluetoothButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH);
+ speakerButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+ headsetButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET);
+ earpieceButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_EARPIECE);
+ }
}
diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java
index e56715981..c393393f8 100644
--- a/java/com/android/incallui/rtt/impl/RttChatFragment.java
+++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java
@@ -107,6 +107,7 @@ public class RttChatFragment extends Fragment
private PrimaryCallState primaryCallState = PrimaryCallState.empty();
private boolean isUserScrolling;
private boolean shouldAutoScrolling;
+ private AudioSelectMenu audioSelectMenu;
/**
* Create a new instance of RttChatFragment.
@@ -558,6 +559,9 @@ public class RttChatFragment extends Fragment
LogUtil.i("RttChatFragment.setAudioState", "audioState: " + audioState);
overflowMenu.setMuteButtonChecked(audioState.isMuted());
overflowMenu.setAudioState(audioState);
+ if (audioSelectMenu != null) {
+ audioSelectMenu.setAudioState(audioState);
+ }
}
@Override
@@ -573,7 +577,7 @@ public class RttChatFragment extends Fragment
@Override
public void showAudioRouteSelector() {
- AudioSelectMenu audioSelectMenu =
+ audioSelectMenu =
new AudioSelectMenu(
getContext(),
inCallButtonUiDelegate,
diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java
index f2721da7d..8a815d385 100644
--- a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java
+++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java
@@ -16,6 +16,7 @@
package com.android.incallui.speakeasy;
+import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import com.android.incallui.call.DialerCall;
@@ -37,4 +38,15 @@ public interface SpeakEasyCallManager {
* @param call The call which has been removed.
*/
void onCallRemoved(@NonNull DialerCall call);
+
+ /**
+ * Indicates the feature is available.
+ *
+ * @param context The application context.
+ */
+ boolean isAvailable(@NonNull Context context);
+
+ /** Returns the config provider flag associated with the feature. */
+ @NonNull
+ String getConfigProviderFlag();
}
diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java
index 9e58ce18f..a0409737b 100644
--- a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java
+++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java
@@ -16,6 +16,8 @@
package com.android.incallui.speakeasy;
+import android.content.Context;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import com.android.incallui.call.DialerCall;
@@ -38,4 +40,17 @@ public class SpeakEasyCallManagerStub implements SpeakEasyCallManager {
/** Always inert in the stub. */
@Override
public void onCallRemoved(DialerCall call) {}
+
+ /** Always returns false. */
+ @Override
+ public boolean isAvailable(@NonNull Context unused) {
+ return false;
+ }
+
+ /** Always returns a stub string. */
+ @NonNull
+ @Override
+ public String getConfigProviderFlag() {
+ return "not_yet_implmented";
+ }
}
diff --git a/java/com/android/incallui/speakeasy/runtime/Constraints.java b/java/com/android/incallui/speakeasy/runtime/Constraints.java
deleted file mode 100644
index 1206d599c..000000000
--- a/java/com/android/incallui/speakeasy/runtime/Constraints.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.incallui.speakeasy.runtime;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build.VERSION_CODES;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.os.BuildCompat;
-import android.support.v4.os.UserManagerCompat;
-import com.android.dialer.common.Assert;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.configprovider.ConfigProviderBindings;
-import com.android.dialer.util.PermissionsUtil;
-
-/** Preconditions for the use of SpeakEasyModule */
-public final class Constraints {
-
- @VisibleForTesting public static final String SPEAK_EASY_ENABLED = "speak_easy_enabled";
- private static final String[] REQUIRED_PERMISSIONS = {
-
- };
-
- // Non-instantiatable.
- private Constraints() {}
-
- public static boolean isAvailable(@NonNull Context context) {
- Assert.isNotNull(context);
-
- return isServerConfigEnabled(context)
- && isUserUnlocked(context)
- && meetsPlatformSdkFloor()
- && hasNecessaryPermissions(context);
- }
-
- private static boolean isServerConfigEnabled(@NonNull Context context) {
- return ConfigProviderBindings.get(context).getBoolean(SPEAK_EASY_ENABLED, false);
- }
-
- private static boolean isUserUnlocked(@NonNull Context context) {
- return UserManagerCompat.isUserUnlocked(context);
- }
-
- private static boolean meetsPlatformSdkFloor() {
- return BuildCompat.isAtLeastP();
- }
-
- @SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs.
- @TargetApi(VERSION_CODES.N)
- private static boolean hasNecessaryPermissions(@NonNull Context context) {
- for (String permission : REQUIRED_PERMISSIONS) {
- if (!PermissionsUtil.hasPermission(context, permission)) {
- LogUtil.i("Constraints.hasNecessaryPermissions", "missing permission: %s ", permission);
- return false;
- }
- }
- return true;
- }
-}