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

Commit 73a74c3c authored by twyen's avatar twyen Committed by Eric Erfanian
Browse files

Handle preferred SIM for ACTION_CALL

Previously preferred SIM is handled only by precall, which covers dialing with dialer or with the special receiver contacts uses. If a third party app uses ACTION_CALL or telecomManager.placeCall(), then the in call UI will be launched directly and the old account selection dialog will be used without preferred SIM support.

In this CL logic from CallingAccountSelector is refactored out so InCallActivity can use it for the dialog.

Bug: 73718976
Test: Unit tests, In call UI not covered.
PiperOrigin-RevId: 188214007
Change-Id: Ifaacf982a3e98601dc362b649c3501d4ee96e63e
parent 28b252f6
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -372,7 +372,11 @@
  <string name="set_default_account">Always use this for calls</string>

  <!-- Title for dialog to select Phone Account for outgoing call.  [CHAR LIMIT=40] -->
  <string name="select_phone_account_for_calls">Call with</string>
  <string name="select_phone_account_for_calls">Choose SIM for this call</string>

  <!-- Checkbox label when selecting a SIM when calling a contact, to use the selected SIM for the
   same contact and never ask again [CHAR LIMIT=40]-->
  <string name="select_phone_account_for_calls_remember">Remember this choice</string>

  <!-- String used for actions in the dialer call log and the quick contact card to initiate
       a call to an individual.  The user is prompted to enter a note which is sent along with
+59 −43
Original line number Diff line number Diff line
40:10695 Android Annotations Support Library
10784:11344 Android Architecture Components Core Library
22182:11344 Android Architecture Components Lifecycle Library
33588:11344 Android Architecture Components Lifecycle Runtime Library
44951:11358 Android Common
56344:10695 Android Compat Support Library
67077:10695 Android Compatibility Library v13
77809:10695 Android Compatibility Library v4
88541:10695 Android Compatibility Library v7
99272:10695 Android Core UI Support Library
110006:10691 Android Core Utils Support Library
120732:10695 Android Design Support Library
131473:10695 Android Dynamic Animation Support Library
142206:10695 Android Fragments Support Library
152938:10695 Android Graphics Support Library
163674:10691 Android Media Compat Support Library
174404:10691 Android Transition Support Library
185117:11359 Apache Commons IO
196487:11358 Dagger
207861:11357 Error Prone
219234:11358 Google Auto
230607:11358 Guava JDK5
241980:11362 Guava JDK7
253353:11358 J2ObjC
264723:11358 JSR 250
276093:11365 JSR 330
287494:11358 Material Components for Android
298863:11358 OkHttp
310230:11358 Okio
321603:11358 OpenCensus
332972:11358 Volley
344341:10695 bubble
355056:12847 carrierservices
367915:11358 flexbox
379287:11358 gRPC Java
390663:12847 jibercsclient
403529:10173 libphonenumber
413713:18982 mime4j
432714:10699 shortcutbadger
443429:16013 Android SDK
459452:4771 Glide
464242:1096 Animal Sniffer
465360:22655 Checker Framework
488027:1602 JSR 305
10771:11344 Android Architecture Components
22164:11344 Android Architecture Components Core Library
33562:11344 Android Architecture Components Lifecycle Library
44968:11344 Android Architecture Components Lifecycle Runtime Library
56331:11358 Android Common
67724:10695 Android Compat Support Library
78457:10695 Android Compatibility Library v13
89189:10695 Android Compatibility Library v4
99921:10695 Android Compatibility Library v7
110652:10695 Android Core UI Support Library
121386:10691 Android Core Utils Support Library
132112:10695 Android Design Support Library
142853:10695 Android Dynamic Animation Support Library
153586:10695 Android Fragments Support Library
164318:10695 Android Graphics Support Library
175054:10691 Android Media Compat Support Library
185784:10691 Android Transition Support Library
196497:11359 Apache Commons IO
207917:10693 Compatibility Libraries for Android asynclayoutinflater.
218663:10693 Compatibility Libraries for Android collections.
229415:10693 Compatibility Libraries for Android coordinatorlayout.
240163:10693 Compatibility Libraries for Android cursoradapter.
250908:10693 Compatibility Libraries for Android customview.
261655:10693 Compatibility Libraries for Android documentfile.
272402:10693 Compatibility Libraries for Android drawerlayout.
283149:10693 Compatibility Libraries for Android interpolator.
293890:10693 Compatibility Libraries for Android loader.
304646:10693 Compatibility Libraries for Android localbroadcastmanager.
315386:10693 Compatibility Libraries for Android print.
326138:10693 Compatibility Libraries for Android slidingpanelayout.
336891:10693 Compatibility Libraries for Android swiperefreshlayout.
347635:10693 Compatibility Libraries for Android viewpager.
358339:11358 Dagger
369713:11357 Error Prone
381086:11358 Google Auto
392459:11358 Guava JDK5
403832:11362 Guava JDK7
415205:11358 J2ObjC
426575:11358 JSR 250
437945:11365 JSR 330
449346:11358 Material Components for Android
460715:11358 OkHttp
472082:11358 Okio
483455:11358 OpenCensus
494824:11358 Volley
506192:11357 ZXing
517569:12847 carrierservices
530428:11358 flexbox
541800:11358 gRPC Java
553167:11358 gson
564543:12847 jibercsclient
577409:10173 libphonenumber
587593:18982 mime4j
606594:10699 shortcutbadger
617309:16013 Android SDK
633344:22655 Checker Framework
656018:1096 Animal Sniffer
657124:4771 Glide
661907:1602 JSR 305
+3622 −511

