1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.precall.impl; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.support.annotation.MainThread; 22 import android.support.annotation.NonNull; 23 import android.support.annotation.Nullable; 24 import android.support.annotation.VisibleForTesting; 25 import android.telecom.PhoneAccount; 26 import android.telecom.PhoneAccountHandle; 27 import android.telecom.TelecomManager; 28 import android.telephony.PhoneNumberUtils; 29 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; 30 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; 31 import com.android.contacts.common.widget.SelectPhoneAccountDialogOptions; 32 import com.android.dialer.callintent.CallIntentBuilder; 33 import com.android.dialer.common.Assert; 34 import com.android.dialer.common.LogUtil; 35 import com.android.dialer.configprovider.ConfigProviderComponent; 36 import com.android.dialer.logging.DialerImpression.Type; 37 import com.android.dialer.logging.Logger; 38 import com.android.dialer.precall.PreCallAction; 39 import com.android.dialer.precall.PreCallCoordinator; 40 import com.android.dialer.precall.PreCallCoordinator.PendingAction; 41 import com.android.dialer.preferredsim.PreferredAccountRecorder; 42 import com.android.dialer.preferredsim.PreferredAccountWorker; 43 import com.android.dialer.preferredsim.suggestion.SuggestionProvider; 44 import com.android.dialer.preferredsim.suggestion.SuggestionProvider.Suggestion; 45 import java.util.List; 46 import javax.inject.Inject; 47 48 /** PreCallAction to select which phone account to call with. Ignored if there's only one account */ 49 @SuppressWarnings("MissingPermission") 50 public class CallingAccountSelector implements PreCallAction { 51 52 @VisibleForTesting static final String TAG_CALLING_ACCOUNT_SELECTOR = "CallingAccountSelector"; 53 54 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment; 55 56 private boolean isDiscarding; 57 58 private final PreferredAccountWorker preferredAccountWorker; 59 60 @Inject CallingAccountSelector(PreferredAccountWorker preferredAccountWorker)61 CallingAccountSelector(PreferredAccountWorker preferredAccountWorker) { 62 this.preferredAccountWorker = preferredAccountWorker; 63 } 64 65 @Override requiresUi(Context context, CallIntentBuilder builder)66 public boolean requiresUi(Context context, CallIntentBuilder builder) { 67 if (!ConfigProviderComponent.get(context) 68 .getConfigProvider() 69 .getBoolean("precall_calling_account_selector_enabled", true)) { 70 return false; 71 } 72 73 if (builder.getPhoneAccountHandle() != null) { 74 return false; 75 } 76 if (PhoneNumberUtils.isEmergencyNumber(builder.getUri().getSchemeSpecificPart())) { 77 return false; 78 } 79 80 TelecomManager telecomManager = context.getSystemService(TelecomManager.class); 81 List<PhoneAccountHandle> accounts = telecomManager.getCallCapablePhoneAccounts(); 82 if (accounts.size() <= 1) { 83 return false; 84 } 85 return true; 86 } 87 88 @Override runWithoutUi(Context context, CallIntentBuilder builder)89 public void runWithoutUi(Context context, CallIntentBuilder builder) { 90 // do nothing. 91 } 92 93 @Override runWithUi(PreCallCoordinator coordinator)94 public void runWithUi(PreCallCoordinator coordinator) { 95 CallIntentBuilder builder = coordinator.getBuilder(); 96 if (!requiresUi(coordinator.getActivity(), builder)) { 97 return; 98 } 99 switch (builder.getUri().getScheme()) { 100 case PhoneAccount.SCHEME_VOICEMAIL: 101 showDialog( 102 coordinator, 103 coordinator.startPendingAction(), 104 preferredAccountWorker.getVoicemailDialogOptions(), 105 null, 106 null, 107 null); 108 Logger.get(coordinator.getActivity()).logImpression(Type.DUAL_SIM_SELECTION_VOICEMAIL); 109 break; 110 case PhoneAccount.SCHEME_TEL: 111 processPreferredAccount(coordinator); 112 break; 113 default: 114 // might be PhoneAccount.SCHEME_SIP 115 LogUtil.e( 116 "CallingAccountSelector.run", 117 "unable to process scheme " + builder.getUri().getScheme()); 118 break; 119 } 120 } 121 122 /** Initiates a background worker to find if there's any preferred account. */ 123 @MainThread processPreferredAccount(PreCallCoordinator coordinator)124 private void processPreferredAccount(PreCallCoordinator coordinator) { 125 Assert.isMainThread(); 126 CallIntentBuilder builder = coordinator.getBuilder(); 127 Activity activity = coordinator.getActivity(); 128 String phoneNumber = builder.getUri().getSchemeSpecificPart(); 129 PendingAction pendingAction = coordinator.startPendingAction(); 130 131 coordinator.listen( 132 preferredAccountWorker.selectAccount( 133 phoneNumber, 134 activity.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()), 135 result -> { 136 if (isDiscarding) { 137 // pendingAction is dropped by the coordinator before onDiscard is triggered. 138 return; 139 } 140 if (result.getSelectedPhoneAccountHandle().isPresent()) { 141 142 if (result.getSuggestion().isPresent() 143 && result 144 .getSelectedPhoneAccountHandle() 145 .get() 146 .equals(result.getSuggestion().get().phoneAccountHandle)) { 147 builder 148 .getInCallUiIntentExtras() 149 .putString( 150 SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON, 151 result.getSuggestion().get().reason.name()); 152 } 153 154 coordinator 155 .getBuilder() 156 .setPhoneAccountHandle(result.getSelectedPhoneAccountHandle().get()); 157 pendingAction.finish(); 158 return; 159 } 160 showDialog( 161 coordinator, 162 pendingAction, 163 result.getDialogOptionsBuilder().get().build(), 164 result.getDataId().orNull(), 165 phoneNumber, 166 result.getSuggestion().orNull()); 167 }, 168 (throwable) -> { 169 throw new RuntimeException(throwable); 170 }); 171 } 172 173 @MainThread showDialog( PreCallCoordinator coordinator, PendingAction pendingAction, SelectPhoneAccountDialogOptions dialogOptions, @Nullable String dataId, @Nullable String number, @Nullable Suggestion suggestion)174 private void showDialog( 175 PreCallCoordinator coordinator, 176 PendingAction pendingAction, 177 SelectPhoneAccountDialogOptions dialogOptions, 178 @Nullable String dataId, 179 @Nullable String number, 180 @Nullable Suggestion suggestion) { 181 Assert.isMainThread(); 182 183 selectPhoneAccountDialogFragment = 184 SelectPhoneAccountDialogFragment.newInstance( 185 dialogOptions, 186 new SelectedListener( 187 coordinator, 188 pendingAction, 189 new PreferredAccountRecorder(number, suggestion, dataId))); 190 selectPhoneAccountDialogFragment.show( 191 coordinator.getActivity().getFragmentManager(), TAG_CALLING_ACCOUNT_SELECTOR); 192 } 193 194 @MainThread 195 @Override onDiscard()196 public void onDiscard() { 197 isDiscarding = true; 198 if (selectPhoneAccountDialogFragment != null) { 199 selectPhoneAccountDialogFragment.dismiss(); 200 } 201 } 202 203 private class SelectedListener extends SelectPhoneAccountListener { 204 205 private final PreCallCoordinator coordinator; 206 private final PreCallCoordinator.PendingAction listener; 207 private final PreferredAccountRecorder recorder; 208 SelectedListener( @onNull PreCallCoordinator builder, @NonNull PreCallCoordinator.PendingAction listener, @NonNull PreferredAccountRecorder recorder)209 public SelectedListener( 210 @NonNull PreCallCoordinator builder, 211 @NonNull PreCallCoordinator.PendingAction listener, 212 @NonNull PreferredAccountRecorder recorder) { 213 this.coordinator = Assert.isNotNull(builder); 214 this.listener = Assert.isNotNull(listener); 215 this.recorder = Assert.isNotNull(recorder); 216 } 217 218 @MainThread 219 @Override onPhoneAccountSelected( PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId)220 public void onPhoneAccountSelected( 221 PhoneAccountHandle selectedAccountHandle, boolean setDefault, @Nullable String callId) { 222 coordinator.getBuilder().setPhoneAccountHandle(selectedAccountHandle); 223 recorder.record(coordinator.getActivity(), selectedAccountHandle, setDefault); 224 listener.finish(); 225 } 226 227 @MainThread 228 @Override onDialogDismissed(@ullable String callId)229 public void onDialogDismissed(@Nullable String callId) { 230 if (isDiscarding) { 231 return; 232 } 233 coordinator.abortCall(); 234 listener.finish(); 235 } 236 } 237 } 238