From 253b4407c125acbb7add26592ba782144c4d0366 Mon Sep 17 00:00:00 2001 From: linyuh Date: Thu, 3 May 2018 12:42:04 -0700 Subject: Simplify how we build bottom sheet options (a.k.a. modules). Test: HistoryItemActionModulesBuilderTest, ModulesTest PiperOrigin-RevId: 195294876 Change-Id: Iac44f965a585975389da7dee758a94a8ad8311d3 --- .../android/dialer/calllog/ui/menu/Modules.java | 188 +++------- .../historyitemactions/BlockReportSpamModules.java | 120 +++++++ .../HistoryItemActionModulesBuilder.java | 400 +++++++++++++++++++++ .../dialer/historyitemactions/IntentModule.java | 9 + .../dialer/historyitemactions/SharedModules.java | 247 ------------- .../history_item_action_module_info.proto | 69 ++++ .../dialer/speeddial/SpeedDialFragment.java | 11 +- .../dialer/voicemail/listui/menu/Modules.java | 96 ++--- 8 files changed, 681 insertions(+), 459 deletions(-) create mode 100644 java/com/android/dialer/historyitemactions/BlockReportSpamModules.java create mode 100644 java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java delete mode 100644 java/com/android/dialer/historyitemactions/SharedModules.java create mode 100644 java/com/android/dialer/historyitemactions/history_item_action_module_info.proto 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 fromRow(Context context, CoalescedRow row) { - // Conditionally add each module, which are items in the bottom sheet's menu. - List modules = new ArrayList<>(); - - String normalizedNumber = row.getNumber().getNormalizedNumber(); - boolean canPlaceCalls = - PhoneNumberHelper.canPlaceCallsTo(normalizedNumber, row.getNumberPresentation()); - - if (canPlaceCalls) { - modules.addAll(createModulesForCalls(context, row, normalizedNumber)); - Optional 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 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 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 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 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 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/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..ca6d3f3e4 --- /dev/null +++ b/java/com/android/dialer/historyitemactions/HistoryItemActionModulesBuilder.java @@ -0,0 +1,400 @@ +/* + * 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}. + * + *

Example usage: + * + *


+ *    // 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 modules = modulesBuilder.build();
+ * 
+ */ +public final class HistoryItemActionModulesBuilder { + + private final Context context; + private final HistoryItemActionModuleInfo moduleInfo; + private final List 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 build() { + return new ArrayList<>(modules); + } + + /** + * Adds a module for placing a voice call. + * + *

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. + * + *

If the number is blocked or marked as spam, this method is a no-op. + * + *

If the provided module info is for a Duo video call and Duo is available, add a Duo video + * call module. + * + *

If the provided module info is for a Duo video call but Duo is unavailable, add a carrier + * video call module. + * + *

If the provided module info is for a carrier video call, add a carrier video call module. + * + *

If the provided module info is for a voice call and the device has carrier video call + * capability, add a carrier video call module. + * + *

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.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. + * + *

The method is a no-op if the number is blocked or empty. + */ + public HistoryItemActionModulesBuilder addModuleForSendingTextMessage() { + // TODO(zachh): There are other conditions where this module should not be shown; consider + // voicemail, business numbers, etc. + if (moduleInfo.getIsBlocked() || TextUtils.isEmpty(moduleInfo.getNormalizedNumber())) { + return this; + } + + modules.add( + IntentModule.newModuleForSendingTextMessage(context, moduleInfo.getNormalizedNumber())); + return this; + } + + /** + * Adds a module for a divider. + * + *

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. + * + *

The method is a no-op if + * + *

    + *
  • the number is blocked, + *
  • the number is marked as spam, + *
  • the number is empty, or + *
  • the number belongs to an existing contact. + *
+ */ + public HistoryItemActionModulesBuilder addModuleForAddingToContacts() { + if (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. + * + *

If a number is marked as spam, add two modules: + * + *

    + *
  • "Not spam" and "Block", or + *
  • "Not spam" and "Unblock". + *
+ * + *

If a number is blocked but not marked as spam, add the "Unblock" module. + * + *

If a number is not blocked or marked as spam, add the "Block/Report spam" module. + */ + public HistoryItemActionModulesBuilder addModuleForBlockedOrSpamNumber() { + 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. + * + *

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. + * + *

We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or + * not. + * + *

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 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. - * - *

We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or - * not. - * - *

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 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 createModulesHandlingBlockedOrSpamNumber( - Context context, - BlockReportSpamDialogInfo blockReportSpamDialogInfo, - boolean isBlocked, - boolean isSpam) { - List 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 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/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 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 fromVoicemailEntry( Context context, VoicemailEntry voicemailEntry) { - // Conditionally add each module, which are items in the bottom sheet's menu. - List modules = new ArrayList<>(); - - // TODO(uabdullah): Handle maybeAddModuleForVideoOrAudioCall(context, modules, row); - Optional 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 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 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(); } } -- cgit v1.2.3