diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f399fc830ac..8b3160f213d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3672,6 +3672,12 @@
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bcae9a5efd4..fb80f293fb5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12627,4 +12627,29 @@
See all apps
+
+
+ Smart Forwarding
+
+ Smart Forwarding Enabled
+
+ Smart Forwarding Disabled
+
+ Call Settings
+
+ Updating Settings...
+
+ Call Settings error
+
+ Network or SIM card error.
+
+ Sim is not activated.
+
+ Enter Phone numbers
+
+ Enter Phone number
+
+ Phone number is missing.
+
+ OK
diff --git a/res/xml/smart_forwarding_mdn_handler.xml b/res/xml/smart_forwarding_mdn_handler.xml
new file mode 100644
index 00000000000..cefbcbf1cf8
--- /dev/null
+++ b/res/xml/smart_forwarding_mdn_handler.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/smart_forwarding_mdn_handler_header.xml b/res/xml/smart_forwarding_mdn_handler_header.xml
new file mode 100644
index 00000000000..30e873f445d
--- /dev/null
+++ b/res/xml/smart_forwarding_mdn_handler_header.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/smart_forwarding_switch.xml b/res/xml/smart_forwarding_switch.xml
new file mode 100644
index 00000000000..dda9d5a4523
--- /dev/null
+++ b/res/xml/smart_forwarding_switch.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java b/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java
new file mode 100644
index 00000000000..45333ec7bcb
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/DisableSmartForwardingTask.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.TAG;
+
+import android.telephony.CallForwardingInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+public class DisableSmartForwardingTask implements Runnable {
+ private final TelephonyManager tm;
+ private final boolean[] callWaitingStatus;
+ private final CallForwardingInfo[] callForwardingInfo;
+
+ public DisableSmartForwardingTask(TelephonyManager tm,
+ boolean[] callWaitingStatus, CallForwardingInfo[] callForwardingInfo) {
+ this.tm = tm;
+ this.callWaitingStatus = callWaitingStatus;
+ this.callForwardingInfo = callForwardingInfo;
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < tm.getActiveModemCount(); i++) {
+ if (callWaitingStatus != null) {
+ Log.d(TAG, "Restore call waiting to " + callWaitingStatus[i]);
+ tm.setCallWaitingEnabled(callWaitingStatus[i], null, null);
+ }
+
+ if (callForwardingInfo != null
+ && callForwardingInfo[i] != null
+ && callForwardingInfo[i].getTimeoutSeconds() > 0) {
+ Log.d(TAG, "Restore call waiting to " + callForwardingInfo);
+ tm.setCallForwarding(callForwardingInfo[i], null, null);
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/sim/smartForwarding/EnableSmartForwardingTask.java b/src/com/android/settings/sim/smartForwarding/EnableSmartForwardingTask.java
new file mode 100644
index 00000000000..6fe62e0fc9c
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/EnableSmartForwardingTask.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import static android.telephony.CallForwardingInfo.REASON_NOT_REACHABLE;
+
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.TAG;
+
+import android.content.Context;
+import android.telephony.CallForwardingInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class EnableSmartForwardingTask
+ implements Callable {
+
+ private static final int TIMEOUT = 20;
+
+ private final SubscriptionManager sm;
+ private final TelephonyManager tm;
+ private final String[] mCallForwardingNumber;
+
+ FeatureResult mResult = new FeatureResult(false, null);
+ SettableFuture client = SettableFuture.create();
+
+ public EnableSmartForwardingTask(Context context, String[] callForwardingNumber) {
+ tm = context.getSystemService(TelephonyManager.class);
+ sm = context.getSystemService(SubscriptionManager.class);
+ mCallForwardingNumber = callForwardingNumber;
+ }
+
+ @Override
+ public FeatureResult call() throws TimeoutException, InterruptedException, ExecutionException {
+ FlowController controller = new FlowController();
+ if (controller.init(mCallForwardingNumber)) {
+ controller.startProcess();
+ } else {
+ client.set(mResult);
+ }
+
+ return client.get(TIMEOUT, TimeUnit.SECONDS);
+ }
+
+ class FlowController {
+ private SlotUTData[] mSlotUTData;
+ private final ArrayList mSteps = new ArrayList<>();
+
+ public boolean init(String[] phoneNum) {
+ if (!initObject(phoneNum)) return false;
+ initSteps();
+ return true;
+ }
+
+ private boolean initObject(String[] phoneNum) {
+ Executor executor = Executors.newSingleThreadExecutor();
+ if (tm == null || sm == null) {
+ Log.e(TAG, "TelephonyManager or SubscriptionManager is null");
+ return false;
+ }
+
+ if (phoneNum.length != tm.getActiveModemCount()) {
+ Log.e(TAG, "The length of PhoneNum array should same as phone count.");
+ return false;
+ }
+
+ mSlotUTData = new SlotUTData[tm.getActiveModemCount()];
+ for (int i = 0; i < mSlotUTData.length; i++) {
+ int[] subIdList = sm.getSubscriptionIds(i);
+ if (subIdList.length < 1) {
+ Log.e(TAG, "getSubscriptionIds() return empty sub id list.");
+ return false;
+ }
+ int subId = subIdList[0];
+
+ if (!sm.isActiveSubId(subId)) {
+ mResult.setReason(FeatureResult.FailedReason.SIM_NOT_ACTIVE);
+ return false;
+ }
+
+ QueryCallWaitingCommand queryCallWaitingCommand =
+ new QueryCallWaitingCommand(tm, executor, subId);
+ QueryCallForwardingCommand queryCallForwardingCommand =
+ new QueryCallForwardingCommand(tm, executor, subId);
+ UpdateCallWaitingCommand updateCallWaitingCommand =
+ new UpdateCallWaitingCommand(tm, executor, queryCallWaitingCommand, subId);
+ UpdateCallForwardingCommand updateCallForwardingCommand =
+ new UpdateCallForwardingCommand(tm, executor, queryCallForwardingCommand,
+ subId, phoneNum[i]);
+
+ mSlotUTData[i] = new SlotUTData(subId, phoneNum[i],
+ queryCallWaitingCommand,
+ queryCallForwardingCommand,
+ updateCallWaitingCommand,
+ updateCallForwardingCommand);
+ }
+ return true;
+ }
+
+ private void initSteps() {
+ // 1. Query call waiting for each slots
+ for (SlotUTData slotUTData : mSlotUTData) {
+ mSteps.add(slotUTData.getQueryCallWaitingCommand());
+ }
+
+ // 2. Query call forwarding for each slots
+ for (SlotUTData slotUTData : mSlotUTData) {
+ mSteps.add(slotUTData.getQueryCallForwardingCommand());
+ }
+
+ // 3. Enable call waiting for each slots
+ for (SlotUTData slotUTData : mSlotUTData) {
+ mSteps.add(slotUTData.getUpdateCallWaitingCommand());
+ }
+
+ // 4. Set call forwarding for each slots
+ for (SlotUTData slotUTData : mSlotUTData) {
+ mSteps.add(slotUTData.getUpdateCallForwardingCommand());
+ }
+ }
+
+ public void startProcess() {
+ int index = 0;
+ boolean result = true;
+
+ // go through all steps
+ while (index < mSteps.size() && result) {
+ Command currentStep = mSteps.get(index);
+ Log.d(TAG, "processing : " + currentStep);
+
+ try {
+ result = currentStep.process();
+ } catch (Exception e) {
+ Log.d(TAG, "Failed on : " + currentStep, e);
+ result = false;
+ }
+
+ if (result) {
+ index++;
+ } else {
+ Log.d(TAG, "Failed on : " + currentStep);
+ }
+ }
+
+ if (result) {
+ // No more steps need to perform, return successful to UI.
+ mResult.result = true;
+ mResult.slotUTData = mSlotUTData;
+ Log.d(TAG, "Smart forwarding successful");
+ client.set(mResult);
+ } else {
+ restoreAllSteps(index);
+ client.set(mResult);
+ }
+ }
+
+ private void restoreAllSteps(int index) {
+ List restoreCommands = mSteps.subList(0, index);
+ Collections.reverse(restoreCommands);
+ for (Command currentStep : restoreCommands) {
+ Log.d(TAG, "restoreStep: " + currentStep);
+ // Only restore update steps
+ if (currentStep instanceof UpdateCommand) {
+ ((UpdateCommand) currentStep).onRestore();
+ }
+ }
+ }
+ }
+
+ final class SlotUTData {
+ int subId;
+ String mCallForwardingNumber;
+
+ QueryCallWaitingCommand mQueryCallWaiting;
+ QueryCallForwardingCommand mQueryCallForwarding;
+ UpdateCallWaitingCommand mUpdateCallWaiting;
+ UpdateCallForwardingCommand mUpdateCallForwarding;
+
+ public SlotUTData(int subId,
+ String callForwardingNumber,
+ QueryCallWaitingCommand queryCallWaiting,
+ QueryCallForwardingCommand queryCallForwarding,
+ UpdateCallWaitingCommand updateCallWaiting,
+ UpdateCallForwardingCommand updateCallForwarding) {
+ this.subId = subId;
+ this.mCallForwardingNumber = callForwardingNumber;
+ this.mQueryCallWaiting = queryCallWaiting;
+ this.mQueryCallForwarding = queryCallForwarding;
+ this.mUpdateCallWaiting = updateCallWaiting;
+ this.mUpdateCallForwarding = updateCallForwarding;
+ }
+
+ public QueryCallWaitingCommand getQueryCallWaitingCommand() {
+ return mQueryCallWaiting;
+ }
+
+ public QueryCallForwardingCommand getQueryCallForwardingCommand() {
+ return mQueryCallForwarding;
+ }
+
+ public UpdateCallWaitingCommand getUpdateCallWaitingCommand() {
+ return mUpdateCallWaiting;
+ }
+
+ public UpdateCallForwardingCommand getUpdateCallForwardingCommand() {
+ return mUpdateCallForwarding;
+ }
+ }
+
+ interface Command {
+ boolean process() throws Exception;
+ }
+
+ abstract static class QueryCommand implements Command {
+ int subId;
+ TelephonyManager tm;
+ Executor executor;
+
+ public QueryCommand(TelephonyManager tm, Executor executor, int subId) {
+ this.subId = subId;
+ this.tm = tm;
+ this.executor = executor;
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "[SubId " + subId + "]";
+ }
+
+ abstract T getResult();
+ }
+
+ abstract static class UpdateCommand implements Command {
+ int subId;
+ TelephonyManager tm;
+ Executor executor;
+
+ public UpdateCommand(TelephonyManager tm, Executor executor, int subId) {
+ this.subId = subId;
+ this.tm = tm;
+ this.executor = executor;
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "[SubId " + subId + "] ";
+ }
+
+ abstract void onRestore();
+ }
+
+ static class QueryCallWaitingCommand extends QueryCommand {
+ int result;
+ SettableFuture resultFuture = SettableFuture.create();
+
+ public QueryCallWaitingCommand(TelephonyManager tm, Executor executor, int subId) {
+ super(tm, executor, subId);
+ }
+
+ @Override
+ public boolean process() throws Exception {
+ tm.createForSubscriptionId(subId)
+ .getCallWaitingStatus(executor, this::queryStatusCallBack);
+ return resultFuture.get();
+ }
+
+ @Override
+ Integer getResult() {
+ return result;
+ }
+
+ public void queryStatusCallBack(int result) {
+ this.result = result;
+
+ if (result == TelephonyManager.CALL_WAITING_STATUS_ENABLED
+ || result == TelephonyManager.CALL_WAITING_STATUS_DISABLED) {
+ Log.d(TAG, "Call Waiting result: " + result);
+ resultFuture.set(true);
+ } else {
+ resultFuture.set(false);
+ }
+ }
+ }
+
+ static class QueryCallForwardingCommand extends QueryCommand {
+ CallForwardingInfo result;
+ SettableFuture resultFuture = SettableFuture.create();
+
+ public QueryCallForwardingCommand(TelephonyManager tm, Executor executor, int subId) {
+ super(tm, executor, subId);
+ }
+
+ @Override
+ public boolean process() throws Exception{
+ tm.createForSubscriptionId(subId)
+ .getCallForwarding(REASON_NOT_REACHABLE, executor,
+ new TelephonyManager.CallForwardingInfoCallback() {
+ @Override
+ public void onCallForwardingInfoAvailable(CallForwardingInfo info) {
+ Log.d(TAG, "Call Forwarding result: " + info);
+ result = info;
+ resultFuture.set(true);
+ }
+
+ @Override
+ public void onError(int error) {
+ Log.d(TAG, "Query Call Forwarding failed.");
+ resultFuture.set(false);
+ }
+ });
+ return resultFuture.get();
+ }
+
+ @Override
+ CallForwardingInfo getResult() {
+ return result;
+ }
+ }
+
+ static class UpdateCallWaitingCommand extends UpdateCommand {
+ SettableFuture resultFuture = SettableFuture.create();
+ QueryCallWaitingCommand queryResult;
+
+ public UpdateCallWaitingCommand(TelephonyManager tm, Executor executor,
+ QueryCallWaitingCommand queryCallWaitingCommand, int subId) {
+ super(tm, executor, subId);
+ this.queryResult = queryCallWaitingCommand;
+ }
+
+ @Override
+ public boolean process() throws Exception {
+ tm.createForSubscriptionId(subId)
+ .setCallWaitingEnabled(true, executor, this::updateStatusCallBack);
+ return resultFuture.get();
+ }
+
+ public void updateStatusCallBack(int result) {
+ Log.d(TAG, "UpdateCallWaitingCommand updateStatusCallBack result: " + result);
+ if (result == TelephonyManager.CALL_WAITING_STATUS_ENABLED
+ || result == TelephonyManager.CALL_WAITING_STATUS_DISABLED) {
+ resultFuture.set(true);
+ } else {
+ resultFuture.set(false);
+ }
+ }
+
+ @Override
+ void onRestore() {
+ Log.d(TAG, "onRestore: " + this);
+ if (queryResult.getResult() != TelephonyManager.CALL_WAITING_STATUS_ENABLED) {
+ tm.createForSubscriptionId(subId)
+ .setCallWaitingEnabled(false, null, null);
+ }
+ }
+ }
+
+ static class UpdateCallForwardingCommand extends UpdateCommand {
+ String phoneNum;
+ SettableFuture resultFuture = SettableFuture.create();
+ QueryCallForwardingCommand queryResult;
+
+ public UpdateCallForwardingCommand(TelephonyManager tm, Executor executor,
+ QueryCallForwardingCommand queryCallForwardingCommand,
+ int subId, String phoneNum) {
+ super(tm, executor, subId);
+ this.phoneNum = phoneNum;
+ this.queryResult = queryCallForwardingCommand;
+ }
+
+ @Override
+ public boolean process() throws Exception {
+ CallForwardingInfo info = new CallForwardingInfo(
+ true, REASON_NOT_REACHABLE, phoneNum, 3);
+ tm.createForSubscriptionId(subId)
+ .setCallForwarding(info, executor, this::updateStatusCallBack);
+ return resultFuture.get();
+ }
+
+ public void updateStatusCallBack(int result) {
+ Log.d(TAG, "UpdateCallForwardingCommand updateStatusCallBack : " + result);
+ if (result == TelephonyManager.CallForwardingInfoCallback.RESULT_SUCCESS) {
+ resultFuture.set(true);
+ } else {
+ resultFuture.set(false);
+ }
+ }
+
+ @Override
+ void onRestore() {
+ Log.d(TAG, "onRestore: " + this);
+
+ tm.createForSubscriptionId(subId)
+ .setCallForwarding(queryResult.getResult(), null, null);
+ }
+ }
+
+ public static class FeatureResult {
+ enum FailedReason {
+ NETWORK_ERROR,
+ SIM_NOT_ACTIVE
+ }
+
+ private boolean result;
+ private FailedReason reason;
+ private SlotUTData[] slotUTData;
+
+ public FeatureResult(boolean result, SlotUTData[] slotUTData) {
+ this.result = result;
+ this.slotUTData = slotUTData;
+ }
+
+ public boolean getResult() {
+ return result;
+ }
+
+ public SlotUTData[] getSlotUTData() {
+ return slotUTData;
+ }
+
+ public void setReason(FailedReason reason) {
+ this.reason = reason;
+ }
+
+ public FailedReason getReason() {
+ return reason;
+ }
+ }
+}
diff --git a/src/com/android/settings/sim/smartForwarding/MDNHandlerFragment.java b/src/com/android/settings/sim/smartForwarding/MDNHandlerFragment.java
new file mode 100644
index 00000000000..cada66aaa00
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/MDNHandlerFragment.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import static com.android.settings.sim.smartForwarding.MDNHandlerHeaderFragment.KEY_SLOT0_PHONE_NUMBER;
+import static com.android.settings.sim.smartForwarding.MDNHandlerHeaderFragment.KEY_SLOT1_PHONE_NUMBER;
+
+import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.R;
+import com.android.settingslib.core.instrumentation.Instrumentable;
+
+public class MDNHandlerFragment extends Fragment implements Instrumentable {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.xml.smart_forwarding_mdn_handler, container, false);
+ getActivity().getActionBar().setTitle(
+ getResources().getString(R.string.smart_forwarding_input_mdn_title));
+
+ Button processBtn = view.findViewById(R.id.process);
+ processBtn.setOnClickListener((View v)-> {
+ pressButtonOnClick();
+ });
+
+ Button cancelBtn = view.findViewById(R.id.cancel);
+ cancelBtn.setOnClickListener((View v)-> {
+ switchToMainFragment(true);
+ });
+ return view;
+ }
+
+ private void pressButtonOnClick() {
+ // Get the phone number from the UI
+ MDNHandlerHeaderFragment fragment = (MDNHandlerHeaderFragment) this
+ .getChildFragmentManager()
+ .findFragmentById(R.id.fragment_settings);
+
+ String slot0Number = "";
+ String slot1Number = "";
+ if (fragment != null) {
+ slot0Number = fragment.findPreference(KEY_SLOT0_PHONE_NUMBER)
+ .getSummary().toString();
+ slot1Number = fragment.findPreference(KEY_SLOT1_PHONE_NUMBER)
+ .getSummary().toString();
+ }
+ final String[] phoneNumber = {slot1Number, slot0Number};
+
+ // If phone number is empty, popup an alert dialog
+ if(TextUtils.isEmpty(phoneNumber[0])
+ || TextUtils.isEmpty(phoneNumber[1])) {
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.smart_forwarding_failed_title)
+ .setMessage(R.string.smart_forwarding_missing_mdn_text)
+ .setPositiveButton(
+ R.string.smart_forwarding_missing_alert_dialog_text,
+ (dialog, which) -> { dialog.dismiss(); })
+ .create()
+ .show();
+ } else {
+ switchToMainFragment(false);
+ ((SmartForwardingActivity)getActivity()).enableSmartForwarding(phoneNumber);
+ }
+ }
+
+ private void switchToMainFragment(boolean turnoffSwitch) {
+ FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.content_frame, new SmartForwardingFragment(turnoffSwitch))
+ .commit();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.MOBILE_NETWORK;
+ }
+}
diff --git a/src/com/android/settings/sim/smartForwarding/MDNHandlerHeaderFragment.java b/src/com/android/settings/sim/smartForwarding/MDNHandlerHeaderFragment.java
new file mode 100644
index 00000000000..5fd8049d927
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/MDNHandlerHeaderFragment.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.getPhoneNumber;
+
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.text.InputType;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+import androidx.preference.EditTextPreference.OnBindEditTextListener;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.settings.R;
+import com.android.settingslib.core.instrumentation.Instrumentable;
+
+public class MDNHandlerHeaderFragment extends PreferenceFragmentCompat
+ implements Preference.OnPreferenceChangeListener, OnBindEditTextListener, Instrumentable {
+
+ public static final String KEY_SLOT0_PHONE_NUMBER = "slot0_phone_number";
+ public static final String KEY_SLOT1_PHONE_NUMBER = "slot1_phone_number";
+
+ public MDNHandlerHeaderFragment() {}
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.smart_forwarding_mdn_handler_header, rootKey);
+
+ EditTextPreference slot0EditText = findPreference(KEY_SLOT0_PHONE_NUMBER);
+ slot0EditText.setOnBindEditTextListener(this);
+ slot0EditText.setOnPreferenceChangeListener(this);
+ String slot0PhoneNumber = getPhoneNumber(getContext(), 0);
+ slot0EditText.setSummary(slot0PhoneNumber);
+ slot0EditText.setText(slot0PhoneNumber);
+
+ EditTextPreference slot1EditText = findPreference(KEY_SLOT1_PHONE_NUMBER);
+ slot1EditText.setOnPreferenceChangeListener(this);
+ slot1EditText.setOnBindEditTextListener(this);
+ String slot1PhoneNumber = getPhoneNumber(getContext(), 1);
+ slot1EditText.setSummary(slot1PhoneNumber);
+ slot1EditText.setText(slot1PhoneNumber);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ preference.setSummary(newValue.toString());
+ return true;
+ }
+
+ @Override
+ public void onBindEditText(@NonNull EditText editText) {
+ editText.setInputType(InputType.TYPE_CLASS_PHONE);
+ editText.setSingleLine(true);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.MOBILE_NETWORK;
+ }
+}
diff --git a/src/com/android/settings/sim/smartForwarding/SmartForwardingActivity.java b/src/com/android/settings/sim/smartForwarding/SmartForwardingActivity.java
new file mode 100644
index 00000000000..217801e5a17
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/SmartForwardingActivity.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import static com.android.settings.sim.smartForwarding.EnableSmartForwardingTask.FeatureResult;
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.TAG;
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.backupPrevStatus;
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.clearAllBackupData;
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.getAllSlotCallForwardingStatus;
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.getAllSlotCallWaitingStatus;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.os.Bundle;
+import android.telephony.CallForwardingInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toolbar;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.settings.R;
+import com.android.settings.core.SettingsBaseActivity;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.concurrent.Executors;
+
+public class SmartForwardingActivity extends SettingsBaseActivity {
+ final ListeningExecutorService service =
+ MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Toolbar toolbar = findViewById(R.id.action_bar);
+ toolbar.setVisibility(View.VISIBLE);
+ setActionBar(toolbar);
+
+ final ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.content_frame, new SmartForwardingFragment())
+ .commit();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ public void enableSmartForwarding(String[] phoneNumber) {
+ // Pop-up ongoing dialog
+ ProgressDialog dialog = new ProgressDialog(this);
+ dialog.setTitle(R.string.smart_forwarding_ongoing_title);
+ dialog.setIndeterminate(true);
+ dialog.setMessage(getText(R.string.smart_forwarding_ongoing_text));
+ dialog.setCancelable(false);
+ dialog.show();
+
+ // Enable feature
+ ListenableFuture enableTask =
+ service.submit(new EnableSmartForwardingTask(this, phoneNumber));
+ Futures.addCallback(enableTask, new FutureCallback() {
+ @Override
+ public void onSuccess(FeatureResult result) {
+ Log.e(TAG, "Enable Feature result: " + result.getResult());
+ if (result.getResult()) {
+ backupPrevStatus(SmartForwardingActivity.this, result.getSlotUTData());
+
+ // Turn on switch preference
+ SmartForwardingFragment fragment =
+ (SmartForwardingFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.content_frame);
+ if (fragment != null) {
+ fragment.turnOnSwitchPreference();
+ }
+ } else {
+ onError(result);
+ }
+ dialog.dismiss();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Enable Feature exception", t);
+ dialog.dismiss();
+
+ // Pop-up error dialog
+ AlertDialog mDialog = new AlertDialog.Builder(SmartForwardingActivity.this)
+ .setTitle(R.string.smart_forwarding_failed_title)
+ .setMessage(R.string.smart_forwarding_failed_text)
+ .setPositiveButton(
+ R.string.smart_forwarding_missing_alert_dialog_text,
+ (dialog, which) -> { dialog.dismiss(); })
+ .create();
+ mDialog.show();
+ }
+ }, ContextCompat.getMainExecutor(this));
+ }
+
+ public void disableSmartForwarding() {
+ TelephonyManager tm = getSystemService(TelephonyManager.class);
+ SubscriptionManager sm = getSystemService(SubscriptionManager.class);
+
+ boolean[] callWaitingStatus = getAllSlotCallWaitingStatus(this, sm, tm);
+ CallForwardingInfo[] callForwardingInfo = getAllSlotCallForwardingStatus(this, sm, tm);
+
+ // Disable feature
+ ListenableFuture disableTask = service.submit(new DisableSmartForwardingTask(
+ tm, callWaitingStatus, callForwardingInfo));
+ Futures.addCallback(disableTask, new FutureCallback() {
+ @Override
+ public void onSuccess(Object result) {
+ clearAllBackupData(SmartForwardingActivity.this, sm, tm);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Disable Feature exception" + t);
+ }
+ }, ContextCompat.getMainExecutor(this));
+ }
+
+ public void onError(FeatureResult result) {
+ int errorMsg;
+ if (result.getReason() == FeatureResult.FailedReason.SIM_NOT_ACTIVE) {
+ errorMsg = R.string.smart_forwarding_failed_not_activated_text;
+ } else {
+ errorMsg = R.string.smart_forwarding_failed_text;
+ }
+
+ // Pop-up error dialog
+ AlertDialog mDialog = new AlertDialog.Builder(SmartForwardingActivity.this)
+ .setTitle(R.string.smart_forwarding_failed_title)
+ .setMessage(errorMsg)
+ .setPositiveButton(
+ R.string.smart_forwarding_missing_alert_dialog_text,
+ (dialog, which) -> { dialog.dismiss(); })
+ .create();
+ mDialog.show();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java b/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java
new file mode 100644
index 00000000000..76eaea111ba
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/SmartForwardingFragment.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.TAG;
+import static com.android.settings.sim.smartForwarding.SmartForwardingUtils.getPhoneNumber;
+
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.fragment.app.FragmentManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settingslib.core.instrumentation.Instrumentable;
+
+public class SmartForwardingFragment extends PreferenceFragmentCompat
+ implements Preference.OnPreferenceChangeListener, Instrumentable {
+
+ private static final String KEY_SMART_FORWARDING_SWITCH = "smart_forwarding_switch";
+ private boolean turnOffSwitch;
+
+ public SmartForwardingFragment() { }
+
+ public SmartForwardingFragment(boolean turnOffSwitch) {
+ this.turnOffSwitch = turnOffSwitch;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.smart_forwarding_switch, rootKey);
+
+ String title = getResources().getString(R.string.smart_forwarding_title);
+ getActivity().getActionBar().setTitle(title);
+
+ SwitchPreference smartForwardingSwitch = findPreference(KEY_SMART_FORWARDING_SWITCH);
+ if (turnOffSwitch) {
+ smartForwardingSwitch.setChecked(false);
+ }
+ smartForwardingSwitch.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ boolean value = (boolean) newValue;
+
+ Log.d(TAG, "onPreferenceChange. Update value to " + value);
+
+ if (value) {
+ String slot0PhoneNumber = getPhoneNumber(getContext(), 0);
+ String slot1PhoneNumber = getPhoneNumber(getContext(), 1);
+
+ String[] phoneNumber = new String[]{slot1PhoneNumber, slot0PhoneNumber};
+
+ if (TextUtils.isEmpty(slot0PhoneNumber) || TextUtils.isEmpty(slot1PhoneNumber)) {
+ Log.d(TAG, "Slot 0 or Slot 1 phone number missing.");
+ switchToMDNFragment();
+ } else {
+ ((SmartForwardingActivity) getActivity()).enableSmartForwarding(phoneNumber);
+ }
+ return false;
+ } else {
+ ((SmartForwardingActivity) getActivity()).disableSmartForwarding();
+ }
+
+ return true;
+ }
+
+
+ private void switchToMDNFragment() {
+ FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.content_frame, new MDNHandlerFragment())
+ .commit();
+ }
+
+ public void turnOnSwitchPreference() {
+ SwitchPreference smartForwardingSwitch = findPreference(KEY_SMART_FORWARDING_SWITCH);
+ smartForwardingSwitch.setChecked(true);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.MOBILE_NETWORK;
+ }
+}
diff --git a/src/com/android/settings/sim/smartForwarding/SmartForwardingUtils.java b/src/com/android/settings/sim/smartForwarding/SmartForwardingUtils.java
new file mode 100644
index 00000000000..5a82d8ba9ce
--- /dev/null
+++ b/src/com/android/settings/sim/smartForwarding/SmartForwardingUtils.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.sim.smartForwarding;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.telephony.CallForwardingInfo;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+public class SmartForwardingUtils {
+ public static final String TAG = "SmartForwarding";
+ public static final String SMART_FORWARDING_PREF = "smart_forwarding_pref_";
+
+ public static final String CALL_WAITING_KEY = "call_waiting_key";
+ public static final String CALL_FORWARDING_ENABLED_KEY = "call_forwarding_enabled_key";
+ public static final String CALL_FORWARDING_REASON_KEY = "call_forwarding_reason_key";
+ public static final String CALL_FORWARDING_NUMBER_KEY = "call_forwarding_number_key";
+ public static final String CALL_FORWARDING_TIME_KEY = "call_forwarding_timekey";
+
+ public static boolean getBackupCallWaitingStatus(Context context, int subId) {
+ SharedPreferences preferences = context.getSharedPreferences(
+ SMART_FORWARDING_PREF + subId, Context.MODE_PRIVATE);
+ return preferences.getBoolean(CALL_WAITING_KEY, false);
+ }
+
+ public static CallForwardingInfo getBackupCallForwardingStatus(Context context, int subId) {
+ SharedPreferences preferences = context.getSharedPreferences(
+ SMART_FORWARDING_PREF + subId, Context.MODE_PRIVATE);
+ if (preferences.contains(CALL_FORWARDING_ENABLED_KEY)) {
+ boolean enabled = preferences.getBoolean(CALL_FORWARDING_ENABLED_KEY, false);
+ int reason = preferences.getInt(CALL_FORWARDING_REASON_KEY,
+ CallForwardingInfo.REASON_UNCONDITIONAL);
+ String number = preferences.getString(CALL_FORWARDING_NUMBER_KEY, "");
+ int time = preferences.getInt(CALL_FORWARDING_TIME_KEY, 1);
+
+ return new CallForwardingInfo(enabled, reason, number, time);
+ } else {
+ return null;
+ }
+ }
+
+ public static void saveCallWaitingStatus(Context context, int subId, boolean value) {
+ SharedPreferences.Editor preferences = context.getSharedPreferences(
+ SMART_FORWARDING_PREF + subId, Context.MODE_PRIVATE).edit();
+ preferences.putBoolean(CALL_WAITING_KEY, value).commit();
+ }
+
+ public static void saveCallForwardingStatus(Context context, int subId,
+ CallForwardingInfo callForwardingInfo) {
+ SharedPreferences.Editor preferences = context.getSharedPreferences(
+ SMART_FORWARDING_PREF + subId, Context.MODE_PRIVATE).edit();
+
+ preferences.putBoolean(CALL_FORWARDING_ENABLED_KEY, callForwardingInfo.isEnabled())
+ .commit();
+ preferences.putInt(CALL_FORWARDING_REASON_KEY, callForwardingInfo.getReason()).commit();
+ preferences.putString(CALL_FORWARDING_NUMBER_KEY, callForwardingInfo.getNumber()).commit();
+ preferences.putInt(CALL_FORWARDING_TIME_KEY, callForwardingInfo.getTimeoutSeconds())
+ .commit();
+ }
+
+ public static void clearBackupData(Context context, int subId) {
+ SharedPreferences.Editor preferences = context.getSharedPreferences(
+ SMART_FORWARDING_PREF + subId, Context.MODE_PRIVATE).edit();
+ preferences.clear().commit();
+ }
+
+ public static boolean[] getAllSlotCallWaitingStatus(Context context, SubscriptionManager sm,
+ TelephonyManager tm) {
+ int phoneCount = tm.getActiveModemCount();
+ boolean[] allStatus = new boolean[phoneCount];
+
+ for (int i = 0; i < phoneCount; i++) {
+ int subId = sm.getSubscriptionIds(i)[0];
+ boolean callWaitingStatus = getBackupCallWaitingStatus(context, subId);
+ allStatus[i] = callWaitingStatus;
+ }
+ return allStatus;
+ }
+
+ public static CallForwardingInfo[] getAllSlotCallForwardingStatus(
+ Context context, SubscriptionManager sm, TelephonyManager tm) {
+ int phoneCount = tm.getActiveModemCount();
+ CallForwardingInfo[] allStatus = new CallForwardingInfo[phoneCount];
+
+ for (int i = 0; i < phoneCount; i++) {
+ int subId = sm.getSubscriptionIds(i)[0];
+ CallForwardingInfo callWaitingStatus = getBackupCallForwardingStatus(context, subId);
+ allStatus[i] = callWaitingStatus;
+ }
+ return allStatus;
+ }
+
+ public static void clearAllBackupData(Context context, SubscriptionManager sm,
+ TelephonyManager tm) {
+ int phoneCount = tm.getActiveModemCount();
+ for (int i = 0; i < phoneCount; i++) {
+ int subId = sm.getSubscriptionIds(i)[0];
+ clearBackupData(context, subId);
+ }
+ }
+
+ public static void backupPrevStatus(Context context,
+ EnableSmartForwardingTask.SlotUTData[] slotUTData) {
+ for (int i = 0; i < slotUTData.length; i++) {
+ int callWaiting = slotUTData[i].mQueryCallWaiting.result;
+ saveCallWaitingStatus(
+ context,
+ slotUTData[i].subId,
+ callWaiting == TelephonyManager.CALL_WAITING_STATUS_ENABLED);
+
+ saveCallForwardingStatus(
+ context,
+ slotUTData[i].subId,
+ slotUTData[i].mQueryCallForwarding.result);
+ }
+ }
+
+ public static String getPhoneNumber(Context context, int slotId) {
+ SubscriptionManager subscriptionManager = context.getSystemService(
+ SubscriptionManager.class);
+ int[] subIdList = subscriptionManager.getSubscriptionIds(slotId);
+ if (subIdList != null) {
+ SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(subIdList[0]);
+ return (subInfo != null) ? subInfo.getNumber() : "";
+ } else {
+ return "";
+ }
+ }
+}
\ No newline at end of file