Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.phone;
     18 
     19 import android.app.ProgressDialog;
     20 import android.content.Context;
     21 import android.content.DialogInterface;
     22 import android.os.AsyncResult;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.os.RemoteException;
     28 import android.preference.ListPreference;
     29 import android.preference.Preference;
     30 import android.telephony.SubscriptionManager;
     31 import android.telephony.TelephonyManager;
     32 import android.text.BidiFormatter;
     33 import android.text.TextDirectionHeuristics;
     34 import android.text.TextUtils;
     35 import android.util.AttributeSet;
     36 import android.util.Log;
     37 
     38 import com.android.internal.telephony.OperatorInfo;
     39 import com.android.internal.telephony.Phone;
     40 import com.android.internal.telephony.PhoneFactory;
     41 
     42 import java.util.List;
     43 
     44 
     45 /**
     46  * "Networks" preference in "Mobile network" settings UI for the Phone app.
     47  * It's used to manually search and choose mobile network. Enabled only when
     48  * autoSelect preference is turned off.
     49  */
     50 public class NetworkSelectListPreference extends ListPreference
     51         implements DialogInterface.OnCancelListener,
     52         Preference.OnPreferenceChangeListener{
     53 
     54     private static final String LOG_TAG = "networkSelect";
     55     private static final boolean DBG = true;
     56 
     57     private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
     58     private static final int EVENT_NETWORK_SELECTION_DONE = 200;
     59 
     60     //dialog ids
     61     private static final int DIALOG_NETWORK_SELECTION = 100;
     62     private static final int DIALOG_NETWORK_LIST_LOAD = 200;
     63 
     64     private int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
     65     private List<OperatorInfo> mOperatorInfoList;
     66     private OperatorInfo mOperatorInfo;
     67 
     68     private int mSubId;
     69     private NetworkOperators mNetworkOperators;
     70 
     71     private ProgressDialog mProgressDialog;
     72     public NetworkSelectListPreference(Context context, AttributeSet attrs) {
     73         super(context, attrs);
     74     }
     75 
     76     public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr,
     77             int defStyleRes) {
     78         super(context, attrs, defStyleAttr, defStyleRes);
     79     }
     80 
     81     @Override
     82     protected void onClick() {
     83         loadNetworksList();
     84     }
     85 
     86     private final Handler mHandler = new Handler() {
     87         @Override
     88         public void handleMessage(Message msg) {
     89             AsyncResult ar;
     90             switch (msg.what) {
     91                 case EVENT_NETWORK_SCAN_COMPLETED:
     92                     networksListLoaded((List<OperatorInfo>) msg.obj, msg.arg1);
     93                     break;
     94 
     95                 case EVENT_NETWORK_SELECTION_DONE:
     96                     if (DBG) logd("hideProgressPanel");
     97                     try {
     98                         dismissProgressBar();
     99                     } catch (IllegalArgumentException e) {
    100                     }
    101                     setEnabled(true);
    102 
    103                     ar = (AsyncResult) msg.obj;
    104                     if (ar.exception != null) {
    105                         if (DBG) logd("manual network selection: failed!");
    106                         mNetworkOperators.displayNetworkSelectionFailed(ar.exception);
    107                     } else {
    108                         if (DBG) {
    109                             logd("manual network selection: succeeded!"
    110                                     + getNetworkTitle(mOperatorInfo));
    111                         }
    112                         mNetworkOperators.displayNetworkSelectionSucceeded();
    113                     }
    114                     mNetworkOperators.getNetworkSelectionMode();
    115                     break;
    116             }
    117 
    118             return;
    119         }
    120     };
    121 
    122     INetworkQueryService mNetworkQueryService = null;
    123     /**
    124      * This implementation of INetworkQueryServiceCallback is used to receive
    125      * callback notifications from the network query service.
    126      */
    127     private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
    128 
    129         /** place the message on the looper queue upon query completion. */
    130         public void onQueryComplete(List<OperatorInfo> networkInfoArray, int status) {
    131             if (DBG) logd("notifying message loop of query completion.");
    132             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED,
    133                     status, 0, networkInfoArray);
    134             msg.sendToTarget();
    135         }
    136     };
    137 
    138     @Override
    139     //implemented for DialogInterface.OnCancelListener
    140     public void onCancel(DialogInterface dialog) {
    141         // request that the service stop the query with this callback object.
    142         try {
    143             if (mNetworkQueryService != null) {
    144                 mNetworkQueryService.stopNetworkQuery(mCallback);
    145             }
    146             // If cancelled, we query NetworkSelectMode and update states of AutoSelect button.
    147             mNetworkOperators.getNetworkSelectionMode();
    148         } catch (RemoteException e) {
    149             loge("onCancel: exception from stopNetworkQuery " + e);
    150         }
    151     }
    152 
    153     @Override
    154     protected void onDialogClosed(boolean positiveResult) {
    155         super.onDialogClosed(positiveResult);
    156 
    157         // If dismissed, we query NetworkSelectMode and update states of AutoSelect button.
    158         if (!positiveResult) {
    159             mNetworkOperators.getNetworkSelectionMode();
    160         }
    161     }
    162 
    163     /**
    164      * Return normalized carrier name given network info.
    165      *
    166      * @param ni is network information in OperatorInfo type.
    167      */
    168     public String getNormalizedCarrierName(OperatorInfo ni) {
    169         if (ni != null) {
    170             return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")";
    171         }
    172         return null;
    173     }
    174 
    175     // This method is provided besides initialize() because bind to network query service
    176     // may be binded after initialize(). In that case this method needs to be called explicitly
    177     // to set mNetworkQueryService. Otherwise mNetworkQueryService will remain null.
    178     public void setNetworkQueryService(INetworkQueryService queryService) {
    179         mNetworkQueryService = queryService;
    180     }
    181 
    182     // This initialize method needs to be called for this preference to work properly.
    183     protected void initialize(int subId, INetworkQueryService queryService,
    184             NetworkOperators networkOperators, ProgressDialog progressDialog) {
    185         mSubId = subId;
    186         mNetworkQueryService = queryService;
    187         mNetworkOperators = networkOperators;
    188         // This preference should share the same progressDialog with networkOperators category.
    189         mProgressDialog = progressDialog;
    190 
    191         if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
    192             mPhoneId = SubscriptionManager.getPhoneId(mSubId);
    193         }
    194 
    195         TelephonyManager telephonyManager = (TelephonyManager)
    196                 getContext().getSystemService(Context.TELEPHONY_SERVICE);
    197 
    198         setSummary(telephonyManager.getNetworkOperatorName());
    199 
    200         setOnPreferenceChangeListener(this);
    201     }
    202 
    203     @Override
    204     protected void onPrepareForRemoval() {
    205         destroy();
    206         super.onPrepareForRemoval();
    207     }
    208 
    209     private void destroy() {
    210         try {
    211             dismissProgressBar();
    212         } catch (IllegalArgumentException e) {
    213             loge("onDestroy: exception from dismissProgressBar " + e);
    214         }
    215 
    216         try {
    217             if (mNetworkQueryService != null) {
    218                 // used to un-register callback
    219                 mNetworkQueryService.unregisterCallback(mCallback);
    220             }
    221         } catch (RemoteException e) {
    222             loge("onDestroy: exception from unregisterCallback " + e);
    223         }
    224     }
    225 
    226     private void displayEmptyNetworkList() {
    227         String status = getContext().getResources().getString(R.string.empty_networks_list);
    228 
    229         final PhoneGlobals app = PhoneGlobals.getInstance();
    230         app.notificationMgr.postTransientNotification(
    231                 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
    232     }
    233 
    234     private void displayNetworkSelectionInProgress() {
    235         showProgressBar(DIALOG_NETWORK_SELECTION);
    236     }
    237 
    238     private void displayNetworkQueryFailed(int error) {
    239         String status = getContext().getResources().getString(R.string.network_query_error);
    240 
    241         try {
    242             dismissProgressBar();
    243         } catch (IllegalArgumentException e1) {
    244             // do nothing
    245         }
    246 
    247         final PhoneGlobals app = PhoneGlobals.getInstance();
    248         app.notificationMgr.postTransientNotification(
    249                 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
    250     }
    251 
    252     private void loadNetworksList() {
    253         if (DBG) logd("load networks list...");
    254 
    255         showProgressBar(DIALOG_NETWORK_LIST_LOAD);
    256 
    257         // delegate query request to the service.
    258         try {
    259             if (mNetworkQueryService != null) {
    260                 mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId);
    261             } else {
    262                 displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION);
    263             }
    264         } catch (RemoteException e) {
    265             loge("loadNetworksList: exception from startNetworkQuery " + e);
    266             displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION);
    267         }
    268     }
    269 
    270     /**
    271      * networksListLoaded has been rewritten to take an array of
    272      * OperatorInfo objects and a status field, instead of an
    273      * AsyncResult.  Otherwise, the functionality which takes the
    274      * OperatorInfo array and creates a list of preferences from it,
    275      * remains unchanged.
    276      */
    277     private void networksListLoaded(List<OperatorInfo> result, int status) {
    278         if (DBG) logd("networks list loaded");
    279 
    280         // used to un-register callback
    281         try {
    282             if (mNetworkQueryService != null) {
    283                 mNetworkQueryService.unregisterCallback(mCallback);
    284             }
    285         } catch (RemoteException e) {
    286             loge("networksListLoaded: exception from unregisterCallback " + e);
    287         }
    288 
    289         // update the state of the preferences.
    290         if (DBG) logd("hideProgressPanel");
    291 
    292         // Always try to dismiss the dialog because activity may
    293         // be moved to background after dialog is shown.
    294         try {
    295             dismissProgressBar();
    296         } catch (IllegalArgumentException e) {
    297             // It's not a error in following scenario, we just ignore it.
    298             // "Load list" dialog will not show, if NetworkQueryService is
    299             // connected after this activity is moved to background.
    300             loge("Fail to dismiss network load list dialog " + e);
    301         }
    302 
    303         setEnabled(true);
    304         clearList();
    305 
    306         if (status != NetworkQueryService.QUERY_OK) {
    307             if (DBG) logd("error while querying available networks");
    308             displayNetworkQueryFailed(status);
    309         } else {
    310             if (result != null) {
    311                 // create a preference for each item in the list.
    312                 // just use the operator name instead of the mildly
    313                 // confusing mcc/mnc.
    314                 mOperatorInfoList = result;
    315                 CharSequence[] networkEntries = new CharSequence[result.size()];
    316                 CharSequence[] networkEntryValues = new CharSequence[result.size()];
    317                 for (int i = 0; i < mOperatorInfoList.size(); i++) {
    318                     if (mOperatorInfoList.get(i).getState() == OperatorInfo.State.FORBIDDEN) {
    319                         networkEntries[i] = getNetworkTitle(mOperatorInfoList.get(i))
    320                             + " "
    321                             + getContext().getResources().getString(R.string.forbidden_network);
    322                     } else {
    323                         networkEntries[i] = getNetworkTitle(mOperatorInfoList.get(i));
    324                     }
    325                     networkEntryValues[i] = Integer.toString(i + 2);
    326                 }
    327 
    328                 setEntries(networkEntries);
    329                 setEntryValues(networkEntryValues);
    330 
    331                 super.onClick();
    332             } else {
    333                 displayEmptyNetworkList();
    334             }
    335         }
    336     }
    337 
    338     /**
    339      * Returns the title of the network obtained in the manual search.
    340      *
    341      * @param ni contains the information of the network.
    342      *
    343      * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
    344      * else MCCMNC string.
    345      */
    346     private String getNetworkTitle(OperatorInfo ni) {
    347         if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
    348             return ni.getOperatorAlphaLong();
    349         } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
    350             return ni.getOperatorAlphaShort();
    351         } else {
    352             BidiFormatter bidiFormatter = BidiFormatter.getInstance();
    353             return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
    354         }
    355     }
    356 
    357     private void clearList() {
    358         if (mOperatorInfoList != null) {
    359             mOperatorInfoList.clear();
    360         }
    361     }
    362 
    363     private void dismissProgressBar() {
    364         if (mProgressDialog != null && mProgressDialog.isShowing()) {
    365             mProgressDialog.dismiss();
    366         }
    367     }
    368 
    369     private void showProgressBar(int id) {
    370         if (mProgressDialog == null) {
    371             mProgressDialog = new ProgressDialog(getContext());
    372         } else {
    373             // Dismiss progress bar if it's showing now.
    374             dismissProgressBar();
    375         }
    376 
    377         if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD)) {
    378             switch (id) {
    379                 case DIALOG_NETWORK_SELECTION:
    380                     final String networkSelectMsg = getContext().getResources()
    381                             .getString(R.string.register_on_network,
    382                                     getNetworkTitle(mOperatorInfo));
    383                     mProgressDialog.setMessage(networkSelectMsg);
    384                     mProgressDialog.setCanceledOnTouchOutside(false);
    385                     mProgressDialog.setCancelable(false);
    386                     mProgressDialog.setIndeterminate(true);
    387                     break;
    388                 case DIALOG_NETWORK_LIST_LOAD:
    389                     mProgressDialog.setMessage(
    390                             getContext().getResources().getString(R.string.load_networks_progress));
    391                     mProgressDialog.setCanceledOnTouchOutside(false);
    392                     mProgressDialog.setCancelable(true);
    393                     mProgressDialog.setIndeterminate(false);
    394                     mProgressDialog.setOnCancelListener(this);
    395                     break;
    396                 default:
    397             }
    398             mProgressDialog.show();
    399         }
    400     }
    401 
    402     /**
    403      * Implemented to support onPreferenceChangeListener to look for preference
    404      * changes specifically on this button.
    405      *
    406      * @param preference is the preference to be changed, should be network select button.
    407      * @param newValue should be the value of the selection as index of operators.
    408      */
    409     public boolean onPreferenceChange(Preference preference, Object newValue) {
    410         int operatorIndex = findIndexOfValue((String) newValue);
    411         mOperatorInfo = mOperatorInfoList.get(operatorIndex);
    412 
    413         if (DBG) logd("selected network: " + getNetworkTitle(mOperatorInfo));
    414 
    415         Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
    416         Phone phone = PhoneFactory.getPhone(mPhoneId);
    417         if (phone != null) {
    418             phone.selectNetworkManually(mOperatorInfo, true, msg);
    419             displayNetworkSelectionInProgress();
    420         } else {
    421             loge("Error selecting network. phone is null.");
    422         }
    423 
    424         return true;
    425     }
    426 
    427     @Override
    428     protected Parcelable onSaveInstanceState() {
    429         final Parcelable superState = super.onSaveInstanceState();
    430         if (isPersistent()) {
    431             // No need to save instance state since it's persistent
    432             return superState;
    433         }
    434 
    435         final SavedState myState = new SavedState(superState);
    436         myState.mDialogListEntries = getEntries();
    437         myState.mDialogListEntryValues = getEntryValues();
    438         myState.mOperatorInfoList = mOperatorInfoList;
    439         return myState;
    440     }
    441 
    442     @Override
    443     protected void onRestoreInstanceState(Parcelable state) {
    444         if (state == null || !state.getClass().equals(SavedState.class)) {
    445             // Didn't save state for us in onSaveInstanceState
    446             super.onRestoreInstanceState(state);
    447             return;
    448         }
    449 
    450         SavedState myState = (SavedState) state;
    451 
    452         if (getEntries() == null && myState.mDialogListEntries != null) {
    453             setEntries(myState.mDialogListEntries);
    454         }
    455         if (getEntryValues() == null && myState.mDialogListEntryValues != null) {
    456             setEntryValues(myState.mDialogListEntryValues);
    457         }
    458         if (mOperatorInfoList == null && myState.mOperatorInfoList != null) {
    459             mOperatorInfoList = myState.mOperatorInfoList;
    460         }
    461 
    462         super.onRestoreInstanceState(myState.getSuperState());
    463     }
    464 
    465     /**
    466      *  We save entries, entryValues and operatorInfoList into bundle.
    467      *  At onCreate of fragment, dialog will be restored if it was open. In this case,
    468      *  we need to restore entries, entryValues and operatorInfoList. Without those information,
    469      *  onPreferenceChange will fail if user select network from the dialog.
    470      */
    471     private static class SavedState extends BaseSavedState {
    472         CharSequence[] mDialogListEntries;
    473         CharSequence[] mDialogListEntryValues;
    474         List<OperatorInfo> mOperatorInfoList;
    475 
    476         SavedState(Parcel source) {
    477             super(source);
    478             final ClassLoader boot = Object.class.getClassLoader();
    479             mDialogListEntries = source.readCharSequenceArray();
    480             mDialogListEntryValues = source.readCharSequenceArray();
    481             mOperatorInfoList = source.readParcelableList(mOperatorInfoList, boot);
    482         }
    483 
    484         @Override
    485         public void writeToParcel(Parcel dest, int flags) {
    486             super.writeToParcel(dest, flags);
    487             dest.writeCharSequenceArray(mDialogListEntries);
    488             dest.writeCharSequenceArray(mDialogListEntryValues);
    489             dest.writeParcelableList(mOperatorInfoList, flags);
    490         }
    491 
    492         SavedState(Parcelable superState) {
    493             super(superState);
    494         }
    495 
    496         public static final Parcelable.Creator<SavedState> CREATOR =
    497                 new Parcelable.Creator<SavedState>() {
    498                     public SavedState createFromParcel(Parcel in) {
    499                         return new SavedState(in);
    500                     }
    501 
    502                     public SavedState[] newArray(int size) {
    503                         return new SavedState[size];
    504                     }
    505                 };
    506     }
    507 
    508     private void logd(String msg) {
    509         Log.d(LOG_TAG, "[NetworksList] " + msg);
    510     }
    511 
    512     private void loge(String msg) {
    513         Log.e(LOG_TAG, "[NetworksList] " + msg);
    514     }
    515 }
    516