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