blob: cec914aaf464b08289c054c790cb081ef43e835a [file] [log] [blame]
/*
* Copyright (C) 2006 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;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.preference.ListPreference;
import android.preference.Preference;
import android.telephony.CellInfo;
import android.telephony.CellInfoCdma;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.telephony.OperatorInfo;
import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* "Networks" preference in "Mobile network" settings UI for the Phone app.
* It's used to manually search and choose mobile network. Enabled only when
* autoSelect preference is turned off.
*/
public class NetworkSelectListPreference extends ListPreference
implements DialogInterface.OnCancelListener,
Preference.OnPreferenceChangeListener{
private static final String LOG_TAG = "networkSelect";
private static final boolean DBG = true;
private static final int EVENT_MANUALLY_NETWORK_SELECTION_DONE = 1;
private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
private static final int EVENT_NETWORK_SCAN_COMPLETED = 3;
//dialog ids
private static final int DIALOG_NETWORK_SELECTION = 100;
private static final int DIALOG_NETWORK_LIST_LOAD = 200;
private List<CellInfo> mCellInfoList;
private CellInfo mCellInfo;
private int mSubId;
private TelephonyManager mTelephonyManager;
private NetworkOperators mNetworkOperators;
private List<String> mForbiddenPlmns;
private ProgressDialog mProgressDialog;
public NetworkSelectListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onClick() {
showProgressDialog(DIALOG_NETWORK_LIST_LOAD);
TelephonyManager telephonyManager = (TelephonyManager)
getContext().getSystemService(Context.TELEPHONY_SERVICE);
new AsyncTask<Void, Void, List<String>>() {
@Override
protected List<String> doInBackground(Void... voids) {
return Arrays.asList(telephonyManager.getForbiddenPlmns());
}
@Override
protected void onPostExecute(List<String> result) {
mForbiddenPlmns = result;
loadNetworksList();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_MANUALLY_NETWORK_SELECTION_DONE:
if (DBG) logd("hideProgressPanel");
try {
dismissProgressBar();
} catch (IllegalArgumentException e) {
}
setEnabled(true);
boolean isSuccessed = (boolean) msg.obj;
if (isSuccessed) {
if (DBG) {
logd("manual network selection: succeeded! "
+ getNetworkTitle(mCellInfo));
}
mNetworkOperators.displayNetworkSelectionSucceeded();
} else {
if (DBG) logd("manual network selection: failed!");
mNetworkOperators.displayNetworkSelectionFailed();
}
mNetworkOperators.getNetworkSelectionMode();
break;
case EVENT_NETWORK_SCAN_RESULTS:
List<CellInfo> results = (List<CellInfo>) msg.obj;
results.removeIf(cellInfo -> cellInfo == null);
mCellInfoList = new ArrayList<>(results);
if (DBG) logd("CALLBACK_SCAN_RESULTS" + mCellInfoList.toString());
break;
case EVENT_NETWORK_SCAN_COMPLETED:
try {
if (mNetworkQueryService != null) {
mNetworkQueryService.unregisterCallback(mCallback);
}
} catch (RemoteException e) {
loge("onComplete: exception from unregisterCallback " + e);
}
if (DBG) logd("scan complete, load the cellInfosList");
// Modify UI to indicate users that the scan has completed.
networksListLoaded();
}
return;
}
};
INetworkQueryService mNetworkQueryService = null;
/**
* This implementation of INetworkQueryServiceCallback is used to receive
* callback notifications from the network query service.
*/
private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
/** Returns the scan results to the user, this callback will be called only one time. */
public void onResults(List<CellInfo> results) {
if (DBG) logd("get scan results: " + results.toString());
Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
msg.sendToTarget();
}
/**
* Informs the user that the scan has stopped.
*
* This callback will be called when the scan is finished or cancelled by the user.
* The related NetworkScanRequest will be deleted after this callback.
*/
public void onComplete() {
if (DBG) logd("network scan completed.");
Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
msg.sendToTarget();
}
/**
* This callback will not be called, since the old Scan API won't send this callback.
*/
public void onError(int error) {}
};
@Override
//implemented for DialogInterface.OnCancelListener
public void onCancel(DialogInterface dialog) {
if (DBG) logd("user manually close the dialog");
// request that the service stop the query with this callback object.
try {
if (mNetworkQueryService != null) {
mNetworkQueryService.stopNetworkQuery();
mNetworkQueryService.unregisterCallback(mCallback);
}
// If cancelled, we query NetworkSelectMode and update states of AutoSelect button.
mNetworkOperators.getNetworkSelectionMode();
} catch (RemoteException e) {
loge("onCancel: exception from stopNetworkQuery " + e);
}
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
// If dismissed, we query NetworkSelectMode and update states of AutoSelect button.
if (!positiveResult) {
mNetworkOperators.getNetworkSelectionMode();
}
}
// This method is provided besides initialize() because bind to network query service
// may be binded after initialize(). In that case this method needs to be called explicitly
// to set mNetworkQueryService. Otherwise mNetworkQueryService will remain null.
public void setNetworkQueryService(INetworkQueryService queryService) {
mNetworkQueryService = queryService;
}
// This initialize method needs to be called for this preference to work properly.
protected void initialize(int subId, INetworkQueryService queryService,
NetworkOperators networkOperators, ProgressDialog progressDialog) {
mSubId = subId;
mNetworkQueryService = queryService;
mNetworkOperators = networkOperators;
// This preference should share the same progressDialog with networkOperators category.
mProgressDialog = progressDialog;
mTelephonyManager = TelephonyManager.from(getContext()).createForSubscriptionId(mSubId);
setSummary(mTelephonyManager.getNetworkOperatorName());
setOnPreferenceChangeListener(this);
}
@Override
protected void onPrepareForRemoval() {
destroy();
super.onPrepareForRemoval();
}
private void destroy() {
try {
dismissProgressBar();
} catch (IllegalArgumentException e) {
loge("onDestroy: exception from dismissProgressBar " + e);
}
try {
if (mNetworkQueryService != null) {
// used to un-register callback
mNetworkQueryService.unregisterCallback(mCallback);
}
} catch (RemoteException e) {
loge("onDestroy: exception from unregisterCallback " + e);
}
}
private void displayEmptyNetworkList() {
String status = getContext().getResources().getString(R.string.empty_networks_list);
final PhoneGlobals app = PhoneGlobals.getInstance();
app.notificationMgr.postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
}
private void displayNetworkSelectionInProgress() {
showProgressDialog(DIALOG_NETWORK_SELECTION);
}
private void displayNetworkQueryFailed(int error) {
String status = getContext().getResources().getString(R.string.network_query_error);
try {
dismissProgressBar();
} catch (IllegalArgumentException e1) {
// do nothing
}
final PhoneGlobals app = PhoneGlobals.getInstance();
app.notificationMgr.postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
}
private void loadNetworksList() {
if (DBG) logd("load networks list...");
try {
if (mNetworkQueryService != null) {
mNetworkQueryService.startNetworkQuery(
mCallback, mSubId, false /* isIncrementalResult */);
} else {
displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION);
}
} catch (RemoteException e) {
loge("loadNetworksList: exception from startNetworkQuery " + e);
displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION);
}
}
private void networksListLoaded() {
if (DBG) logd("networks list loaded");
// update the state of the preferences.
if (DBG) logd("hideProgressPanel");
// Always try to dismiss the dialog because activity may
// be moved to background after dialog is shown.
try {
dismissProgressBar();
} catch (IllegalArgumentException e) {
// It's not a error in following scenario, we just ignore it.
// "Load list" dialog will not show, if NetworkQueryService is
// connected after this activity is moved to background.
loge("Fail to dismiss network load list dialog " + e);
}
mNetworkOperators.getNetworkSelectionMode();
if (mCellInfoList != null) {
// create a preference for each item in the list.
// just use the operator name instead of the mildly
// confusing mcc/mnc.
List<CharSequence> networkEntriesList = new ArrayList<>();
List<CharSequence> networkEntryValuesList = new ArrayList<>();
for (CellInfo cellInfo: mCellInfoList) {
// Display each operator name only once.
String networkTitle = getNetworkTitle(cellInfo);
if (CellInfoUtil.isForbidden(cellInfo, mForbiddenPlmns)) {
networkTitle += " "
+ getContext().getResources().getString(R.string.forbidden_network);
}
networkEntriesList.add(networkTitle);
networkEntryValuesList.add(getOperatorNumeric(cellInfo));
}
setEntries(networkEntriesList.toArray(new CharSequence[networkEntriesList.size()]));
setEntryValues(networkEntryValuesList.toArray(
new CharSequence[networkEntryValuesList.size()]));
super.onClick();
} else {
displayEmptyNetworkList();
}
}
private void dismissProgressBar() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
private void showProgressDialog(int id) {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(getContext());
} else {
// Dismiss progress bar if it's showing now.
dismissProgressBar();
}
switch (id) {
case DIALOG_NETWORK_SELECTION:
final String networkSelectMsg = getContext().getResources()
.getString(R.string.register_on_network,
getNetworkTitle(mCellInfo));
mProgressDialog.setMessage(networkSelectMsg);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(false);
mProgressDialog.setIndeterminate(true);
break;
case DIALOG_NETWORK_LIST_LOAD:
mProgressDialog.setMessage(
getContext().getResources().getString(R.string.load_networks_progress));
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(true);
mProgressDialog.setIndeterminate(false);
mProgressDialog.setOnCancelListener(this);
break;
default:
}
mProgressDialog.show();
}
/**
* Implemented to support onPreferenceChangeListener to look for preference
* changes specifically on this button.
*
* @param preference is the preference to be changed, should be network select button.
* @param newValue should be the value of the selection as index of operators.
*/
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int operatorIndex = findIndexOfValue((String) newValue);
mCellInfo = mCellInfoList.get(operatorIndex);
if (DBG) logd("selected network: " + mCellInfo.toString());
MetricsLogger.action(getContext(),
MetricsEvent.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
ThreadUtils.postOnBackgroundThread(() -> {
final OperatorInfo operatorInfo = getOperatorInfoFromCellInfo(mCellInfo);
if (DBG) logd("manually selected network: " + operatorInfo.toString());
boolean isSuccessed = mTelephonyManager.setNetworkSelectionModeManual(
operatorInfo.getOperatorNumeric(), true /* persistSelection */);
int mode = mTelephonyManager.getNetworkSelectionMode();
Message msg = mHandler.obtainMessage(EVENT_MANUALLY_NETWORK_SELECTION_DONE);
msg.obj = isSuccessed;
msg.sendToTarget();
});
} else {
loge("Error selecting network, subscription Id is invalid " + mSubId);
}
return true;
}
/**
* Returns the title of the network obtained in the manual search.
*
* @param cellInfo contains the information of the network.
* @return Long Name if not null/empty, otherwise Short Name if not null/empty,
* else MCCMNC string.
*/
private String getNetworkTitle(CellInfo cellInfo) {
OperatorInfo ni = getOperatorInfoFromCellInfo(cellInfo);
if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
return ni.getOperatorAlphaLong();
} else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
return ni.getOperatorAlphaShort();
} else {
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
}
}
/**
* Returns the operator numeric (MCCMNC) obtained in the manual search.
*
* @param cellInfo contains the information of the network.
* @return MCCMNC string.
*/
private String getOperatorNumeric(CellInfo cellInfo) {
return getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
}
/**
* Wrap a cell info into an operator info.
*/
private OperatorInfo getOperatorInfoFromCellInfo(CellInfo cellInfo) {
OperatorInfo oi;
if (cellInfo instanceof CellInfoLte) {
CellInfoLte lte = (CellInfoLte) cellInfo;
oi = new OperatorInfo(
(String) lte.getCellIdentity().getOperatorAlphaLong(),
(String) lte.getCellIdentity().getOperatorAlphaShort(),
lte.getCellIdentity().getMobileNetworkOperator());
} else if (cellInfo instanceof CellInfoWcdma) {
CellInfoWcdma wcdma = (CellInfoWcdma) cellInfo;
oi = new OperatorInfo(
(String) wcdma.getCellIdentity().getOperatorAlphaLong(),
(String) wcdma.getCellIdentity().getOperatorAlphaShort(),
wcdma.getCellIdentity().getMobileNetworkOperator());
} else if (cellInfo instanceof CellInfoGsm) {
CellInfoGsm gsm = (CellInfoGsm) cellInfo;
oi = new OperatorInfo(
(String) gsm.getCellIdentity().getOperatorAlphaLong(),
(String) gsm.getCellIdentity().getOperatorAlphaShort(),
gsm.getCellIdentity().getMobileNetworkOperator());
} else if (cellInfo instanceof CellInfoCdma) {
CellInfoCdma cdma = (CellInfoCdma) cellInfo;
oi = new OperatorInfo(
(String) cdma.getCellIdentity().getOperatorAlphaLong(),
(String) cdma.getCellIdentity().getOperatorAlphaShort(),
"" /* operator numeric */);
} else {
oi = new OperatorInfo("", "", "");
}
return oi;
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
// No need to save instance state since it's persistent
return superState;
}
final SavedState myState = new SavedState(superState);
myState.mDialogListEntries = getEntries();
myState.mDialogListEntryValues = getEntryValues();
myState.mCellInfoList = mCellInfoList;
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
if (getEntries() == null && myState.mDialogListEntries != null) {
setEntries(myState.mDialogListEntries);
}
if (getEntryValues() == null && myState.mDialogListEntryValues != null) {
setEntryValues(myState.mDialogListEntryValues);
}
if (mCellInfoList == null && myState.mCellInfoList != null) {
mCellInfoList = myState.mCellInfoList;
}
super.onRestoreInstanceState(myState.getSuperState());
}
/**
* We save entries, entryValues and operatorInfoList into bundle.
* At onCreate of fragment, dialog will be restored if it was open. In this case,
* we need to restore entries, entryValues and operatorInfoList. Without those information,
* onPreferenceChange will fail if user select network from the dialog.
*/
private static class SavedState extends BaseSavedState {
CharSequence[] mDialogListEntries;
CharSequence[] mDialogListEntryValues;
List<CellInfo> mCellInfoList;
SavedState(Parcel source) {
super(source);
final ClassLoader boot = Object.class.getClassLoader();
mDialogListEntries = source.readCharSequenceArray();
mDialogListEntryValues = source.readCharSequenceArray();
mCellInfoList = source.readParcelableList(mCellInfoList, boot);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeCharSequenceArray(mDialogListEntries);
dest.writeCharSequenceArray(mDialogListEntryValues);
dest.writeParcelableList(mCellInfoList, flags);
}
SavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
private void logd(String msg) {
Log.d(LOG_TAG, "[NetworksList] " + msg);
}
private void loge(String msg) {
Log.e(LOG_TAG, "[NetworksList] " + msg);
}
}