Merge "Remove internal dependency for PhoneGlobals/Phone"
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index bfeee7a..bdffb26 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -76,6 +76,11 @@
mReplaceInvalidCFNumber = replaceInvalidCFNumber;
}
+ void restoreCallForwardInfo(CallForwardInfo cf) {
+ handleCallForwardResult(cf);
+ updateSummaryText();
+ }
+
@Override
protected void onBindDialogView(View view) {
// default the button clicked to be the cancel button.
@@ -100,7 +105,15 @@
int action = (isToggled() || (mButtonClicked == DialogInterface.BUTTON_POSITIVE)) ?
CommandsInterface.CF_ACTION_REGISTRATION :
CommandsInterface.CF_ACTION_DISABLE;
- int time = (reason != CommandsInterface.CF_REASON_NO_REPLY) ? 0 : 20;
+ int time = 0;
+ if (reason == CommandsInterface.CF_REASON_NO_REPLY) {
+ PersistableBundle carrierConfig = PhoneGlobals.getInstance()
+ .getCarrierConfigForSubId(mPhone.getSubId());
+ if (carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true)) {
+ time = 20;
+ }
+ }
final String number = getPhoneNumber();
Log.d(LOG_TAG, "callForwardInfo=" + callForwardInfo);
@@ -137,7 +150,7 @@
}
}
- void handleCallForwardResult(CallForwardInfo cf) {
+ private void handleCallForwardResult(CallForwardInfo cf) {
callForwardInfo = cf;
Log.d(LOG_TAG, "handleGetCFResponse done, callForwardInfo=" + callForwardInfo);
// In some cases, the network can send call forwarding URIs for voicemail that violate the
diff --git a/src/com/android/phone/GsmUmtsCallForwardOptions.java b/src/com/android/phone/GsmUmtsCallForwardOptions.java
index e562e46..6d80621 100644
--- a/src/com/android/phone/GsmUmtsCallForwardOptions.java
+++ b/src/com/android/phone/GsmUmtsCallForwardOptions.java
@@ -31,6 +31,7 @@
private static final String KEY_TOGGLE = "toggle";
private static final String KEY_STATUS = "status";
private static final String KEY_NUMBER = "number";
+ private static final String KEY_ENABLE = "enable";
private CallForwardEditPreference mButtonCFU;
private CallForwardEditPreference mButtonCFB;
@@ -112,11 +113,12 @@
for (CallForwardEditPreference pref : mPreferences) {
Bundle bundle = mIcicle.getParcelable(pref.getKey());
pref.setToggled(bundle.getBoolean(KEY_TOGGLE));
+ pref.setEnabled(bundle.getBoolean(KEY_ENABLE));
CallForwardInfo cf = new CallForwardInfo();
cf.number = bundle.getString(KEY_NUMBER);
cf.status = bundle.getInt(KEY_STATUS);
pref.init(this, mPhone, mReplaceInvalidCFNumbers);
- pref.handleCallForwardResult(cf);
+ pref.restoreCallForwardInfo(cf);
}
}
mFirstResume = false;
@@ -131,6 +133,7 @@
for (CallForwardEditPreference pref : mPreferences) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_TOGGLE, pref.isToggled());
+ bundle.putBoolean(KEY_ENABLE, pref.isEnabled());
if (pref.callForwardInfo != null) {
bundle.putString(KEY_NUMBER, pref.callForwardInfo.number);
bundle.putInt(KEY_STATUS, pref.callForwardInfo.status);
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index b1e061f..ba36f1b 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -62,6 +62,7 @@
import com.android.internal.telephony.dataconnection.DataConnectionReasons;
import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.anas.AlternativeNetworkAccessService;
import com.android.phone.common.CallLogAsync;
import com.android.phone.settings.SettingsConstants;
import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
@@ -333,6 +334,7 @@
phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());
configLoader = CarrierConfigLoader.init(this);
+ AlternativeNetworkAccessService.initInstance(this);
// Create the CallNotifier singleton, which handles
// asynchronous events from the telephony layer (like
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index fd23fd2..abfa8d7 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -112,6 +112,8 @@
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.SmsApplication;
+import com.android.internal.telephony.SmsApplication.SmsApplicationData;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.euicc.EuiccConnector;
@@ -135,6 +137,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -3285,24 +3288,30 @@
}
}
- /**
- * Set the network selection mode to manual with the selected carrier.
+ /**
+ * Ask the radio to connect to the input network and change selection mode to manual.
+ *
+ * @param subId the id of the subscription.
+ * @param operatorInfo the operator information, included the PLMN, long name and short name of
+ * the operator to attach to.
+ * @param persistSelection whether the selection will persist until reboot. If true, only allows
+ * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
+ * normal network selection next time.
+ * @return {@code true} on success; {@code true} on any failure.
*/
@Override
- public boolean setNetworkSelectionModeManual(int subId, String operatorNumeric,
- boolean persistSelection) {
+ public boolean setNetworkSelectionModeManual(
+ int subId, OperatorInfo operatorInfo, boolean persistSelection) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setNetworkSelectionModeManual");
-
final long identity = Binder.clearCallingIdentity();
try {
- OperatorInfo operator = new OperatorInfo(
- /* operatorAlphaLong */ "",
- /* operatorAlphaShort */ "",
- operatorNumeric);
- if (DBG) log("setNetworkSelectionModeManual: subId:" + subId + " operator:" + operator);
- ManualNetworkSelectionArgument arg = new ManualNetworkSelectionArgument(operator,
+ ManualNetworkSelectionArgument arg = new ManualNetworkSelectionArgument(operatorInfo,
persistSelection);
+ if (DBG) {
+ log("setNetworkSelectionModeManual: subId: " + subId
+ + " operator: " + operatorInfo);
+ }
return (Boolean) sendRequest(CMD_SET_NETWORK_SELECTION_MODE_MANUAL, arg, subId);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -5305,4 +5314,73 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ private void ensureUserRunning(int userId) {
+ if (!mUserManager.isUserRunning(userId)) {
+ throw new IllegalStateException("User " + userId + " does not exist or not running");
+ }
+ }
+
+ /**
+ * Returns a list of SMS apps on a given user.
+ *
+ * Only the shell user (UID 2000 or 0) can call it.
+ * Target user must be running.
+ */
+ @Override
+ public String[] getSmsApps(int userId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getSmsApps");
+ ensureUserRunning(userId);
+
+ final Collection<SmsApplicationData> apps =
+ SmsApplication.getApplicationCollectionAsUser(mApp, userId);
+
+ String[] ret = new String[apps.size()];
+ int i = 0;
+ for (SmsApplicationData app : apps) {
+ ret[i++] = app.mPackageName;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns the default SMS app package name on a given user.
+ *
+ * Only the shell user (UID 2000 or 0) can call it.
+ * Target user must be running.
+ */
+ @Override
+ public String getDefaultSmsApp(int userId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getDefaultSmsApp");
+ ensureUserRunning(userId);
+
+ final ComponentName cn = SmsApplication.getDefaultSmsApplicationAsUser(mApp,
+ /* updateIfNeeded= */ true, userId);
+ return cn == null ? null : cn.getPackageName();
+ }
+
+ /**
+ * Set a package as the default SMS app on a given user.
+ *
+ * Only the shell user (UID 2000 or 0) can call it.
+ * Target user must be running.
+ */
+ @Override
+ public void setDefaultSmsApp(int userId, String packageName) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "setDefaultSmsApp");
+ ensureUserRunning(userId);
+
+ boolean found = false;
+ for (String pkg : getSmsApps(userId)) {
+ if (TextUtils.equals(packageName, pkg)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException("Package " + packageName + " is not an SMS app");
+ }
+
+ SmsApplication.setDefaultApplicationAsUser(packageName, mApp, userId);
+ }
}
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 4acb46b..1a25ae3 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -18,6 +18,7 @@
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.util.Log;
@@ -39,11 +40,16 @@
private static final int DEFAULT_PHONE_ID = 0;
private static final String IMS_SUBCOMMAND = "ims";
+ private static final String SMS_SUBCOMMAND = "sms";
private static final String IMS_SET_CARRIER_SERVICE = "set-ims-service";
private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service";
private static final String IMS_ENABLE = "enable";
private static final String IMS_DISABLE = "disable";
+ private static final String SMS_GET_APPS = "get-apps";
+ private static final String SMS_GET_DEFAULT_APP = "get-default-app";
+ private static final String SMS_SET_DEFAULT_APP = "set-default-app";
+
// Take advantage of existing methods that already contain permissions checks when possible.
private final ITelephony mInterface;
@@ -61,6 +67,9 @@
case IMS_SUBCOMMAND: {
return handleImsCommand();
}
+ case SMS_SUBCOMMAND: {
+ return handleSmsCommand();
+ }
default: {
return handleDefaultCommands(cmd);
}
@@ -75,7 +84,10 @@
pw.println(" Print this help text.");
pw.println(" ims");
pw.println(" IMS Commands.");
+ pw.println(" sms");
+ pw.println(" SMS Commands.");
onHelpIms();
+ onHelpSms();
}
private void onHelpIms() {
@@ -103,6 +115,17 @@
pw.println(" slot if none is specified.");
}
+ private void onHelpSms() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("SMS Commands:");
+ pw.println(" sms get-apps [--user USER_ID]");
+ pw.println(" Print all SMS apps on a user.");
+ pw.println(" sms get-default-app [--user USER_ID]");
+ pw.println(" Get the default SMS app.");
+ pw.println(" sms set-default-app [--user USER_ID] PACKAGE_NAME");
+ pw.println(" Set PACKAGE_NAME as the default SMS app.");
+ }
+
private int handleImsCommand() {
String arg = getNextArg();
if (arg == null) {
@@ -296,4 +319,85 @@
}
return slotId;
}
+
+ private int handleSmsCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpSms();
+ return 0;
+ }
+
+ try {
+ switch (arg) {
+ case SMS_GET_APPS: {
+ return handleSmsGetApps();
+ }
+ case SMS_GET_DEFAULT_APP: {
+ return handleSmsGetDefaultApp();
+ }
+ case SMS_SET_DEFAULT_APP: {
+ return handleSmsSetDefaultApp();
+ }
+ default:
+ getErrPrintWriter().println("Unknown command " + arg);
+ }
+ } catch (RemoteException e) {
+ getErrPrintWriter().println("RemoteException: " + e.getMessage());
+ }
+
+ return -1;
+ }
+
+ private int maybeParseUserIdArg() {
+ int userId = UserHandle.USER_SYSTEM;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user": {
+ try {
+ userId = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Invalid user ID for --user");
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+ return userId;
+ }
+
+ private int handleSmsGetApps() throws RemoteException {
+ final int userId = maybeParseUserIdArg();
+ if (userId < 0) {
+ return -1;
+ }
+
+ for (String packageName : mInterface.getSmsApps(userId)) {
+ getOutPrintWriter().println(packageName);
+ }
+ return 0;
+ }
+
+ private int handleSmsGetDefaultApp() throws RemoteException {
+ final int userId = maybeParseUserIdArg();
+ if (userId < 0) {
+ return -1;
+ }
+
+ getOutPrintWriter().println(mInterface.getDefaultSmsApp(userId));
+ return 0;
+ }
+
+ private int handleSmsSetDefaultApp() throws RemoteException {
+ final int userId = maybeParseUserIdArg();
+ if (userId < 0) {
+ return -1;
+ }
+
+ String packageName = getNextArgRequired();
+ mInterface.setDefaultSmsApp(userId, packageName);
+ getOutPrintWriter().println("SMS app set to " + mInterface.getDefaultSmsApp(userId));
+ return 0;
+ }
}
diff --git a/src/com/android/phone/anas/ANASNetworkScanCtlr.java b/src/com/android/phone/anas/ANASNetworkScanCtlr.java
new file mode 100644
index 0000000..6c75706
--- /dev/null
+++ b/src/com/android/phone/anas/ANASNetworkScanCtlr.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2018 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.phone.anas;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoLte;
+import android.telephony.NetworkScan;
+import android.telephony.NetworkScanRequest;
+import android.telephony.RadioAccessSpecifier;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyScanManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Network Scan controller class which will scan for the specific bands as requested and
+ * provide results to caller when ready.
+ */
+public class ANASNetworkScanCtlr {
+ private static final String LOG_TAG = "ANASNetworkScanCtlr";
+ private static final boolean DBG = true;
+ private static final int SEARCH_PERIODICITY_SLOW = (int) TimeUnit.MINUTES.toSeconds(5);
+ private static final int SEARCH_PERIODICITY_FAST = (int) TimeUnit.MINUTES.toSeconds(1);
+ private static final int MAX_SEARCH_TIME = (int) TimeUnit.MINUTES.toSeconds(1);
+ private final Object mLock = new Object();
+
+ /* message to handle scan responses from modem */
+ private static final int MSG_SCAN_RESULTS_AVAILABLE = 1;
+ private static final int MSG_SCAN_COMPLETE = 2;
+ private static final int MSG_SCAN_ERROR = 3;
+
+ /* scan object to keep track of current scan request */
+ private NetworkScan mCurrentScan;
+ private boolean mIsScanActive;
+ private NetworkScanRequest mCurrentScanRequest;
+ private List<String> mMccMncs;
+ private TelephonyManager mTelephonyManager;
+ @VisibleForTesting
+ protected NetworkAvailableCallBack mNetworkAvailableCallBack;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SCAN_RESULTS_AVAILABLE:
+ logDebug("Msg received for scan results");
+ /* Todo: need to aggregate the results */
+ analyzeScanResults((List<CellInfo>) msg.obj);
+ break;
+ case MSG_SCAN_COMPLETE:
+ logDebug("Msg received for scan complete");
+ restartScan();
+ break;
+ case MSG_SCAN_ERROR:
+ logDebug("Msg received for scan error");
+ invalidateScanOnError((int) msg.obj);
+ break;
+ default:
+ log("invalid message");
+ break;
+ }
+ }
+ };
+
+ @VisibleForTesting
+ public TelephonyScanManager.NetworkScanCallback mNetworkScanCallback =
+ new TelephonyScanManager.NetworkScanCallback() {
+
+ @Override
+ public void onResults(List<CellInfo> results) {
+ logDebug("Total results :" + results.size());
+ for (CellInfo cellInfo : results) {
+ logDebug("cell info: " + cellInfo);
+ }
+
+ Message message = Message.obtain(mHandler, MSG_SCAN_RESULTS_AVAILABLE, results);
+ message.sendToTarget();
+ }
+
+ @Override
+ public void onComplete() {
+ logDebug("Scan completed!");
+ Message message = Message.obtain(mHandler, MSG_SCAN_COMPLETE, NetworkScan.SUCCESS);
+ message.sendToTarget();
+ }
+
+ @Override
+ public void onError(@NetworkScan.ScanErrorCode int error) {
+ logDebug("Scan error " + error);
+ Message message = Message.obtain(mHandler, MSG_SCAN_ERROR, error);
+ message.sendToTarget();
+ }
+ };
+
+ /**
+ * call back for network availability
+ */
+ public interface NetworkAvailableCallBack {
+
+ /**
+ * Returns the scan results to the user, this callback will be called multiple times.
+ */
+ void onNetworkAvailability(List<CellInfo> results);
+
+ /**
+ * on error
+ * @param error
+ */
+ void onError(int error);
+ }
+
+
+ /**
+ * analyze scan results
+ * @param results contains all available cells matching the scan request at current location.
+ */
+ public void analyzeScanResults(List<CellInfo> results) {
+ /* Inform registrants about availability of network */
+ if (results != null) {
+ List<CellInfo> filteredResults = new ArrayList<CellInfo>();
+ synchronized (mLock) {
+ for (CellInfo cellInfo : results) {
+ if (mMccMncs.contains(getMccMnc(cellInfo))) {
+ filteredResults.add(cellInfo);
+ }
+ }
+ }
+
+ if ((filteredResults.size() >= 1) && (mNetworkAvailableCallBack != null)) {
+ /* Todo: change to aggregate results on success. */
+ mNetworkAvailableCallBack.onNetworkAvailability(filteredResults);
+ }
+ }
+ }
+
+ private void invalidateScanOnError(int error) {
+ logDebug("scan invalidated on error");
+ if (mNetworkAvailableCallBack != null) {
+ mNetworkAvailableCallBack.onError(error);
+ }
+
+ synchronized (mLock) {
+ mIsScanActive = false;
+ mCurrentScan = null;
+ }
+ }
+
+ public ANASNetworkScanCtlr(Context c, TelephonyManager telephonyManager,
+ NetworkAvailableCallBack networkAvailableCallBack) {
+ init(c, telephonyManager, networkAvailableCallBack);
+ }
+
+ /**
+ * initialize Network Scan controller
+ * @param c context
+ * @param telephonyManager Telephony manager instance
+ * @param networkAvailableCallBack callback to be called when network selection is done
+ */
+ public void init(Context c, TelephonyManager telephonyManager,
+ NetworkAvailableCallBack networkAvailableCallBack) {
+ log("init called");
+ mTelephonyManager = telephonyManager;
+ mNetworkAvailableCallBack = networkAvailableCallBack;
+ }
+
+ /* get mcc mnc from cell info if the cell is for LTE */
+ private String getMccMnc(CellInfo cellInfo) {
+ if (cellInfo instanceof CellInfoLte) {
+ return ((CellInfoLte) cellInfo).getCellIdentity().getMccString()
+ + ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
+ }
+
+ return null;
+ }
+
+ private NetworkScanRequest createNetworkScanRequest(List<SubscriptionInfo> subscriptionInfos,
+ int periodicity) {
+ RadioAccessSpecifier[] ras = new RadioAccessSpecifier[1];
+ int[] bands = new int[1];
+
+ /* hardcoding band for now, Todo b/113753823 */
+ bands[0] = AccessNetworkConstants.EutranBand.BAND_48;
+
+ ArrayList<String> mccMncs = new ArrayList<String>();
+ /* retrieve mcc mncs for a subscription id */
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
+ mccMncs.add(subscriptionInfo.getMccString() + subscriptionInfo.getMncString());
+ }
+
+ /* create network scan request */
+ ras[0] = new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, bands,
+ null);
+ NetworkScanRequest networkScanRequest = new NetworkScanRequest(
+ NetworkScanRequest.SCAN_TYPE_PERIODIC, ras, periodicity, MAX_SEARCH_TIME, false,
+ NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC, mccMncs);
+ synchronized (mLock) {
+ mMccMncs = mccMncs;
+ }
+ return networkScanRequest;
+ }
+
+ /**
+ * start high interval network scan
+ * @param subscriptionInfos list of subscriptions for which the scanning needs to be started.
+ * @return true if successfully accepted request.
+ */
+ public boolean startSlowNetworkScan(List<SubscriptionInfo> subscriptionInfos) {
+ NetworkScanRequest networkScanRequest = createNetworkScanRequest(subscriptionInfos,
+ SEARCH_PERIODICITY_SLOW);
+ return startNetworkScan(networkScanRequest);
+ }
+
+ /**
+ * start less interval network scan
+ * @param subscriptionInfos list of subscriptions for which the scanning needs to be started.
+ * @return true if successfully accepted request.
+ */
+ public boolean startFastNetworkScan(List<SubscriptionInfo> subscriptionInfos) {
+ NetworkScanRequest networkScanRequest = createNetworkScanRequest(subscriptionInfos,
+ SEARCH_PERIODICITY_FAST);
+ return startNetworkScan(networkScanRequest);
+ }
+
+
+ private boolean startNetworkScan(NetworkScanRequest networkScanRequest) {
+ NetworkScan networkScan;
+ synchronized (mLock) {
+ /* if the request is same as existing one, then make sure to not proceed */
+ if (mIsScanActive && mCurrentScanRequest.equals(networkScanRequest)) {
+ return true;
+ }
+
+ /* Need to stop current scan if we already have one */
+ stopNetworkScan();
+
+ /* start new scan */
+ networkScan = mTelephonyManager.requestNetworkScan(networkScanRequest,
+ mNetworkScanCallback);
+
+ mCurrentScan = networkScan;
+ mIsScanActive = true;
+ mCurrentScanRequest = networkScanRequest;
+ }
+
+ logDebug("startNetworkScan " + networkScanRequest);
+ return true;
+ }
+
+ private void restartScan() {
+ NetworkScan networkScan;
+ synchronized (mLock) {
+ if (mCurrentScanRequest != null) {
+ networkScan = mTelephonyManager.requestNetworkScan(mCurrentScanRequest,
+ mNetworkScanCallback);
+ mIsScanActive = true;
+ }
+ }
+ }
+
+ /**
+ * stop network scan
+ */
+ public void stopNetworkScan() {
+ logDebug("stopNetworkScan");
+ synchronized (mLock) {
+ if (mIsScanActive && mCurrentScan != null) {
+ try {
+ mCurrentScan.stopScan();
+ } catch (IllegalArgumentException iae) {
+ logDebug("Scan failed with exception " + iae);
+ }
+ mIsScanActive = false;
+ mCurrentScan = null;
+ mCurrentScanRequest = null;
+ }
+ }
+ }
+
+ private static void log(String msg) {
+ Rlog.d(LOG_TAG, msg);
+ }
+
+ private static void logDebug(String msg) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, msg);
+ }
+ }
+}
diff --git a/src/com/android/phone/anas/ANASProfileSelector.java b/src/com/android/phone/anas/ANASProfileSelector.java
new file mode 100644
index 0000000..d347cba
--- /dev/null
+++ b/src/com/android/phone/anas/ANASProfileSelector.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2018 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.phone.anas;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.telephony.Rlog;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Profile selector class which will select the right profile based upon
+ * geographic information input and network scan results.
+ */
+public class ANASProfileSelector {
+ private static final String LOG_TAG = "ANASProfileSelector";
+ private static final boolean DBG = true;
+ private final Object mLock = new Object();
+
+ private static final int INVALID_SEQUENCE_ID = -1;
+ private static final int START_SEQUENCE_ID = 1;
+
+ /* message to indicate profile update */
+ private static final int MSG_PROFILE_UPDATE = 1;
+
+ /* message to indicate start of profile selection process */
+ private static final int MSG_START_PROFILE_SELECTION = 2;
+ private boolean mIsEnabled = false;
+
+ @VisibleForTesting
+ protected Context mContext;
+
+ @VisibleForTesting
+ protected TelephonyManager mTelephonyManager;
+
+ @VisibleForTesting
+ protected ANASNetworkScanCtlr mNetworkScanCtlr;
+
+ private SubscriptionManager mSubscriptionManager;
+ private ANASProfileSelectionCallback mProfileSelectionCallback;
+ private int mSequenceId;
+
+ /* monitor the subscription for registration */
+ private ANASServiceStateMonitor mRegMonitor;
+ public static final String ACTION_SUB_SWITCH =
+ "android.intent.action.SUBSCRIPTION_SWITCH_REPLY";
+
+ /* service monitor callback will get called for service state change on a particular subId. */
+ private ANASServiceStateMonitor.ANASServiceMonitorCallback mServiceMonitorCallback =
+ new ANASServiceStateMonitor.ANASServiceMonitorCallback() {
+ @Override
+ public void onServiceMonitorUpdate(int subId, int state) {
+ switch (state) {
+ case ANASServiceStateMonitor.EVALUATED_STATE_BAD:
+ switchPreferredData(subId);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private SubscriptionManager.OnOpportunisticSubscriptionsChangedListener mProfileChangeListener =
+ new SubscriptionManager.OnOpportunisticSubscriptionsChangedListener() {
+ @Override
+ public void onOpportunisticSubscriptionsChanged() {
+ mHandler.sendEmptyMessage(MSG_PROFILE_UPDATE);
+ }
+ };
+
+ @VisibleForTesting
+ protected Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PROFILE_UPDATE:
+ case MSG_START_PROFILE_SELECTION:
+ logDebug("Msg received for profile update");
+ checkProfileUpdate();
+ break;
+ default:
+ log("invalid message");
+ break;
+ }
+ }
+ };
+
+ /**
+ * Broadcast receiver to receive intents
+ */
+ @VisibleForTesting
+ protected final BroadcastReceiver mProfileSelectorBroadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int sequenceId;
+ int subId;
+ String action = intent.getAction();
+ if (!mIsEnabled || action == null) {
+ return;
+ }
+
+ switch (action) {
+ case ACTION_SUB_SWITCH:
+ sequenceId = intent.getIntExtra("sequenceId", INVALID_SEQUENCE_ID);
+ subId = intent.getIntExtra("subId",
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (sequenceId != mSequenceId) {
+ return;
+ }
+
+ onSubSwitchComplete(subId);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Network scan callback handler
+ */
+ @VisibleForTesting
+ protected ANASNetworkScanCtlr.NetworkAvailableCallBack mNetworkAvailableCallBack =
+ new ANASNetworkScanCtlr.NetworkAvailableCallBack() {
+ @Override
+ public void onNetworkAvailability(List<CellInfo> results) {
+ /* sort the results according to signal strength level */
+ Collections.sort(results, new Comparator<CellInfo>() {
+ @Override
+ public int compare(CellInfo cellInfo1, CellInfo cellInfo2) {
+ return getSignalLevel(cellInfo1) - getSignalLevel(cellInfo2);
+ }
+ });
+
+ /* get subscription id for the best network scan result */
+ int subId = getSubId(getMcc(results.get(0)), getMnc(results.get(0)));
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ /* could not find any matching subscriptions */
+ return;
+ }
+
+ /* if subscription is already active, proceed to data switch */
+ if (mSubscriptionManager.isActiveSubId(subId)) {
+ /* if subscription already is data subscription,
+ complete the profile selection process */
+ /* Todo: change to getPreferredDataSubscriptionId once ready */
+ if (mSubscriptionManager.getDefaultDataSubscriptionId() == subId) {
+ mProfileSelectionCallback.onProfileSelectionDone(subId,
+ mSubscriptionManager.getDefaultSubscriptionId());
+ } else {
+ switchPreferredData(subId);
+ }
+ } else {
+ switchToSubscription(subId);
+ }
+ }
+
+ @Override
+ public void onError(int error) {
+ log("Network scan failed with error " + error);
+ }
+ };
+
+ /**
+ * interface call back to confirm profile selection
+ */
+ public interface ANASProfileSelectionCallback {
+
+ /**
+ * interface call back to confirm profile selection
+ */
+ void onProfileSelectionDone(int dataSubId, int voiceSubId);
+ }
+
+ /**
+ * ANASProfileSelector constructor
+ * @param c context
+ * @param profileSelectionCallback callback to be called once selection is done
+ */
+ public ANASProfileSelector(Context c, ANASProfileSelectionCallback profileSelectionCallback) {
+ init(c, profileSelectionCallback);
+ log("ANASProfileSelector init complete");
+ }
+
+ private int getSignalLevel(CellInfo cellInfo) {
+ if (cellInfo != null) {
+ return cellInfo.getCellSignalStrength().getLevel();
+ } else {
+ return SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ }
+ }
+
+ private String getMcc(CellInfo cellInfo) {
+ String mcc = "";
+ if (cellInfo instanceof CellInfoGsm) {
+ mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMccString();
+ } else if (cellInfo instanceof CellInfoLte) {
+ mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
+ } else if (cellInfo instanceof CellInfoWcdma) {
+ mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMccString();
+ }
+
+ return mcc;
+ }
+
+ private String getMnc(CellInfo cellInfo) {
+ String mnc = "";
+ if (cellInfo instanceof CellInfoGsm) {
+ mnc = ((CellInfoGsm) cellInfo).getCellIdentity().getMncString();
+ } else if (cellInfo instanceof CellInfoLte) {
+ mnc = ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
+ } else if (cellInfo instanceof CellInfoWcdma) {
+ mnc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMncString();
+ }
+
+ return mnc;
+ }
+
+ private int getSubId(String mcc, String mnc) {
+ List<SubscriptionInfo> subscriptionInfos =
+ mSubscriptionManager.getOpportunisticSubscriptions(1);
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
+ if (TextUtils.equals(subscriptionInfo.getMccString(), mcc)
+ && TextUtils.equals(subscriptionInfo.getMncString(), mnc)) {
+ return subscriptionInfo.getSubscriptionId();
+ }
+ }
+
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ private void switchToSubscription(int subId) {
+ Intent callbackIntent = new Intent(ACTION_SUB_SWITCH);
+ callbackIntent.setClass(mContext, ANASProfileSelector.class);
+ callbackIntent.putExtra("sequenceId", getAndUpdateToken());
+ callbackIntent.putExtra("subId", subId);
+
+ PendingIntent replyIntent = PendingIntent.getService(mContext,
+ 1, callbackIntent,
+ Intent.FILL_IN_ACTION);
+ mSubscriptionManager.switchToSubscription(subId, replyIntent);
+ }
+
+ private void switchPreferredData(int subId) {
+ mSubscriptionManager.setPreferredData(mSubscriptionManager.getSlotIndex(subId));
+ onDataSwitchComplete(subId);
+ }
+
+ private void onSubSwitchComplete(int subId) {
+ mRegMonitor.startListeningForNetworkConditionChange(subId);
+ }
+
+ private void onDataSwitchComplete(int subId) {
+ mProfileSelectionCallback.onProfileSelectionDone(subId,
+ mSubscriptionManager.getDefaultSubscriptionId());
+ }
+
+ private int getAndUpdateToken() {
+ synchronized (mLock) {
+ return mSequenceId++;
+ }
+ }
+
+ private void checkProfileUpdate() {
+ List<SubscriptionInfo> subscriptionInfos =
+ mSubscriptionManager.getOpportunisticSubscriptions(1);
+ if (subscriptionInfos == null) {
+ logDebug("received null subscription infos");
+ return;
+ }
+
+ if (subscriptionInfos.size() > 0) {
+ logDebug("opportunistic subscriptions size " + subscriptionInfos.size());
+
+ /* start scan immediately */
+ mNetworkScanCtlr.startFastNetworkScan(subscriptionInfos);
+ } else if (subscriptionInfos.size() == 0) {
+ /* check if no profile */
+ log("checkProfileUpdate 0 out");
+ mNetworkScanCtlr.stopNetworkScan();
+ }
+ }
+
+ /**
+ * start profile selection procedure
+ */
+ public void startProfileSelection() {
+ synchronized (mLock) {
+ if (!mIsEnabled) {
+ mIsEnabled = true;
+ mHandler.sendEmptyMessage(MSG_START_PROFILE_SELECTION);
+ }
+ }
+ }
+
+ /**
+ * select primary profile for data
+ */
+ public void selectPrimaryProfileForData() {
+ mSubscriptionManager.setPreferredData(mSubscriptionManager.getDefaultSubscriptionId());
+ }
+
+ /**
+ * stop profile selection procedure
+ */
+ public void stopProfileSelection() {
+ mNetworkScanCtlr.stopNetworkScan();
+ synchronized (mLock) {
+ mIsEnabled = false;
+ }
+ }
+
+ protected void init(Context c, ANASProfileSelectionCallback profileSelectionCallback) {
+ mContext = c;
+ mNetworkScanCtlr = new ANASNetworkScanCtlr(mContext, mTelephonyManager,
+ mNetworkAvailableCallBack);
+ mSequenceId = START_SEQUENCE_ID;
+ mProfileSelectionCallback = profileSelectionCallback;
+ mTelephonyManager = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ mSubscriptionManager = (SubscriptionManager)
+ mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mRegMonitor = new ANASServiceStateMonitor(mContext, mServiceMonitorCallback);
+
+ /* register for profile update events */
+ mSubscriptionManager.addOnOpportunisticSubscriptionsChangedListener(
+ AsyncTask.SERIAL_EXECUTOR, mProfileChangeListener);
+
+ /* register for subscription switch intent */
+ mContext.registerReceiver(mProfileSelectorBroadcastReceiver,
+ new IntentFilter(ACTION_SUB_SWITCH));
+ }
+
+ private void log(String msg) {
+ Rlog.d(LOG_TAG, msg);
+ }
+
+ private void logDebug(String msg) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, msg);
+ }
+ }
+}
diff --git a/src/com/android/phone/anas/ANASServiceStateEvaluator.java b/src/com/android/phone/anas/ANASServiceStateEvaluator.java
new file mode 100644
index 0000000..23549a4
--- /dev/null
+++ b/src/com/android/phone/anas/ANASServiceStateEvaluator.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2018 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.phone.anas;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * ANASServiceStateEvaluator class which will evaluate the data service state of a subId
+ * compared to another.
+ */
+public class ANASServiceStateEvaluator {
+ private Context mContext;
+ private final Object mLock = new Object();
+ private int mOppDataSubId;
+ private int mPrimarySubId;
+ /* new opportunistic data service state */
+ private int mOppDataNewState = ANASServiceStateMonitor.EVALUATED_STATE_UNKNOWN;
+ private int mPrimaryNewState = ANASServiceStateMonitor.EVALUATED_STATE_UNKNOWN;
+ private boolean mIsWaitingForTimeout = false;
+
+ @VisibleForTesting
+ protected ANASServiceEvaluatorCallback mServiceEvaluatorCallback;
+
+ @VisibleForTesting
+ protected ANASServiceStateMonitor mOppDataSubMonitor;
+
+ @VisibleForTesting
+ protected ANASServiceStateMonitor mPrimarySubMonitor;
+
+ @VisibleForTesting
+ protected AlarmManager mAlarmManager;
+
+ private static final int WAIT_FOR_DATA_SERVICE_PERIOD = (int) TimeUnit.SECONDS.toMillis(10);
+ private static final String LOG_TAG = "ANASServiceStateEvaluator";
+ private static final boolean DBG = true;
+
+ /* message to indicate no data for WAIT_FOR_DATA_SERVICE_PERIOD */
+ private static final int MSG_WAIT_FOR_DATA_SERVICE_TIMOUT = 1;
+
+ /**
+ * call back to confirm service state evaluation
+ */
+ public interface ANASServiceEvaluatorCallback {
+
+ /**
+ * call back to confirm bad service
+ */
+ void onBadDataService();
+ }
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WAIT_FOR_DATA_SERVICE_TIMOUT:
+ mIsWaitingForTimeout = false;
+ logDebug("Msg received to get data");
+ evaluateUpdatedState();
+ break;
+
+ default:
+ log("invalid message");
+ break;
+ }
+ }
+ };
+
+ public final AlarmManager.OnAlarmListener mDataServiceWaitTimer =
+ (AlarmManager.OnAlarmListener) () -> {
+ logDebug("Alarm fired");
+ mHandler.sendEmptyMessage(MSG_WAIT_FOR_DATA_SERVICE_TIMOUT);
+ };
+
+ /**
+ * set alarm to wait for data service
+ */
+ private void setDataServiceWaitAlarm() {
+ mAlarmManager.set(AlarmManager.RTC, System.currentTimeMillis()
+ + WAIT_FOR_DATA_SERVICE_PERIOD, LOG_TAG, mDataServiceWaitTimer, null);
+ }
+
+ /**
+ * stop the alarm
+ */
+ private void stopDataServiceWaitAlarm() {
+ mAlarmManager.cancel(mDataServiceWaitTimer);
+ }
+
+ private boolean evaluateIfBadOpportunisticDataService() {
+ /* if we have not received update on both subId, we can not take decision, yes */
+ log("evaluateIfBadOpportunisticDataService: mPrimaryNewState: "
+ + ANASServiceStateMonitor.getStateString(mPrimaryNewState) + " mOppDataNewState: "
+ + ANASServiceStateMonitor.getStateString(mOppDataNewState));
+
+ if ((mPrimaryNewState == ANASServiceStateMonitor.EVALUATED_STATE_UNKNOWN)
+ || (mOppDataNewState == ANASServiceStateMonitor.EVALUATED_STATE_UNKNOWN)) {
+ return false;
+ }
+
+ /* Evaluate if primary subscription has good service and if
+ opportunistic data subscription is not, if yes return true.
+ */
+ switch (mPrimaryNewState) {
+ case ANASServiceStateMonitor.EVALUATED_STATE_NO_SERVICE:
+ /* no need to make any change */
+ return false;
+ case ANASServiceStateMonitor.EVALUATED_STATE_BAD:
+ if ((mOppDataNewState == ANASServiceStateMonitor.EVALUATED_STATE_BAD)
+ || (mOppDataNewState == ANASServiceStateMonitor.EVALUATED_STATE_GOOD)) {
+ return false;
+ }
+
+ break;
+ case ANASServiceStateMonitor.EVALUATED_STATE_GOOD:
+ if (mOppDataNewState == ANASServiceStateMonitor.EVALUATED_STATE_GOOD) {
+ return false;
+ }
+ break;
+ default:
+ log("invalid state");
+ break;
+ }
+
+ return true;
+ }
+
+ private void evaluateUpdatedState() {
+ logDebug("evaluateUpdatedState " + mIsWaitingForTimeout);
+ if (!mIsWaitingForTimeout && evaluateIfBadOpportunisticDataService()) {
+ mServiceEvaluatorCallback.onBadDataService();
+ }
+ }
+
+ /* service monitor callback will get called for service state change on a particular subId. */
+ ANASServiceStateMonitor.ANASServiceMonitorCallback mServiceMonitorCallback =
+ new ANASServiceStateMonitor.ANASServiceMonitorCallback() {
+ @Override
+ public void onServiceMonitorUpdate(int subId, int state) {
+ logDebug("onServiceMonitorUpdate subId: " + subId + " state: "
+ + ANASServiceStateMonitor.getStateString(state));
+ synchronized (mLock) {
+ if (mServiceEvaluatorCallback == null) {
+ return;
+ }
+
+ if (subId == mPrimarySubId) {
+ mPrimaryNewState = state;
+ } else if (subId == mOppDataSubId) {
+ mOppDataNewState = state;
+ } else {
+ logDebug("invalid sub id");
+ }
+
+ evaluateUpdatedState();
+ }
+ }
+ };
+
+ public ANASServiceStateEvaluator(Context c,
+ ANASServiceEvaluatorCallback serviceEvaluatorCallback) {
+ init(c, serviceEvaluatorCallback);
+ }
+
+ protected void init(Context c, ANASServiceEvaluatorCallback serviceEvaluatorCallback) {
+ mContext = c;
+ mServiceEvaluatorCallback = serviceEvaluatorCallback;
+ mOppDataSubMonitor = new ANASServiceStateMonitor(mContext, mServiceMonitorCallback);
+ mPrimarySubMonitor = new ANASServiceStateMonitor(mContext, mServiceMonitorCallback);
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ /**
+ * start service state evaluation for dataSubId compared to voiceSubId.
+ * This API evaluates the service state conditions of dataSubId and decides whether
+ * data service is bad compared to voiceSubId
+ * @param dataSubId current data subscription id
+ * @param voiceSubId voice subscription id
+ */
+ public void startEvaluation(int dataSubId, int voiceSubId) {
+ logDebug("Start evaluation");
+ /* make sure to clean up if there is any evaluation going on. */
+ stopEvaluation();
+ setDataServiceWaitAlarm();
+ synchronized (mLock) {
+ mIsWaitingForTimeout = true;
+ mOppDataSubId = dataSubId;
+ mPrimarySubId = voiceSubId;
+ mOppDataSubMonitor.startListeningForNetworkConditionChange(dataSubId);
+ mPrimarySubMonitor.startListeningForNetworkConditionChange(voiceSubId);
+ }
+ }
+
+ /**
+ * stop service state evaluation
+ */
+ public void stopEvaluation() {
+ logDebug("Stop evaluation");
+ synchronized (mLock) {
+ mOppDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ mPrimarySubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ if (mIsWaitingForTimeout) {
+ stopDataServiceWaitAlarm();
+ }
+ mIsWaitingForTimeout = false;
+ mOppDataSubMonitor.stopListeningForNetworkConditionChange();
+ mPrimarySubMonitor.stopListeningForNetworkConditionChange();
+ mOppDataNewState = ANASServiceStateMonitor.EVALUATED_STATE_UNKNOWN;
+ mPrimaryNewState = ANASServiceStateMonitor.EVALUATED_STATE_UNKNOWN;
+ }
+ }
+
+
+ private void log(String msg) {
+ Rlog.d(LOG_TAG, msg);
+ }
+
+ private void logDebug(String msg) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, msg);
+ }
+ }
+}
diff --git a/src/com/android/phone/anas/ANASServiceStateMonitor.java b/src/com/android/phone/anas/ANASServiceStateMonitor.java
new file mode 100644
index 0000000..90a1564
--- /dev/null
+++ b/src/com/android/phone/anas/ANASServiceStateMonitor.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2018 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.phone.anas;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * ANASServiceStateMonitor class which will monitor service state of a given subscription.
+ */
+public class ANASServiceStateMonitor {
+ @VisibleForTesting
+ protected Context mContext;
+
+ @VisibleForTesting
+ protected TelephonyManager mTelephonyManager;
+
+ @VisibleForTesting
+ protected ConnectivityManager mConnectivityManager;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"EVALUATED_STATE_"},
+ value = {
+ EVALUATED_STATE_UNKNOWN,
+ EVALUATED_STATE_NO_SERVICE,
+ EVALUATED_STATE_BAD,
+ EVALUATED_STATE_GOOD})
+ public @interface EvaluatedState {}
+
+ /* service states to be used while reporting onServiceMonitorUpdate */
+ public static final int EVALUATED_STATE_UNKNOWN = 0;
+
+ /* network is not available */
+ public static final int EVALUATED_STATE_NO_SERVICE = 1;
+
+ /* network is available but not good */
+ public static final int EVALUATED_STATE_BAD = 2;
+
+ /* network is available and good */
+ public static final int EVALUATED_STATE_GOOD = 3;
+
+ private static final String LOG_TAG = "ANASServiceStateMonitor";
+ private static final boolean DBG = true;
+ private ANASServiceMonitorCallback mServiceMonitorCallback;
+ private PhoneStateListener mPhoneStateListener;
+ private int mSubId;
+ private @EvaluatedState int mSignalStrengthEvaluatedState;
+ private @EvaluatedState int mServiceStateEvaluatedState;
+ private final Object mLock = new Object();
+
+ protected void init(Context c, ANASServiceMonitorCallback serviceMonitorCallback) {
+ mContext = c;
+ mTelephonyManager = TelephonyManager.from(mContext);
+ mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mSignalStrengthEvaluatedState = EVALUATED_STATE_UNKNOWN;
+ mServiceStateEvaluatedState = EVALUATED_STATE_UNKNOWN;
+ mServiceMonitorCallback = serviceMonitorCallback;
+ logDebug("[ANASServiceStateMonitor] init by Context");
+ }
+
+ /**
+ * get the string name of a state
+ * @param state service state
+ * @return string name of a state
+ */
+ public static String getStateString(@EvaluatedState int state) {
+ switch (state) {
+ case EVALUATED_STATE_NO_SERVICE:
+ return "No Service";
+ case EVALUATED_STATE_BAD:
+ return "Bad Service";
+ case EVALUATED_STATE_GOOD:
+ return "Good Service";
+ default:
+ return "Unknown";
+ }
+ }
+
+ /**
+ * returns whether the fail reason is permanent
+ * @param failCause fail reason
+ * @return true if reason is permanent
+ */
+ @VisibleForTesting
+ public static boolean isFatalFailCause(String failCause) {
+ if (failCause == null || failCause.isEmpty()) {
+ return false;
+ }
+
+ switch (failCause) {
+ case "OPERATOR_BARRED":
+ case "USER_AUTHENTICATION":
+ case "ACTIVATION_REJECT_GGSN":
+ case "SERVICE_OPTION_NOT_SUPPORTED":
+ case "SERVICE_OPTION_NOT_SUBSCRIBED":
+ case "SERVICE_OPTION_OUT_OF_ORDER":
+ case "PROTOCOL_ERRORS":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void updateCallbackOnFinalState() {
+ int evaluatedState = EVALUATED_STATE_UNKNOWN;
+
+ logDebug("mServiceStateEvaluatedState: " + getStateString(mServiceStateEvaluatedState)
+ + " mSignalStrengthEvaluatedState: "
+ + getStateString(mSignalStrengthEvaluatedState));
+
+ /* Service state has highest priority in this validation. If no service, no need to
+ check further. */
+ if (mServiceStateEvaluatedState == EVALUATED_STATE_GOOD) {
+ evaluatedState = EVALUATED_STATE_GOOD;
+ } else if (mServiceStateEvaluatedState == EVALUATED_STATE_NO_SERVICE) {
+ evaluatedState = EVALUATED_STATE_NO_SERVICE;
+ mServiceMonitorCallback.onServiceMonitorUpdate(mSubId, EVALUATED_STATE_NO_SERVICE);
+ return;
+ }
+
+ /* use signal strength to determine service quality only, i.e is good or bad. */
+ if (evaluatedState == EVALUATED_STATE_GOOD) {
+ if (mSignalStrengthEvaluatedState == EVALUATED_STATE_BAD) {
+ evaluatedState = EVALUATED_STATE_BAD;
+ }
+ }
+
+ if (evaluatedState != EVALUATED_STATE_UNKNOWN) {
+ mServiceMonitorCallback.onServiceMonitorUpdate(mSubId, evaluatedState);
+ }
+ }
+
+ private void analyzeSignalStrengthChange(SignalStrength signalStrength) {
+ if (mServiceMonitorCallback == null) {
+ return;
+ }
+
+ if (signalStrength.getLevel() <= SignalStrength.SIGNAL_STRENGTH_POOR) {
+ mSignalStrengthEvaluatedState = EVALUATED_STATE_BAD;
+ } else {
+ mSignalStrengthEvaluatedState = EVALUATED_STATE_GOOD;
+ }
+
+ updateCallbackOnFinalState();
+ }
+
+ private void analyzeServiceStateChange(ServiceState serviceState) {
+ logDebug("analyzeServiceStateChange state:"
+ + serviceState.getDataRegState());
+ if (mServiceMonitorCallback == null) {
+ return;
+ }
+
+ if ((serviceState.getDataRegState() == ServiceState.STATE_OUT_OF_SERVICE)
+ || (serviceState.getState() == ServiceState.STATE_EMERGENCY_ONLY)) {
+ mServiceStateEvaluatedState = EVALUATED_STATE_NO_SERVICE;
+ } else if (serviceState.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+ mServiceStateEvaluatedState = EVALUATED_STATE_GOOD;
+ }
+
+ updateCallbackOnFinalState();
+ }
+
+ /**
+ * Implements phone state listener
+ */
+ @VisibleForTesting
+ public class PhoneStateListenerImpl extends PhoneStateListener {
+ PhoneStateListenerImpl(int subId) {
+ super(subId);
+ }
+
+ private boolean shouldIgnore() {
+ if (PhoneStateListenerImpl.this.mSubId != ANASServiceStateMonitor.this.mSubId) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ synchronized (mLock) {
+ if (shouldIgnore()) {
+ return;
+ }
+
+ analyzeSignalStrengthChange(signalStrength);
+ }
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ synchronized (mLock) {
+ if (shouldIgnore()) {
+ return;
+ }
+
+ analyzeServiceStateChange(serviceState);
+ }
+ }
+ };
+
+ /**
+ * get phone state listener instance
+ * @param subId subscription id
+ * @return the listener instance
+ */
+ @VisibleForTesting
+ public PhoneStateListener getPhoneStateListener(int subId) {
+ synchronized (mLock) {
+ if (mPhoneStateListener != null && subId == mSubId) {
+ return mPhoneStateListener;
+ }
+
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mSubId = subId;
+ mPhoneStateListener = (PhoneStateListener) new PhoneStateListenerImpl(subId);
+ }
+ return mPhoneStateListener;
+ }
+
+ /**
+ * call back interface
+ */
+ public interface ANASServiceMonitorCallback {
+ /**
+ * call back interface
+ */
+ void onServiceMonitorUpdate(int subId, @EvaluatedState int state);
+ }
+
+ /**
+ * request to start listening for network changes.
+ */
+ public void startListeningForNetworkConditionChange(int subId) {
+
+ logDebug("start network condition listen for " + subId);
+ /* monitor service state, signal strength and data connection state */
+ synchronized (mLock) {
+ int events = PhoneStateListener.LISTEN_SERVICE_STATE
+ | PhoneStateListener.LISTEN_SIGNAL_STRENGTH;
+ mTelephonyManager.listen(getPhoneStateListener(subId), events);
+ }
+ }
+
+ /**
+ * request to stop listening for network changes.
+ */
+ public void stopListeningForNetworkConditionChange() {
+ logDebug("stop network condition listen for " + mSubId);
+ synchronized (mLock) {
+ if (mPhoneStateListener != null) {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ }
+ mSignalStrengthEvaluatedState = EVALUATED_STATE_UNKNOWN;
+ mServiceStateEvaluatedState = EVALUATED_STATE_UNKNOWN;
+ }
+ }
+
+ public ANASServiceStateMonitor(Context c, ANASServiceMonitorCallback serviceMonitorCallback) {
+ init(c, serviceMonitorCallback);
+ }
+
+ private static void log(String msg) {
+ Rlog.d(LOG_TAG, msg);
+ }
+
+ private static void logDebug(String msg) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, msg);
+ }
+ }
+}
diff --git a/src/com/android/phone/anas/AlternativeNetworkAccessService.java b/src/com/android/phone/anas/AlternativeNetworkAccessService.java
new file mode 100644
index 0000000..b972813
--- /dev/null
+++ b/src/com/android/phone/anas/AlternativeNetworkAccessService.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2018 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.phone.anas;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IAnas;
+import com.android.internal.telephony.TelephonyPermissions;
+
+/**
+ * AlternativeNetworkAccessService implements ianas.
+ * It scans network and matches the results with opportunistic subscriptions.
+ * Use the same to provide user opportunistic data in areas with corresponding networks
+ */
+public class AlternativeNetworkAccessService extends IAnas.Stub {
+ private Context mContext;
+ private TelephonyManager mTelephonyManager;
+ private SubscriptionManager mSubsriptionManager;
+
+ private final Object mLock = new Object();
+ private boolean mIsEnabled;
+ private ANASProfileSelector mProfileSelector;
+ private ANASServiceStateEvaluator mServiceStateEvaluator;
+ private SharedPreferences mSharedPref;
+
+ /** The singleton instance. */
+ private static AlternativeNetworkAccessService sInstance = null;
+ private static final String TAG = "ANAS";
+ private static final String PREF_NAME = TAG;
+ private static final String PREF_ENABLED = "isEnabled";
+ private static final boolean DBG = true;
+
+ /**
+ * Profile selection callback. Will be called once Profile selector decides on
+ * the opportunistic data profile.
+ */
+ private ANASProfileSelector.ANASProfileSelectionCallback mProfileSelectionCallback =
+ new ANASProfileSelector.ANASProfileSelectionCallback() {
+
+ @Override
+ public void onProfileSelectionDone(int dataSubId, int voiceSubId) {
+ logDebug("profile selection done");
+ mProfileSelector.stopProfileSelection();
+ mServiceStateEvaluator.startEvaluation(dataSubId, voiceSubId);
+ }
+ };
+
+ /**
+ * Service state evaluator callback. Will be called once service state evaluator thinks
+ * that current opportunistic data is not providing good service.
+ */
+ private ANASServiceStateEvaluator.ANASServiceEvaluatorCallback mServiceEvaluatorCallback =
+ new ANASServiceStateEvaluator.ANASServiceEvaluatorCallback() {
+ @Override
+ public void onBadDataService() {
+ logDebug("Bad opportunistic data service");
+ mServiceStateEvaluator.stopEvaluation();
+ mProfileSelector.selectPrimaryProfileForData();
+ mProfileSelector.startProfileSelection();
+ }
+ };
+
+ /**
+ * create AlternativeNetworkAccessService instance
+ *
+ * @param c context
+ *
+ */
+ public static void initInstance(Context c) {
+ if (sInstance == null) {
+ sInstance = new AlternativeNetworkAccessService(c);
+ }
+ return;
+ }
+
+ /**
+ * get AlternativeNetworkAccessService instance
+ *
+ */
+ @VisibleForTesting
+ public static AlternativeNetworkAccessService getInstance() {
+ if (sInstance == null) {
+ Log.wtf(TAG, "getInstance null");
+ }
+ return sInstance;
+ }
+
+ /**
+ * Enable or disable Alternative Network Access service.
+ *
+ * This method should be called to enable or disable
+ * AlternativeNetworkAccess service on the device.
+ *
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ *
+ * @param enable enable(True) or disable(False)
+ * @param callingPackage caller's package name
+ * @return returns true if successfully set.
+ */
+ @Override
+ public boolean setEnable(boolean enable, String callingPackage) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ mContext, mSubsriptionManager.getDefaultSubscriptionId(), "setEnable");
+ log("setEnable: " + enable);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ enableAlternativeNetworkAccess(enable);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ return true;
+ }
+
+ /**
+ * is Alternative Network Access service enabled
+ *
+ * This method should be called to determine if the Alternative Network Access service
+ * is enabled
+ *
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+ *
+ * @param callingPackage caller's package name
+ */
+ @Override
+ public boolean isEnabled(String callingPackage) {
+ TelephonyPermissions.enforeceCallingOrSelfReadPhoneStatePermissionOrCarrierPrivilege(
+ mContext, mSubsriptionManager.getDefaultSubscriptionId(), "isEnabled");
+ return mIsEnabled;
+ }
+
+ /**
+ * initialize ANAS and register as service.
+ * Read persistent state to update enable state
+ * Start sub components if already enabled.
+ * @param context context instance
+ */
+ private void initializeAndRegisterAsService(Context context) {
+ mContext = context;
+ mTelephonyManager = TelephonyManager.from(mContext);
+ mServiceStateEvaluator = new ANASServiceStateEvaluator(mContext, mServiceEvaluatorCallback);
+ mProfileSelector = new ANASProfileSelector(mContext, mProfileSelectionCallback);
+ mSharedPref = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
+ mSubsriptionManager = (SubscriptionManager) mContext.getSystemService(
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+
+ /* register the service */
+ if (ServiceManager.getService("ianas") == null) {
+ ServiceManager.addService("ianas", this);
+ }
+
+ enableAlternativeNetworkAccess(getPersistentEnableState());
+ }
+
+ private AlternativeNetworkAccessService(Context c) {
+ initializeAndRegisterAsService(c);
+ log("init completed");
+ }
+
+ private boolean getPersistentEnableState() {
+ return mSharedPref.getBoolean(PREF_ENABLED, true);
+ }
+
+ private void updateEnableState(boolean enable) {
+ mIsEnabled = enable;
+ mSharedPref.edit().putBoolean(PREF_ENABLED, mIsEnabled).apply();
+ }
+
+ /**
+ * update the enable state
+ * start profile selection if enabled.
+ * @param enable enable(true) or disable(false)
+ */
+ private void enableAlternativeNetworkAccess(boolean enable) {
+ synchronized (mLock) {
+ if (mIsEnabled != enable) {
+ updateEnableState(enable);
+ if (mIsEnabled) {
+ mProfileSelector.startProfileSelection();
+ } else {
+ mProfileSelector.stopProfileSelection();
+ }
+ }
+ }
+ logDebug("service is enable state " + mIsEnabled);
+ }
+
+ private void log(String msg) {
+ Rlog.d(TAG, msg);
+ }
+
+ private void logDebug(String msg) {
+ if (DBG) Rlog.d(TAG, msg);
+ }
+}
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index 24c3870..5d987f7 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -211,6 +211,7 @@
// 4) Add the conference to the connection service if it is new.
if (isNewlyCreated) {
Log.d(this, "Adding the conference call");
+ mConference.updateCallRadioTechAfterCreation();
mConnectionService.addConference(mConference);
}
} else if (conferenceConnections.isEmpty()) {
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index b196d57..5722834 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -640,6 +640,7 @@
setState(mConferenceHost.getState());
updateStatusHints();
+ putExtras(mConferenceHost.getExtras());
}
/**
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index e96815c..e9eef46 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -264,6 +264,7 @@
mTelephonyConference, connection);
mTelephonyConference.addConnection(connection);
}
+ mTelephonyConference.updateCallRadioTechAfterCreation();
mConnectionService.addConference(mTelephonyConference);
} else {
Log.d(this, "Trigger recalculate later");
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 31fe68f..7d25767 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -36,6 +36,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
import android.text.TextUtils;
@@ -96,6 +97,7 @@
private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15;
private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16;
private static final int MSG_HANGUP = 17;
+ private static final int MSG_SET_CALL_RADIO_TECH = 18;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
@@ -240,6 +242,31 @@
int cause = (int) msg.obj;
hangup(cause);
break;
+
+ case MSG_SET_CALL_RADIO_TECH:
+ int vrat = (int) msg.obj;
+ // Check whether Wi-Fi call tech is changed, it means call radio tech is:
+ // a) changed from IWLAN to other value, or
+ // b) changed from other value to IWLAN.
+ //
+ // In other word, below conditions are all met:
+ // 1) {@link #getCallRadioTech} is different from new vrat
+ // 2) Current call radio technology indicates Wi-Fi call, i.e. {@link #isWifi}
+ // is true, or new vrat indicates Wi-Fi call.
+ boolean isWifiTechChange = getCallRadioTech() != vrat
+ && (isWifi() || vrat == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+
+ // Step 1) Updates call radio tech firstly, so that afterwards Wi-Fi related
+ // update actions are taken correctly.
+ setCallRadioTech(vrat);
+
+ // Step 2) Handles Wi-Fi call tech change.
+ if (isWifiTechChange) {
+ updateConnectionProperties();
+ updateStatusHints();
+ refreshDisableAddCall();
+ }
+ break;
}
}
};
@@ -421,14 +448,14 @@
}
/**
- * Used by {@link com.android.internal.telephony.Connection} to report a change in whether
- * the call is being made over a wifi network.
+ * Used by {@link com.android.internal.telephony.Connection} to report a change for
+ * the call radio technology.
*
- * @param isWifi True if call is made over wifi.
+ * @param vrat the RIL Voice Radio Technology used for current connection.
*/
@Override
- public void onWifiChanged(boolean isWifi) {
- setWifi(isWifi);
+ public void onCallRadioTechChanged(@ServiceState.RilRadioTechnology int vrat) {
+ mHandler.obtainMessage(MSG_SET_CALL_RADIO_TECH, vrat).sendToTarget();
}
/**
@@ -599,13 +626,6 @@
private int mOriginalConnectionCapabilities;
/**
- * Determines if the {@link TelephonyConnection} is using wifi.
- * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to
- * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property.
- */
- private boolean mIsWifi;
-
- /**
* Determines the audio quality is high for the {@link TelephonyConnection}.
* This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to
* indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property.
@@ -1030,7 +1050,7 @@
newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO,
hasHighDefAudioProperty());
- newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi);
+ newProperties = changeBitmask(newProperties, PROPERTY_WIFI, isWifi());
newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
isExternalConnection());
newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
@@ -1117,12 +1137,13 @@
// Set video state and capabilities
setVideoState(mOriginalConnection.getVideoState());
setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
- setWifi(mOriginalConnection.isWifi());
setAudioModeIsVoip(mOriginalConnection.getAudioModeIsVoip());
setVideoProvider(mOriginalConnection.getVideoProvider());
setAudioQuality(mOriginalConnection.getAudioQuality());
setTechnologyTypeExtra();
+ setCallRadioTech(mOriginalConnection.getCallRadioTech());
+
// Post update of extras to the handler; extras are updated via the handler to ensure thread
// safety. The Extras Bundle is cloned in case the original extras are modified while they
// are being added to mOriginalConnectionExtras in updateExtras.
@@ -1239,7 +1260,7 @@
if (isCurrentVideoCall) {
return true;
- } else if (wasVideoCall && mIsWifi && !isVowifiEnabled) {
+ } else if (wasVideoCall && isWifi() && !isVowifiEnabled) {
return true;
}
return false;
@@ -1274,7 +1295,7 @@
return false;
}
- if (mIsWifi && !canWifiCallsBeHdAudio) {
+ if (isWifi() && !canWifiCallsBeHdAudio) {
return false;
}
@@ -1831,21 +1852,10 @@
}
/**
- * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset
- * the {@link Connection#PROPERTY_WIFI} property.
- */
- public void setWifi(boolean isWifi) {
- mIsWifi = isWifi;
- updateConnectionProperties();
- updateStatusHints();
- refreshDisableAddCall();
- }
-
- /**
* Whether the call is using wifi.
*/
boolean isWifi() {
- return mIsWifi;
+ return getCallRadioTech() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
}
/**
@@ -2025,7 +2035,7 @@
}
private void updateStatusHints() {
- if (mIsWifi && getPhone() != null) {
+ if (isWifi() && getPhone() != null) {
int labelId = isValidRingingCall()
? R.string.status_hint_label_incoming_wifi_call
: R.string.status_hint_label_wifi_call;