File changed.

Preview size limit exceeded, changes collapsed.

+23 −363
Original line number Diff line number Diff line
@@ -17,27 +17,11 @@
package com.android.dialer.precall.impl;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -46,7 +30,6 @@ import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.Selec
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.Worker;
import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.configprovider.ConfigProviderBindings;
import com.android.dialer.logging.DialerImpression;
@@ -55,16 +38,11 @@ import com.android.dialer.logging.Logger;
import com.android.dialer.precall.PreCallAction;
import com.android.dialer.precall.PreCallCoordinator;
import com.android.dialer.precall.PreCallCoordinator.PendingAction;
import com.android.dialer.preferredsim.PreferredAccountUtil;
import com.android.dialer.preferredsim.PreferredSimFallbackContract;
import com.android.dialer.preferredsim.PreferredSimFallbackContract.PreferredSim;
import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
import com.android.dialer.preferredsim.PreferredAccountRecorder;
import com.android.dialer.preferredsim.PreferredAccountWorker;
import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion;
import com.android.dialer.telecom.TelecomUtil;
import com.android.dialer.util.PermissionsUtil;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.List;

/** PreCallAction to select which phone account to call with. Ignored if there's only one account */
@@ -73,9 +51,6 @@ public class CallingAccountSelector implements PreCallAction {

  @VisibleForTesting static final String TAG_CALLING_ACCOUNT_SELECTOR = "CallingAccountSelector";

  @VisibleForTesting
  static final String METADATA_SUPPORTS_PREFERRED_SIM = "supports_per_number_preferred_account";

  private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;

  private boolean isDiscarding;
@@ -153,10 +128,12 @@ public class CallingAccountSelector implements PreCallAction {
              if (isDiscarding) {
                return;
              }
              if (result.phoneAccountHandle.isPresent()) {
              if (result.getPhoneAccountHandle().isPresent()) {
                Logger.get(coordinator.getActivity())
                    .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_PREFERRED_USED);
                coordinator.getBuilder().setPhoneAccountHandle(result.phoneAccountHandle.get());
                coordinator
                    .getBuilder()
                    .setPhoneAccountHandle(result.getPhoneAccountHandle().get());
                pendingAction.finish();
                return;
              }
@@ -171,17 +148,17 @@ public class CallingAccountSelector implements PreCallAction {
                pendingAction.finish();
                return;
              }
              if (result.suggestion.isPresent()) {
              if (result.getSuggestion().isPresent()) {
                LogUtil.i(
                    "CallingAccountSelector.processPreferredAccount",
                    "SIM suggested: " + result.suggestion.get().reason);
                if (result.suggestion.get().shouldAutoSelect) {
                    "SIM suggested: " + result.getSuggestion().get().reason);
                if (result.getSuggestion().get().shouldAutoSelect) {
                  Logger.get(coordinator.getActivity())
                      .logImpression(
                          DialerImpression.Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED);
                  LogUtil.i(
                      "CallingAccountSelector.processPreferredAccount", "Auto selected suggestion");
                  builder.setPhoneAccountHandle(result.suggestion.get().phoneAccountHandle);
                  builder.setPhoneAccountHandle(result.getSuggestion().get().phoneAccountHandle);
                  pendingAction.finish();
                  return;
                }
@@ -189,9 +166,9 @@ public class CallingAccountSelector implements PreCallAction {
              showDialog(
                  coordinator,
                  pendingAction,
                  result.dataId.orNull(),
                  result.getDataId().orNull(),
                  phoneNumber,
                  result.suggestion.orNull());
                  result.getSuggestion().orNull());
            }))
        .build()
        .executeParallel(activity);
@@ -235,41 +212,17 @@ public class CallingAccountSelector implements PreCallAction {
            dataId != null /* canSetDefault */,
            R.string.pre_call_select_phone_account_remember,
            phoneAccountHandles,
            new SelectedListener(coordinator, pendingAction, dataId, number, suggestion),
            new SelectedListener(
                coordinator,
                pendingAction,
                new PreferredAccountRecorder(number, suggestion, dataId)),
            null /* call ID */,
            buildHint(coordinator.getActivity(), phoneAccountHandles, suggestion));
            SuggestionProvider.buildHint(
                coordinator.getActivity(), phoneAccountHandles, suggestion));
    selectPhoneAccountDialogFragment.show(
        coordinator.getActivity().getFragmentManager(), TAG_CALLING_ACCOUNT_SELECTOR);
  }

  @Nullable
  private static List<String> buildHint(
      Context context,
      List<PhoneAccountHandle> phoneAccountHandles,
      @Nullable Suggestion suggestion) {
    if (suggestion == null) {
      return null;
    }
    List<String> hints = new ArrayList<>();
    for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
      if (!phoneAccountHandle.equals(suggestion.phoneAccountHandle)) {
        hints.add(null);
        continue;
      }
      switch (suggestion.reason) {
        case INTRA_CARRIER:
          hints.add(context.getString(R.string.pre_call_select_phone_account_hint_intra_carrier));
          break;
        case FREQUENT:
          hints.add(context.getString(R.string.pre_call_select_phone_account_hint_frequent));
          break;
        default:
          LogUtil.w("CallingAccountSelector.buildHint", "unhandled reason " + suggestion.reason);
      }
    }
    return hints;
  }

  @MainThread
  @Override
  public void onDiscard() {
@@ -279,221 +232,27 @@ public class CallingAccountSelector implements PreCallAction {
    }
  }

  private static class PreferredAccountWorkerResult {

    /** The preferred phone account for the number. Absent if not set or invalid. */
    Optional<PhoneAccountHandle> phoneAccountHandle = Optional.absent();

    /**
     * {@link android.provider.ContactsContract.Data#_ID} of the row matching the number. If the
     * preferred account is to be set it should be stored in this row
     */
    Optional<String> dataId = Optional.absent();

    Optional<Suggestion> suggestion = Optional.absent();
  }

  private static class PreferredAccountWorker
      implements Worker<Context, PreferredAccountWorkerResult> {

    private final String phoneNumber;

    public PreferredAccountWorker(String phoneNumber) {
      this.phoneNumber = phoneNumber;
    }

    @NonNull
    @Override
    @WorkerThread
    public PreferredAccountWorkerResult doInBackground(Context context) throws Throwable {
      PreferredAccountWorkerResult result = new PreferredAccountWorkerResult();
      if (!isPreferredSimEnabled(context)) {
        return result;
      }
      if (!PermissionsUtil.hasContactsReadPermissions(context)) {
        LogUtil.i(
            "CallingAccountSelector.PreferredAccountWorker.doInBackground",
            "missing READ_CONTACTS permission");
        return result;
      }
      result.dataId = getDataId(context, phoneNumber);
      if (result.dataId.isPresent()) {
        result.phoneAccountHandle = getPreferredAccount(context, result.dataId.get());
      }
      if (!result.phoneAccountHandle.isPresent()) {
        result.suggestion =
            SimSuggestionComponent.get(context)
                .getSuggestionProvider()
                .getSuggestion(context, phoneNumber);
      }
      return result;
    }
  }

  @WorkerThread
  @NonNull
  private static Optional<String> getDataId(
      @NonNull Context context, @Nullable String phoneNumber) {
    Assert.isWorkerThread();
    if (VERSION.SDK_INT < VERSION_CODES.N) {
      return Optional.absent();
    }
    try (Cursor cursor =
        context
            .getContentResolver()
            .query(
                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)),
                new String[] {PhoneLookup.DATA_ID},
                null,
                null,
                null)) {
      if (cursor == null) {
        return Optional.absent();
      }
      ImmutableSet<String> validAccountTypes = PreferredAccountUtil.getValidAccountTypes(context);
      String result = null;
      while (cursor.moveToNext()) {
        Optional<String> accountType =
            getAccountType(context.getContentResolver(), cursor.getLong(0));
        if (accountType.isPresent() && !validAccountTypes.contains(accountType.get())) {
          // Empty accountType is treated as writable
          LogUtil.i("CallingAccountSelector.getDataId", "ignoring non-writable " + accountType);
          continue;
        }
        if (result != null && !result.equals(cursor.getString(0))) {
          // TODO(twyen): if there are multiple entries attempt to grab from the contact that
          // initiated the call.
          LogUtil.i("CallingAccountSelector.getDataId", "lookup result not unique, ignoring");
          return Optional.absent();
        }
        result = cursor.getString(0);
      }
      return Optional.fromNullable(result);
    }
  }

  @WorkerThread
  private static Optional<String> getAccountType(ContentResolver contentResolver, long dataId) {
    Assert.isWorkerThread();
    Optional<Long> rawContactId = getRawContactId(contentResolver, dataId);
    if (!rawContactId.isPresent()) {
      return Optional.absent();
    }
    try (Cursor cursor =
        contentResolver.query(
            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId.get()),
            new String[] {RawContacts.ACCOUNT_TYPE},
            null,
            null,
            null)) {
      if (cursor == null || !cursor.moveToFirst()) {
        return Optional.absent();
      }
      return Optional.fromNullable(cursor.getString(0));
    }
  }

  @WorkerThread
  private static Optional<Long> getRawContactId(ContentResolver contentResolver, long dataId) {
    Assert.isWorkerThread();
    try (Cursor cursor =
        contentResolver.query(
            ContentUris.withAppendedId(Data.CONTENT_URI, dataId),
            new String[] {Data.RAW_CONTACT_ID},
            null,
            null,
            null)) {
      if (cursor == null || !cursor.moveToFirst()) {
        return Optional.absent();
      }
      return Optional.of(cursor.getLong(0));
    }
  }

  @WorkerThread
  @NonNull
  private static Optional<PhoneAccountHandle> getPreferredAccount(
      @NonNull Context context, @NonNull String dataId) {
    Assert.isWorkerThread();
    Assert.isNotNull(dataId);
    try (Cursor cursor =
        context
            .getContentResolver()
            .query(
                PreferredSimFallbackContract.CONTENT_URI,
                new String[] {
                  PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
                  PreferredSim.PREFERRED_PHONE_ACCOUNT_ID
                },
                PreferredSim.DATA_ID + " = ?",
                new String[] {dataId},
                null)) {
      if (cursor == null) {
        return Optional.absent();
      }
      if (!cursor.moveToFirst()) {
        return Optional.absent();
      }
      return PreferredAccountUtil.getValidPhoneAccount(
          context, cursor.getString(0), cursor.getString(1));
    }
  }

  private class SelectedListener extends SelectPhoneAccountListener {

    private final PreCallCoordinator coordinator;
    private final PreCallCoordinator.PendingAction listener;
    private final String dataId;
    private final String number;
    private final Suggestion suggestion;
    private final PreferredAccountRecorder recorder;

    public SelectedListener(
        @NonNull PreCallCoordinator builder,
        @NonNull PreCallCoordinator.PendingAction listener,
        @Nullable String dataId,
        @Nullable String number,
        @Nullable Suggestion suggestion) {
        @NonNull PreferredAccountRecorder recorder) {
      this.coordinator = Assert.isNotNull(builder);
      this.listener = Assert.isNotNull(listener);
      this.dataId = dataId;
      this.number = number;
      this.suggestion = suggestion;
      this.recorder = Assert.isNotNull(recorder);
    }

    @MainThread
    @Override
    public void onPhoneAccountSelected(
        PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId) {
      if (suggestion != null) {
        if (suggestion.phoneAccountHandle.equals(selectedAccountHandle)) {
          Logger.get(coordinator.getActivity())
              .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_SUGGESTED_SIM_SELECTED);
        } else {
          Logger.get(coordinator.getActivity())
              .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_NON_SUGGESTED_SIM_SELECTED);
        }
      }
      coordinator.getBuilder().setPhoneAccountHandle(selectedAccountHandle);

      if (dataId != null && setDefault) {
        Logger.get(coordinator.getActivity())
            .logImpression(DialerImpression.Type.DUAL_SIM_SELECTION_PREFERRED_SET);
        DialerExecutorComponent.get(coordinator.getActivity())
            .dialerExecutorFactory()
            .createNonUiTaskBuilder(new WritePreferredAccountWorker())
            .build()
            .executeParallel(
                new WritePreferredAccountWorkerInput(
                    coordinator.getActivity(), dataId, selectedAccountHandle));
      }
      if (number != null) {
        DialerExecutorComponent.get(coordinator.getActivity())
            .dialerExecutorFactory()
            .createNonUiTaskBuilder(
                new UserSelectionReporter(selectedAccountHandle, number, setDefault))
            .build()
            .executeParallel(coordinator.getActivity());
      }
      recorder.record(coordinator.getActivity(), selectedAccountHandle, setDefault);
      listener.finish();
    }

