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