diff options
Diffstat (limited to 'java')
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; - } -} |