Merge changes Icee8c85e,I98be6f30,I343b705b

* changes:
  [Smart forwarding] Enable/Disable Smart Forwarding flow
  [Smart forwarding] Add UI for Smart Forwarding
  [Smart forwarding] Add string and layout resource
This commit is contained in:
Calvin Pan
2021-01-25 07:29:45 +00:00
committed by Android (Google) Code Review
12 changed files with 1197 additions and 0 deletions

View File

@@ -3672,6 +3672,12 @@
<service android:name=".sim.SimNotificationService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<activity android:name=".sim.smartForwarding.SmartForwardingActivity"
android:theme="@style/Theme.SubSettings.Base"
android:exported="true"
android:launchMode="singleTask">
</activity>
<!-- This is the longest AndroidManifest.xml ever. -->
</application>
</manifest>

View File

@@ -12627,4 +12627,29 @@
<!-- Default preference title for showing all apps on device [CHAR_LIMIT=50]-->
<string name="default_see_all_apps_title">See all apps</string>
<!-- Title for smart forwarding [CHAR LIMIT=50]-->
<string name="smart_forwarding_title">Smart Forwarding</string>
<!-- Summary for smart forwarding enabled [CHAR LIMIT=50]-->
<string name="smart_forwarding_summary_enabled">Smart Forwarding Enabled</string>
<!-- Summary for smart forwarding disabled [CHAR LIMIT=50]-->
<string name="smart_forwarding_summary_disabled">Smart Forwarding Disabled</string>
<!-- Dialog title for smart forwarding ongoing [CHAR LIMIT=50]-->
<string name="smart_forwarding_ongoing_title">Call Settings</string>
<!-- Subtext for smart forwarding ongoing [CHAR LIMIT=50]-->
<string name="smart_forwarding_ongoing_text">Updating Settings...</string>
<!-- Dialog title for smart forwarding failed [CHAR LIMIT=50]-->
<string name="smart_forwarding_failed_title">Call Settings error</string>
<!-- Subtext for smart forwarding failed [CHAR LIMIT=50]-->
<string name="smart_forwarding_failed_text">Network or SIM card error.</string>
<!-- Subtext for sim is not activated [CHAR LIMIT=50]-->
<string name="smart_forwarding_failed_not_activated_text">Sim is not activated.</string>
<!-- Title when smart forwarding can't get the phone number [CHAR LIMIT=50]-->
<string name="smart_forwarding_input_mdn_title">Enter Phone numbers</string>
<!-- Dialog title when user update the phone number [CHAR LIMIT=50]-->
<string name="smart_forwarding_input_mdn_dialog_title">Enter Phone number</string>
<!-- Alert Dialog text when user didn't input the phone number [CHAR LIMIT=50]-->
<string name="smart_forwarding_missing_mdn_text">Phone number is missing.</string>
<!-- Alert Dialog text when user didn't input the phone number [CHAR LIMIT=50]-->
<string name="smart_forwarding_missing_alert_dialog_text">OK</string>
</resources>

View File

@@ -0,0 +1,27 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:id="@+id/fragment_settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/cancel"
android:name="com.android.settings.sim.smartForwarding.MDNHandlerHeaderFragment"
android:layout_marginBottom="15dp" />
<Button android:id="@+id/process"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="5dip"
android:text="process" />
<Button android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="5dip"
android:text="cancel" />
</RelativeLayout>

View File

@@ -0,0 +1,18 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="network_and_internet_screen"
android:title="@string/smart_forwarding_input_mdn_title">
<EditTextPreference
android:key="slot0_phone_number"
android:title="SIM 1"
android:dialogTitle="@string/smart_forwarding_input_mdn_dialog_title"
android:persistent="false" />
<EditTextPreference
android:key="slot1_phone_number"
android:title="SIM 2"
android:dialogTitle="@string/smart_forwarding_input_mdn_dialog_title"
android:persistent="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,14 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="smart_forwarding_preference"
android:title="@string/smart_forwarding_title">
<SwitchPreference
android:key="smart_forwarding_switch"
android:title="Smart forwarding"
android:summaryOff="@string/smart_forwarding_summary_disabled"
android:summaryOn="@string/smart_forwarding_summary_enabled"
android:disableDependentsState="true" />
</PreferenceScreen>

View File

@@ -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);
}
}
}
}

View File

@@ -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<EnableSmartForwardingTask.FeatureResult> {
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<FeatureResult> 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<Command> 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<Command> 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<T> 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<T> 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<Integer> {
int result;
SettableFuture<Boolean> 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> {
CallForwardingInfo result;
SettableFuture<Boolean> 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<Integer> {
SettableFuture<Boolean> 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<Integer> {
String phoneNum;
SettableFuture<Boolean> 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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<FeatureResult> enableTask =
service.submit(new EnableSmartForwardingTask(this, phoneNumber));
Futures.addCallback(enableTask, new FutureCallback<FeatureResult>() {
@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();
}
}

View File

@@ -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;
}
}

View File

@@ -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 "";
}
}
}