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