@@ -507,103 +266,4 @@ public class CallingAccountSelector implements PreCallAction {
      listener.finish();
    }
  }

  private static class UserSelectionReporter implements Worker<Context, Void> {

    private final String number;
    private final PhoneAccountHandle phoneAccountHandle;
    private final boolean remember;

    public UserSelectionReporter(
        @NonNull PhoneAccountHandle phoneAccountHandle, @Nullable String number, boolean remember) {
      this.phoneAccountHandle = Assert.isNotNull(phoneAccountHandle);
      this.number = Assert.isNotNull(number);
      this.remember = remember;
    }

    @Nullable
    @Override
    public Void doInBackground(@NonNull Context context) throws Throwable {
      SimSuggestionComponent.get(context)
          .getSuggestionProvider()
          .reportUserSelection(context, number, phoneAccountHandle, remember);
      return null;
    }
  }

  private static class WritePreferredAccountWorkerInput {
    private final Context context;
    private final String dataId;
    private final PhoneAccountHandle phoneAccountHandle;

    WritePreferredAccountWorkerInput(
        @NonNull Context context,
        @NonNull String dataId,
        @NonNull PhoneAccountHandle phoneAccountHandle) {
      this.context = Assert.isNotNull(context);
      this.dataId = Assert.isNotNull(dataId);
      this.phoneAccountHandle = Assert.isNotNull(phoneAccountHandle);
    }
  }

  private static class WritePreferredAccountWorker
      implements Worker<WritePreferredAccountWorkerInput, Void> {

    @Nullable
    @Override
    @WorkerThread
    public Void doInBackground(WritePreferredAccountWorkerInput input) throws Throwable {
      ContentValues values = new ContentValues();
      values.put(
          PreferredSim.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
          input.phoneAccountHandle.getComponentName().flattenToString());
      values.put(PreferredSim.PREFERRED_PHONE_ACCOUNT_ID, input.phoneAccountHandle.getId());
      input
          .context
          .getContentResolver()
          .update(
              PreferredSimFallbackContract.CONTENT_URI,
              values,
              PreferredSim.DATA_ID + " = ?",
              new String[] {String.valueOf(input.dataId)});
      return null;
    }
  }

  @WorkerThread
  private static boolean isPreferredSimEnabled(Context context) {
    Assert.isWorkerThread();
    if (!ConfigProviderBindings.get(context).getBoolean("preferred_sim_enabled", true)) {
      return false;
    }

    Intent quickContactIntent = getQuickContactIntent();
    ResolveInfo resolveInfo =
        context
            .getPackageManager()
            .resolveActivity(quickContactIntent, PackageManager.GET_META_DATA);
    if (resolveInfo == null
        || resolveInfo.activityInfo == null
        || resolveInfo.activityInfo.applicationInfo == null
        || resolveInfo.activityInfo.applicationInfo.metaData == null) {
      LogUtil.e("CallingAccountSelector.isPreferredSimEnabled", "cannot resolve quick contact app");
      return false;
    }
    if (!resolveInfo.activityInfo.applicationInfo.metaData.getBoolean(
        METADATA_SUPPORTS_PREFERRED_SIM, false)) {
      LogUtil.i(
          "CallingAccountSelector.isPreferredSimEnabled",
          "system contacts does not support preferred SIM");
      return false;
    }
    return true;
  }

  @VisibleForTesting
  static Intent getQuickContactIntent() {
    Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
    intent.addCategory(Intent.CATEGORY_DEFAULT);
    intent.setData(Contacts.CONTENT_URI.buildUpon().appendPath("1").build());
    return intent;
  }
}
+0 −8
Original line number Diff line number Diff line
@@ -26,12 +26,4 @@
   same contact and never ask again [CHAR LIMIT=40]-->
  <string name="pre_call_select_phone_account_remember">Remember this choice</string>

  <!-- Hint text under a SIM when selecting SIM to call, indicating the SIM is on the same carrier
   as the outgoing call.[CHAR LIMIT=40]-->
  <string name="pre_call_select_phone_account_hint_intra_carrier">Uses same carrier</string>

  <!-- Hint text under a SIM when selecting SIM to call, indicating user often use the SIM to call
  the contact.[CHAR LIMIT=40]-->
  <string name="pre_call_select_phone_account_hint_frequent">Recently used</string>

</resources>
 No newline at end of file
Loading