/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.incallui.multisim; import android.content.Context; import android.net.Uri; import android.os.Bundle; 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; 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.ThreadUtil; import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent; import com.android.dialer.util.PermissionsUtil; import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; import com.android.incallui.call.DialerCallListener; import com.android.incallui.incalluilock.InCallUiLock; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Hangs up the current call and redial the call using the {@code otherAccount} instead. the in call * ui will be prevented from closing until the process has finished. */ public class SwapSimWorker implements Worker, DialerCallListener, CallList.Listener { // Timeout waiting for the call to hangup or redial. private static final int DEFAULT_TIMEOUT_MILLIS = 5_000; private final Context context; private final DialerCall call; private final CallList callList; private final InCallUiLock inCallUiLock; private final CountDownLatch disconnectLatch = new CountDownLatch(1); private final CountDownLatch dialingLatch = new CountDownLatch(1); private final PhoneAccountHandle otherAccount; private final String number; private final int timeoutMillis; private CountDownLatch latchForTest; @MainThread public SwapSimWorker( Context context, DialerCall call, CallList callList, PhoneAccountHandle otherAccount, InCallUiLock lock) { this(context, call, callList, otherAccount, lock, DEFAULT_TIMEOUT_MILLIS); } @VisibleForTesting SwapSimWorker( Context context, DialerCall call, CallList callList, PhoneAccountHandle otherAccount, InCallUiLock lock, int timeoutMillis) { Assert.isMainThread(); this.context = context; this.call = call; this.callList = callList; this.otherAccount = otherAccount; inCallUiLock = lock; this.timeoutMillis = timeoutMillis; number = call.getNumber(); call.addListener(this); call.disconnect(); } @WorkerThread @Nullable @Override @SuppressWarnings("MissingPermission") public Void doInBackground(Void unused) { try { SimSuggestionComponent.get(context) .getSuggestionProvider() .reportIncorrectSuggestion(context, number, otherAccount); if (!PermissionsUtil.hasPhonePermissions(context)) { LogUtil.e("SwapSimWorker.doInBackground", "missing phone permission"); return null; } if (!disconnectLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to disconnect"); return null; } LogUtil.i("SwapSimWorker.doInBackground", "call disconnected, redialing"); TelecomManager telecomManager = context.getSystemService(TelecomManager.class); Bundle extras = new Bundle(); extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, otherAccount); callList.addListener(this); telecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras); if (latchForTest != null) { latchForTest.countDown(); } if (!dialingLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to dial"); } return null; } catch (InterruptedException e) { LogUtil.e("SwapSimWorker.doInBackground", "interrupted", e); Thread.currentThread().interrupt(); return null; } finally { ThreadUtil.postOnUiThread( () -> { call.removeListener(this); callList.removeListener(this); inCallUiLock.release(); }); } } @MainThread @Override public void onDialerCallDisconnect() { disconnectLatch.countDown(); } @Override public void onCallListChange(CallList callList) { if (callList.getOutgoingCall() != null) { dialingLatch.countDown(); } } @VisibleForTesting void setLatchForTest(CountDownLatch latch) { latchForTest = latch; } @Override public void onDialerCallUpdate() {} @Override public void onDialerCallChildNumberChange() {} @Override public void onDialerCallLastForwardedNumberChange() {} @Override public void onDialerCallUpgradeToVideo() {} @Override public void onDialerCallSessionModificationStateChange() {} @Override public void onWiFiToLteHandover() {} @Override public void onHandoverToWifiFailure() {} @Override public void onInternationalCallOnWifi() {} @Override public void onEnrichedCallSessionUpdate() {} @Override public void onIncomingCall(DialerCall call) {} @Override public void onUpgradeToVideo(DialerCall call) {} @Override public void onSessionModificationStateChange(DialerCall call) {} @Override public void onDisconnect(DialerCall call) {} @Override public void onWiFiToLteHandover(DialerCall call) {} @Override public void onHandoverToWifiFailed(DialerCall call) {} @Override public void onInternationalCallOnWifi(@NonNull DialerCall call) {} }