Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 253b4407 authored by linyuh's avatar linyuh Committed by Copybara-Service
Browse files

Simplify how we build bottom sheet options (a.k.a. modules).

Test: HistoryItemActionModulesBuilderTest, ModulesTest
PiperOrigin-RevId: 195294876
Change-Id: Iac44f965a585975389da7dee758a94a8ad8311d3
parent 49f90b33
Loading
Loading
Loading
Loading
+43 −145
Original line number Diff line number Diff line
@@ -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());
    if (PhoneNumberHelper.canPlaceCallsTo(
        row.getNumber().getNormalizedNumber(), row.getNumberPresentation())) {
      modulesBuilder
          .addModuleForVoiceCall()
          .addModuleForVideoCall()
          .addModuleForSendingTextMessage()
          .addModuleForDivider()
          .addModuleForAddingToContacts()
          .addModuleForBlockedOrSpamNumber()
          .addModuleForCopyingNumber();
    }

      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());
      }
    }
    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();
  }
}
+120 −0
Original line number Diff line number Diff line
/*
 * 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.
      }
    };
  }
}
+400 −0

File added.

Preview size limit exceeded, changes collapsed.

+9 −0
Original line number Diff line number Diff line
@@ -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);
  }
}
+0 −247
Original line number Diff line number Diff line
/*
 * 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;
          }
        });
  }
}
Loading