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.incallui.multisim;
18 
19 import android.content.Context;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.support.annotation.MainThread;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.Nullable;
25 import android.support.annotation.VisibleForTesting;
26 import android.support.annotation.WorkerThread;
27 import android.telecom.PhoneAccount;
28 import android.telecom.PhoneAccountHandle;
29 import android.telecom.TelecomManager;
30 import com.android.dialer.common.Assert;
31 import com.android.dialer.common.LogUtil;
32 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
33 import com.android.dialer.common.concurrent.ThreadUtil;
34 import com.android.dialer.preferredsim.suggestion.SimSuggestionComponent;
35 import com.android.dialer.util.PermissionsUtil;
36 import com.android.incallui.call.CallList;
37 import com.android.incallui.call.DialerCall;
38 import com.android.incallui.call.DialerCallListener;
39 import com.android.incallui.incalluilock.InCallUiLock;
40 import java.util.concurrent.CountDownLatch;
41 import java.util.concurrent.TimeUnit;
42 
43 /**
44  * Hangs up the current call and redial the call using the {@code otherAccount} instead. the in call
45  * ui will be prevented from closing until the process has finished.
46  */
47 public class SwapSimWorker implements Worker<Void, Void>, DialerCallListener, CallList.Listener {
48 
49   // Timeout waiting for the call to hangup or redial.
50   private static final int DEFAULT_TIMEOUT_MILLIS = 5_000;
51 
52   private final Context context;
53   private final DialerCall call;
54   private final CallList callList;
55   private final InCallUiLock inCallUiLock;
56 
57   private final CountDownLatch disconnectLatch = new CountDownLatch(1);
58   private final CountDownLatch dialingLatch = new CountDownLatch(1);
59 
60   private final PhoneAccountHandle otherAccount;
61   private final String number;
62 
63   private final int timeoutMillis;
64 
65   private CountDownLatch latchForTest;
66 
67   @MainThread
SwapSimWorker( Context context, DialerCall call, CallList callList, PhoneAccountHandle otherAccount, InCallUiLock lock)68   public SwapSimWorker(
69       Context context,
70       DialerCall call,
71       CallList callList,
72       PhoneAccountHandle otherAccount,
73       InCallUiLock lock) {
74     this(context, call, callList, otherAccount, lock, DEFAULT_TIMEOUT_MILLIS);
75   }
76 
77   @VisibleForTesting
SwapSimWorker( Context context, DialerCall call, CallList callList, PhoneAccountHandle otherAccount, InCallUiLock lock, int timeoutMillis)78   SwapSimWorker(
79       Context context,
80       DialerCall call,
81       CallList callList,
82       PhoneAccountHandle otherAccount,
83       InCallUiLock lock,
84       int timeoutMillis) {
85     Assert.isMainThread();
86     this.context = context;
87     this.call = call;
88     this.callList = callList;
89     this.otherAccount = otherAccount;
90     inCallUiLock = lock;
91     this.timeoutMillis = timeoutMillis;
92     number = call.getNumber();
93     call.addListener(this);
94     call.disconnect();
95   }
96 
97   @WorkerThread
98   @Nullable
99   @Override
100   @SuppressWarnings("MissingPermission")
doInBackground(Void unused)101   public Void doInBackground(Void unused) {
102     try {
103       SimSuggestionComponent.get(context)
104           .getSuggestionProvider()
105           .reportIncorrectSuggestion(context, number, otherAccount);
106 
107       if (!PermissionsUtil.hasPhonePermissions(context)) {
108         LogUtil.e("SwapSimWorker.doInBackground", "missing phone permission");
109         return null;
110       }
111       if (!disconnectLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
112         LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to disconnect");
113         return null;
114       }
115       LogUtil.i("SwapSimWorker.doInBackground", "call disconnected, redialing");
116       TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
117       Bundle extras = new Bundle();
118       extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, otherAccount);
119       callList.addListener(this);
120       telecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras);
121       if (latchForTest != null) {
122         latchForTest.countDown();
123       }
124       if (!dialingLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
125         LogUtil.e("SwapSimWorker.doInBackground", "timeout waiting for call to dial");
126       }
127       return null;
128     } catch (InterruptedException e) {
129       LogUtil.e("SwapSimWorker.doInBackground", "interrupted", e);
130       Thread.currentThread().interrupt();
131       return null;
132     } finally {
133       ThreadUtil.postOnUiThread(
134           () -> {
135             call.removeListener(this);
136             callList.removeListener(this);
137             inCallUiLock.release();
138           });
139     }
140   }
141 
142   @MainThread
143   @Override
onDialerCallDisconnect()144   public void onDialerCallDisconnect() {
145     disconnectLatch.countDown();
146   }
147 
148   @Override
onCallListChange(CallList callList)149   public void onCallListChange(CallList callList) {
150     if (callList.getOutgoingCall() != null) {
151       dialingLatch.countDown();
152     }
153   }
154 
155   @VisibleForTesting
setLatchForTest(CountDownLatch latch)156   void setLatchForTest(CountDownLatch latch) {
157     latchForTest = latch;
158   }
159 
160   @Override
onDialerCallUpdate()161   public void onDialerCallUpdate() {}
162 
163   @Override
onDialerCallChildNumberChange()164   public void onDialerCallChildNumberChange() {}
165 
166   @Override
onDialerCallLastForwardedNumberChange()167   public void onDialerCallLastForwardedNumberChange() {}
168 
169   @Override
onDialerCallUpgradeToVideo()170   public void onDialerCallUpgradeToVideo() {}
171 
172   @Override
onDialerCallSessionModificationStateChange()173   public void onDialerCallSessionModificationStateChange() {}
174 
175   @Override
onWiFiToLteHandover()176   public void onWiFiToLteHandover() {}
177 
178   @Override
onHandoverToWifiFailure()179   public void onHandoverToWifiFailure() {}
180 
181   @Override
onInternationalCallOnWifi()182   public void onInternationalCallOnWifi() {}
183 
184   @Override
onEnrichedCallSessionUpdate()185   public void onEnrichedCallSessionUpdate() {}
186 
187   @Override
onIncomingCall(DialerCall call)188   public void onIncomingCall(DialerCall call) {}
189 
190   @Override
onUpgradeToVideo(DialerCall call)191   public void onUpgradeToVideo(DialerCall call) {}
192 
193   @Override
onSessionModificationStateChange(DialerCall call)194   public void onSessionModificationStateChange(DialerCall call) {}
195 
196   @Override
onDisconnect(DialerCall call)197   public void onDisconnect(DialerCall call) {}
198 
199   @Override
onWiFiToLteHandover(DialerCall call)200   public void onWiFiToLteHandover(DialerCall call) {}
201 
202   @Override
onHandoverToWifiFailed(DialerCall call)203   public void onHandoverToWifiFailed(DialerCall call) {}
204 
205   @Override
onInternationalCallOnWifi(@onNull DialerCall call)206   public void onInternationalCallOnWifi(@NonNull DialerCall call) {}
207 }
208