1 /*
2  * Copyright (C) 2020 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.settings.sim.smartForwarding;
18 
19 import static android.telephony.CallForwardingInfo.REASON_NOT_REACHABLE;
20 
21 import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.TAG;
22 
23 import android.content.Context;
24 import android.telephony.CallForwardingInfo;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.TelephonyManager;
27 import android.util.Log;
28 
29 import com.google.common.util.concurrent.SettableFuture;
30 
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.concurrent.Callable;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.Executor;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.TimeoutException;
40 
41 public class EnableSmartForwardingTask
42         implements Callable<EnableSmartForwardingTask.FeatureResult> {
43 
44     private static final int TIMEOUT = 20;
45 
46     private final SubscriptionManager sm;
47     private final TelephonyManager tm;
48     private final String[] mCallForwardingNumber;
49 
50     FeatureResult mResult = new FeatureResult(false, null);
51     SettableFuture<FeatureResult> client = SettableFuture.create();
52 
EnableSmartForwardingTask(Context context, String[] callForwardingNumber)53     public EnableSmartForwardingTask(Context context, String[] callForwardingNumber) {
54         tm = context.getSystemService(TelephonyManager.class);
55         sm = context.getSystemService(SubscriptionManager.class);
56         mCallForwardingNumber = callForwardingNumber;
57     }
58 
59     @Override
call()60     public FeatureResult call() throws TimeoutException, InterruptedException, ExecutionException {
61         FlowController controller = new FlowController();
62         if (controller.init(mCallForwardingNumber)) {
63             controller.startProcess();
64         } else {
65             client.set(mResult);
66         }
67 
68         return client.get(TIMEOUT, TimeUnit.SECONDS);
69     }
70 
71     class FlowController {
72         private SlotUTData[] mSlotUTData;
73         private final ArrayList<Command> mSteps = new ArrayList<>();
74 
init(String[] phoneNum)75         public boolean init(String[] phoneNum) {
76             if (!initObject(phoneNum)) return false;
77             initSteps();
78             return true;
79         }
80 
initObject(String[] phoneNum)81         private boolean initObject(String[] phoneNum) {
82             Executor executor = Executors.newSingleThreadExecutor();
83             if (tm == null || sm == null) {
84                 Log.e(TAG, "TelephonyManager or SubscriptionManager is null");
85                 return false;
86             }
87 
88             if (phoneNum.length != tm.getActiveModemCount()) {
89                 Log.e(TAG, "The length of PhoneNum array should same as phone count.");
90                 return false;
91             }
92 
93             mSlotUTData = new SlotUTData[tm.getActiveModemCount()];
94             for (int i = 0; i < mSlotUTData.length; i++) {
95                 int subId = SubscriptionManager.getSubscriptionId(i);
96 
97                 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
98                     mResult.setReason(FeatureResult.FailedReason.SIM_NOT_ACTIVE);
99                     return false;
100                 }
101 
102                 QueryCallWaitingCommand queryCallWaitingCommand =
103                         new QueryCallWaitingCommand(tm, executor, subId);
104                 QueryCallForwardingCommand queryCallForwardingCommand =
105                         new QueryCallForwardingCommand(tm, executor, subId);
106                 UpdateCallWaitingCommand updateCallWaitingCommand =
107                         new UpdateCallWaitingCommand(tm, executor, queryCallWaitingCommand, subId);
108                 UpdateCallForwardingCommand updateCallForwardingCommand =
109                         new UpdateCallForwardingCommand(tm, executor, queryCallForwardingCommand,
110                                 subId, phoneNum[i]);
111 
112                 mSlotUTData[i] = new SlotUTData(subId, phoneNum[i],
113                         queryCallWaitingCommand,
114                         queryCallForwardingCommand,
115                         updateCallWaitingCommand,
116                         updateCallForwardingCommand);
117             }
118             return true;
119         }
120 
initSteps()121         private void initSteps() {
122             // 1. Query call waiting for each slots
123             for (SlotUTData slotUTData : mSlotUTData) {
124                 mSteps.add(slotUTData.getQueryCallWaitingCommand());
125             }
126 
127             // 2. Query call forwarding for each slots
128             for (SlotUTData slotUTData : mSlotUTData) {
129                 mSteps.add(slotUTData.getQueryCallForwardingCommand());
130             }
131 
132             // 3. Enable call waiting for each slots
133             for (SlotUTData slotUTData : mSlotUTData) {
134                 mSteps.add(slotUTData.getUpdateCallWaitingCommand());
135             }
136 
137             // 4. Set call forwarding for each slots
138             for (SlotUTData slotUTData : mSlotUTData) {
139                 mSteps.add(slotUTData.getUpdateCallForwardingCommand());
140             }
141         }
142 
startProcess()143         public void startProcess() {
144             int index = 0;
145             boolean result = true;
146 
147             // go through all steps
148             while (index < mSteps.size() && result) {
149                 Command currentStep = mSteps.get(index);
150                 Log.d(TAG, "processing : " + currentStep);
151 
152                 try {
153                     result = currentStep.process();
154                 } catch (Exception e) {
155                     Log.d(TAG, "Failed on : " + currentStep, e);
156                     result = false;
157                 }
158 
159                 if (result) {
160                     index++;
161                 } else {
162                     Log.d(TAG, "Failed on : " + currentStep);
163                 }
164             }
165 
166             if (result) {
167                 // No more steps need to perform, return successful to UI.
168                 mResult.result = true;
169                 mResult.slotUTData = mSlotUTData;
170                 Log.d(TAG, "Smart forwarding successful");
171                 client.set(mResult);
172             } else {
173                 restoreAllSteps(index);
174                 client.set(mResult);
175             }
176         }
177 
restoreAllSteps(int index)178         private void restoreAllSteps(int index) {
179             List<Command> restoreCommands = mSteps.subList(0, index);
180             Collections.reverse(restoreCommands);
181             for (Command currentStep : restoreCommands) {
182                 Log.d(TAG, "restoreStep: " + currentStep);
183                 // Only restore update steps
184                 if (currentStep instanceof UpdateCommand) {
185                     ((UpdateCommand) currentStep).onRestore();
186                 }
187             }
188         }
189     }
190 
191     final class SlotUTData {
192         int subId;
193         String mCallForwardingNumber;
194 
195         QueryCallWaitingCommand mQueryCallWaiting;
196         QueryCallForwardingCommand mQueryCallForwarding;
197         UpdateCallWaitingCommand mUpdateCallWaiting;
198         UpdateCallForwardingCommand mUpdateCallForwarding;
199 
SlotUTData(int subId, String callForwardingNumber, QueryCallWaitingCommand queryCallWaiting, QueryCallForwardingCommand queryCallForwarding, UpdateCallWaitingCommand updateCallWaiting, UpdateCallForwardingCommand updateCallForwarding)200         public SlotUTData(int subId,
201                 String callForwardingNumber,
202                 QueryCallWaitingCommand queryCallWaiting,
203                 QueryCallForwardingCommand queryCallForwarding,
204                 UpdateCallWaitingCommand updateCallWaiting,
205                 UpdateCallForwardingCommand updateCallForwarding) {
206             this.subId = subId;
207             this.mCallForwardingNumber = callForwardingNumber;
208             this.mQueryCallWaiting = queryCallWaiting;
209             this.mQueryCallForwarding = queryCallForwarding;
210             this.mUpdateCallWaiting = updateCallWaiting;
211             this.mUpdateCallForwarding = updateCallForwarding;
212         }
213 
getQueryCallWaitingCommand()214         public QueryCallWaitingCommand getQueryCallWaitingCommand() {
215             return mQueryCallWaiting;
216         }
217 
getQueryCallForwardingCommand()218         public QueryCallForwardingCommand getQueryCallForwardingCommand() {
219             return mQueryCallForwarding;
220         }
221 
getUpdateCallWaitingCommand()222         public UpdateCallWaitingCommand getUpdateCallWaitingCommand() {
223             return mUpdateCallWaiting;
224         }
225 
getUpdateCallForwardingCommand()226         public UpdateCallForwardingCommand getUpdateCallForwardingCommand() {
227             return mUpdateCallForwarding;
228         }
229     }
230 
231     interface Command {
process()232         boolean process() throws Exception;
233     }
234 
235     abstract static class QueryCommand<T> implements Command {
236         int subId;
237         TelephonyManager tm;
238         Executor executor;
239 
QueryCommand(TelephonyManager tm, Executor executor, int subId)240         public QueryCommand(TelephonyManager tm, Executor executor, int subId) {
241             this.subId = subId;
242             this.tm = tm;
243             this.executor = executor;
244         }
245 
246         @Override
toString()247         public String toString() {
248             return this.getClass().getSimpleName() + "[SubId " + subId + "]";
249         }
250 
getResult()251         abstract T getResult();
252     }
253 
254     abstract static class UpdateCommand<T> implements Command {
255         int subId;
256         TelephonyManager tm;
257         Executor executor;
258 
UpdateCommand(TelephonyManager tm, Executor executor, int subId)259         public UpdateCommand(TelephonyManager tm, Executor executor, int subId) {
260             this.subId = subId;
261             this.tm = tm;
262             this.executor = executor;
263         }
264 
265         @Override
toString()266         public String toString() {
267             return this.getClass().getSimpleName() + "[SubId " + subId + "] ";
268         }
269 
onRestore()270         abstract void onRestore();
271     }
272 
273     static class QueryCallWaitingCommand extends QueryCommand<Integer> {
274         int result;
275         SettableFuture<Boolean> resultFuture = SettableFuture.create();
276 
QueryCallWaitingCommand(TelephonyManager tm, Executor executor, int subId)277         public QueryCallWaitingCommand(TelephonyManager tm, Executor executor, int subId) {
278             super(tm, executor, subId);
279         }
280 
281         @Override
process()282         public boolean process() throws Exception {
283             tm.createForSubscriptionId(subId)
284                     .getCallWaitingStatus(executor, this::queryStatusCallBack);
285             return resultFuture.get();
286         }
287 
288         @Override
getResult()289         Integer getResult() {
290             return result;
291         }
292 
queryStatusCallBack(int result)293         public void queryStatusCallBack(int result) {
294             this.result = result;
295 
296             if (result == TelephonyManager.CALL_WAITING_STATUS_ENABLED
297                     || result == TelephonyManager.CALL_WAITING_STATUS_DISABLED) {
298                 Log.d(TAG, "Call Waiting result: " + result);
299                 resultFuture.set(true);
300             } else {
301                 resultFuture.set(false);
302             }
303         }
304     }
305 
306     static class QueryCallForwardingCommand extends QueryCommand<CallForwardingInfo> {
307         CallForwardingInfo result;
308         SettableFuture<Boolean> resultFuture = SettableFuture.create();
309 
QueryCallForwardingCommand(TelephonyManager tm, Executor executor, int subId)310         public QueryCallForwardingCommand(TelephonyManager tm, Executor executor, int subId) {
311             super(tm, executor, subId);
312         }
313 
314         @Override
process()315         public boolean process() throws Exception{
316             tm.createForSubscriptionId(subId)
317                     .getCallForwarding(REASON_NOT_REACHABLE, executor,
318                             new TelephonyManager.CallForwardingInfoCallback() {
319                                 @Override
320                                 public void onCallForwardingInfoAvailable(CallForwardingInfo info) {
321                                     Log.d(TAG, "Call Forwarding result: " + info);
322                                     result = info;
323                                     resultFuture.set(true);
324                                 }
325 
326                                 @Override
327                                 public void onError(int error) {
328                                     Log.d(TAG, "Query Call Forwarding failed.");
329                                     resultFuture.set(false);
330                                 }
331                             });
332             return resultFuture.get();
333         }
334 
335         @Override
getResult()336         CallForwardingInfo getResult() {
337             return result;
338         }
339     }
340 
341     static class UpdateCallWaitingCommand extends UpdateCommand<Integer> {
342         SettableFuture<Boolean> resultFuture = SettableFuture.create();
343         QueryCallWaitingCommand queryResult;
344 
UpdateCallWaitingCommand(TelephonyManager tm, Executor executor, QueryCallWaitingCommand queryCallWaitingCommand, int subId)345         public UpdateCallWaitingCommand(TelephonyManager tm, Executor executor,
346                 QueryCallWaitingCommand queryCallWaitingCommand, int subId) {
347             super(tm, executor, subId);
348             this.queryResult = queryCallWaitingCommand;
349         }
350 
351         @Override
process()352         public boolean process() throws Exception {
353             tm.createForSubscriptionId(subId)
354                     .setCallWaitingEnabled(true, executor, this::updateStatusCallBack);
355             return resultFuture.get();
356         }
357 
updateStatusCallBack(int result)358         public void updateStatusCallBack(int result) {
359             Log.d(TAG, "UpdateCallWaitingCommand updateStatusCallBack result: " + result);
360             if (result == TelephonyManager.CALL_WAITING_STATUS_ENABLED
361                     || result == TelephonyManager.CALL_WAITING_STATUS_DISABLED) {
362                 resultFuture.set(true);
363             } else {
364                 resultFuture.set(false);
365             }
366         }
367 
368         @Override
onRestore()369         void onRestore() {
370             Log.d(TAG, "onRestore: " + this);
371             if (queryResult.getResult() != TelephonyManager.CALL_WAITING_STATUS_ENABLED) {
372                 tm.createForSubscriptionId(subId)
373                         .setCallWaitingEnabled(false, null, null);
374             }
375         }
376     }
377 
378     static class UpdateCallForwardingCommand extends UpdateCommand<Integer> {
379         String phoneNum;
380         SettableFuture<Boolean> resultFuture = SettableFuture.create();
381         QueryCallForwardingCommand queryResult;
382 
UpdateCallForwardingCommand(TelephonyManager tm, Executor executor, QueryCallForwardingCommand queryCallForwardingCommand, int subId, String phoneNum)383         public UpdateCallForwardingCommand(TelephonyManager tm, Executor executor,
384                 QueryCallForwardingCommand queryCallForwardingCommand,
385                 int subId, String phoneNum) {
386             super(tm, executor, subId);
387             this.phoneNum = phoneNum;
388             this.queryResult = queryCallForwardingCommand;
389         }
390 
391         @Override
process()392         public boolean process() throws Exception {
393             CallForwardingInfo info = new CallForwardingInfo(
394                     true, REASON_NOT_REACHABLE, phoneNum, 3);
395             tm.createForSubscriptionId(subId)
396                     .setCallForwarding(info, executor, this::updateStatusCallBack);
397             return resultFuture.get();
398         }
399 
updateStatusCallBack(int result)400         public void updateStatusCallBack(int result) {
401             Log.d(TAG, "UpdateCallForwardingCommand updateStatusCallBack : " + result);
402             if (result == TelephonyManager.CallForwardingInfoCallback.RESULT_SUCCESS) {
403                 resultFuture.set(true);
404             } else {
405                 resultFuture.set(false);
406             }
407         }
408 
409         @Override
onRestore()410         void onRestore() {
411             Log.d(TAG, "onRestore: " + this);
412 
413             tm.createForSubscriptionId(subId)
414                     .setCallForwarding(queryResult.getResult(), null, null);
415         }
416     }
417 
418     public static class FeatureResult {
419         enum FailedReason {
420             NETWORK_ERROR,
421             SIM_NOT_ACTIVE
422         }
423 
424         private boolean result;
425         private FailedReason reason;
426         private SlotUTData[] slotUTData;
427 
FeatureResult(boolean result, SlotUTData[] slotUTData)428         public FeatureResult(boolean result, SlotUTData[] slotUTData) {
429             this.result = result;
430             this.slotUTData = slotUTData;
431         }
432 
getResult()433         public boolean getResult() {
434             return result;
435         }
436 
getSlotUTData()437         public SlotUTData[] getSlotUTData() {
438             return slotUTData;
439         }
440 
setReason(FailedReason reason)441         public void setReason(FailedReason reason) {
442             this.reason = reason;
443         }
444 
getReason()445         public FailedReason getReason() {
446             return reason;
447         }
448     }
449 }
450