Home | History | Annotate | Download | only in network
      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.settings.network;
     18 
     19 import static android.content.Context.TELEPHONY_SERVICE;
     20 
     21 import android.app.AlertDialog;
     22 import android.app.Dialog;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.database.Cursor;
     27 import android.net.Uri;
     28 import android.os.Bundle;
     29 import android.os.PersistableBundle;
     30 import android.provider.Telephony;
     31 import android.support.annotation.VisibleForTesting;
     32 import android.support.v14.preference.MultiSelectListPreference;
     33 import android.support.v14.preference.SwitchPreference;
     34 import android.support.v7.preference.EditTextPreference;
     35 import android.support.v7.preference.ListPreference;
     36 import android.support.v7.preference.Preference;
     37 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
     38 import android.telephony.CarrierConfigManager;
     39 import android.telephony.ServiceState;
     40 import android.telephony.SubscriptionManager;
     41 import android.telephony.TelephonyManager;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 import android.view.KeyEvent;
     45 import android.view.Menu;
     46 import android.view.MenuInflater;
     47 import android.view.MenuItem;
     48 import android.view.View;
     49 import android.view.View.OnKeyListener;
     50 
     51 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     52 import com.android.internal.telephony.PhoneConstants;
     53 import com.android.internal.util.ArrayUtils;
     54 import com.android.settings.R;
     55 import com.android.settings.SettingsPreferenceFragment;
     56 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
     57 import com.android.settingslib.utils.ThreadUtils;
     58 
     59 import java.util.Arrays;
     60 import java.util.HashSet;
     61 import java.util.List;
     62 import java.util.Set;
     63 
     64 public class ApnEditor extends SettingsPreferenceFragment
     65         implements OnPreferenceChangeListener, OnKeyListener {
     66 
     67     private final static String TAG = ApnEditor.class.getSimpleName();
     68     private final static boolean VDBG = false;   // STOPSHIP if true
     69 
     70     private final static String KEY_AUTH_TYPE = "auth_type";
     71     private final static String KEY_PROTOCOL = "apn_protocol";
     72     private final static String KEY_ROAMING_PROTOCOL = "apn_roaming_protocol";
     73     private final static String KEY_CARRIER_ENABLED = "carrier_enabled";
     74     private final static String KEY_BEARER_MULTI = "bearer_multi";
     75     private final static String KEY_MVNO_TYPE = "mvno_type";
     76     private final static String KEY_PASSWORD = "apn_password";
     77 
     78     private static final int MENU_DELETE = Menu.FIRST;
     79     private static final int MENU_SAVE = Menu.FIRST + 1;
     80     private static final int MENU_CANCEL = Menu.FIRST + 2;
     81 
     82     @VisibleForTesting
     83     static String sNotSet;
     84     @VisibleForTesting
     85     EditTextPreference mName;
     86     @VisibleForTesting
     87     EditTextPreference mApn;
     88     @VisibleForTesting
     89     EditTextPreference mProxy;
     90     @VisibleForTesting
     91     EditTextPreference mPort;
     92     @VisibleForTesting
     93     EditTextPreference mUser;
     94     @VisibleForTesting
     95     EditTextPreference mServer;
     96     @VisibleForTesting
     97     EditTextPreference mPassword;
     98     @VisibleForTesting
     99     EditTextPreference mMmsc;
    100     @VisibleForTesting
    101     EditTextPreference mMcc;
    102     @VisibleForTesting
    103     EditTextPreference mMnc;
    104     @VisibleForTesting
    105     EditTextPreference mMmsProxy;
    106     @VisibleForTesting
    107     EditTextPreference mMmsPort;
    108     @VisibleForTesting
    109     ListPreference mAuthType;
    110     @VisibleForTesting
    111     EditTextPreference mApnType;
    112     @VisibleForTesting
    113     ListPreference mProtocol;
    114     @VisibleForTesting
    115     ListPreference mRoamingProtocol;
    116     @VisibleForTesting
    117     SwitchPreference mCarrierEnabled;
    118     @VisibleForTesting
    119     MultiSelectListPreference mBearerMulti;
    120     @VisibleForTesting
    121     ListPreference mMvnoType;
    122     @VisibleForTesting
    123     EditTextPreference mMvnoMatchData;
    124 
    125     @VisibleForTesting
    126     ApnData mApnData;
    127 
    128     private String mCurMnc;
    129     private String mCurMcc;
    130 
    131     private boolean mNewApn;
    132     private int mSubId;
    133     private TelephonyManager mTelephonyManager;
    134     private int mBearerInitialVal = 0;
    135     private String mMvnoTypeStr;
    136     private String mMvnoMatchDataStr;
    137     private String[] mReadOnlyApnTypes;
    138     private String[] mReadOnlyApnFields;
    139     private boolean mReadOnlyApn;
    140     private Uri mCarrierUri;
    141 
    142     /**
    143      * Standard projection for the interesting columns of a normal note.
    144      */
    145     private static final String[] sProjection = new String[] {
    146             Telephony.Carriers._ID,     // 0
    147             Telephony.Carriers.NAME,    // 1
    148             Telephony.Carriers.APN,     // 2
    149             Telephony.Carriers.PROXY,   // 3
    150             Telephony.Carriers.PORT,    // 4
    151             Telephony.Carriers.USER,    // 5
    152             Telephony.Carriers.SERVER,  // 6
    153             Telephony.Carriers.PASSWORD, // 7
    154             Telephony.Carriers.MMSC, // 8
    155             Telephony.Carriers.MCC, // 9
    156             Telephony.Carriers.MNC, // 10
    157             Telephony.Carriers.NUMERIC, // 11
    158             Telephony.Carriers.MMSPROXY,// 12
    159             Telephony.Carriers.MMSPORT, // 13
    160             Telephony.Carriers.AUTH_TYPE, // 14
    161             Telephony.Carriers.TYPE, // 15
    162             Telephony.Carriers.PROTOCOL, // 16
    163             Telephony.Carriers.CARRIER_ENABLED, // 17
    164             Telephony.Carriers.BEARER, // 18
    165             Telephony.Carriers.BEARER_BITMASK, // 19
    166             Telephony.Carriers.ROAMING_PROTOCOL, // 20
    167             Telephony.Carriers.MVNO_TYPE,   // 21
    168             Telephony.Carriers.MVNO_MATCH_DATA,  // 22
    169             Telephony.Carriers.EDITED,   // 23
    170             Telephony.Carriers.USER_EDITABLE    //24
    171     };
    172 
    173     private static final int ID_INDEX = 0;
    174     @VisibleForTesting
    175     static final int NAME_INDEX = 1;
    176     @VisibleForTesting
    177     static final int APN_INDEX = 2;
    178     private static final int PROXY_INDEX = 3;
    179     private static final int PORT_INDEX = 4;
    180     private static final int USER_INDEX = 5;
    181     private static final int SERVER_INDEX = 6;
    182     private static final int PASSWORD_INDEX = 7;
    183     private static final int MMSC_INDEX = 8;
    184     @VisibleForTesting
    185     static final int MCC_INDEX = 9;
    186     @VisibleForTesting
    187     static final int MNC_INDEX = 10;
    188     private static final int MMSPROXY_INDEX = 12;
    189     private static final int MMSPORT_INDEX = 13;
    190     private static final int AUTH_TYPE_INDEX = 14;
    191     private static final int TYPE_INDEX = 15;
    192     private static final int PROTOCOL_INDEX = 16;
    193     @VisibleForTesting
    194     static final int CARRIER_ENABLED_INDEX = 17;
    195     private static final int BEARER_INDEX = 18;
    196     private static final int BEARER_BITMASK_INDEX = 19;
    197     private static final int ROAMING_PROTOCOL_INDEX = 20;
    198     private static final int MVNO_TYPE_INDEX = 21;
    199     private static final int MVNO_MATCH_DATA_INDEX = 22;
    200     private static final int EDITED_INDEX = 23;
    201     private static final int USER_EDITABLE_INDEX = 24;
    202 
    203     @Override
    204     public void onCreate(Bundle icicle) {
    205         super.onCreate(icicle);
    206 
    207         addPreferencesFromResource(R.xml.apn_editor);
    208 
    209         sNotSet = getResources().getString(R.string.apn_not_set);
    210         mName = (EditTextPreference) findPreference("apn_name");
    211         mApn = (EditTextPreference) findPreference("apn_apn");
    212         mProxy = (EditTextPreference) findPreference("apn_http_proxy");
    213         mPort = (EditTextPreference) findPreference("apn_http_port");
    214         mUser = (EditTextPreference) findPreference("apn_user");
    215         mServer = (EditTextPreference) findPreference("apn_server");
    216         mPassword = (EditTextPreference) findPreference(KEY_PASSWORD);
    217         mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
    218         mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
    219         mMmsc = (EditTextPreference) findPreference("apn_mmsc");
    220         mMcc = (EditTextPreference) findPreference("apn_mcc");
    221         mMnc = (EditTextPreference) findPreference("apn_mnc");
    222         mApnType = (EditTextPreference) findPreference("apn_type");
    223         mAuthType = (ListPreference) findPreference(KEY_AUTH_TYPE);
    224         mProtocol = (ListPreference) findPreference(KEY_PROTOCOL);
    225         mRoamingProtocol = (ListPreference) findPreference(KEY_ROAMING_PROTOCOL);
    226         mCarrierEnabled = (SwitchPreference) findPreference(KEY_CARRIER_ENABLED);
    227         mBearerMulti = (MultiSelectListPreference) findPreference(KEY_BEARER_MULTI);
    228         mMvnoType = (ListPreference) findPreference(KEY_MVNO_TYPE);
    229         mMvnoMatchData = (EditTextPreference) findPreference("mvno_match_data");
    230 
    231         final Intent intent = getIntent();
    232         final String action = intent.getAction();
    233         mSubId = intent.getIntExtra(ApnSettings.SUB_ID,
    234                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
    235 
    236         mReadOnlyApn = false;
    237         mReadOnlyApnTypes = null;
    238         mReadOnlyApnFields = null;
    239 
    240         CarrierConfigManager configManager = (CarrierConfigManager)
    241                 getSystemService(Context.CARRIER_CONFIG_SERVICE);
    242         if (configManager != null) {
    243             PersistableBundle b = configManager.getConfig();
    244             if (b != null) {
    245                 mReadOnlyApnTypes = b.getStringArray(
    246                         CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY);
    247                 if (!ArrayUtils.isEmpty(mReadOnlyApnTypes)) {
    248                     for (String apnType : mReadOnlyApnTypes) {
    249                         Log.d(TAG, "onCreate: read only APN type: " + apnType);
    250                     }
    251                 }
    252                 mReadOnlyApnFields = b.getStringArray(
    253                         CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY);
    254             }
    255         }
    256 
    257         Uri uri = null;
    258         if (action.equals(Intent.ACTION_EDIT)) {
    259             uri = intent.getData();
    260             if (!uri.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) {
    261                 Log.e(TAG, "Edit request not for carrier table. Uri: " + uri);
    262                 finish();
    263                 return;
    264             }
    265         } else if (action.equals(Intent.ACTION_INSERT)) {
    266             mCarrierUri = intent.getData();
    267             if (!mCarrierUri.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) {
    268                 Log.e(TAG, "Insert request not for carrier table. Uri: " + mCarrierUri);
    269                 finish();
    270                 return;
    271             }
    272             mNewApn = true;
    273             mMvnoTypeStr = intent.getStringExtra(ApnSettings.MVNO_TYPE);
    274             mMvnoMatchDataStr = intent.getStringExtra(ApnSettings.MVNO_MATCH_DATA);
    275         } else {
    276             finish();
    277             return;
    278         }
    279 
    280         // Creates an ApnData to store the apn data temporary, so that we don't need the cursor to
    281         // get the apn data. The uri is null if the action is ACTION_INSERT, that mean there is no
    282         // record in the database, so create a empty ApnData to represent a empty row of database.
    283         if (uri != null) {
    284             mApnData = getApnDataFromUri(uri);
    285         } else {
    286             mApnData = new ApnData(sProjection.length);
    287         }
    288 
    289         mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
    290 
    291         boolean isUserEdited = mApnData.getInteger(EDITED_INDEX, Telephony.Carriers.USER_EDITED)
    292                 == Telephony.Carriers.USER_EDITED;
    293 
    294         Log.d(TAG, "onCreate: EDITED " + isUserEdited);
    295         // if it's not a USER_EDITED apn, check if it's read-only
    296         if (!isUserEdited && (mApnData.getInteger(USER_EDITABLE_INDEX, 1) == 0
    297                 || apnTypesMatch(mReadOnlyApnTypes, mApnData.getString(TYPE_INDEX)))) {
    298             Log.d(TAG, "onCreate: apnTypesMatch; read-only APN");
    299             mReadOnlyApn = true;
    300             disableAllFields();
    301         } else if (!ArrayUtils.isEmpty(mReadOnlyApnFields)) {
    302             disableFields(mReadOnlyApnFields);
    303         }
    304 
    305         for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
    306             getPreferenceScreen().getPreference(i).setOnPreferenceChangeListener(this);
    307         }
    308 
    309         fillUI(icicle == null);
    310     }
    311 
    312     @VisibleForTesting
    313     static String formatInteger(String value) {
    314         try {
    315             final int intValue = Integer.parseInt(value);
    316             return String.format("%d", intValue);
    317         } catch (NumberFormatException e) {
    318             return value;
    319         }
    320     }
    321 
    322     /**
    323      * Check if passed in array of APN types indicates all APN types
    324      * @param apnTypes array of APN types. "*" indicates all types.
    325      * @return true if all apn types are included in the array, false otherwise
    326      */
    327     static boolean hasAllApns(String[] apnTypes) {
    328         if (ArrayUtils.isEmpty(apnTypes)) {
    329             return false;
    330         }
    331 
    332         List apnList = Arrays.asList(apnTypes);
    333         if (apnList.contains(PhoneConstants.APN_TYPE_ALL)) {
    334             Log.d(TAG, "hasAllApns: true because apnList.contains(PhoneConstants.APN_TYPE_ALL)");
    335             return true;
    336         }
    337         for (String apn : PhoneConstants.APN_TYPES) {
    338             if (!apnList.contains(apn)) {
    339                 return false;
    340             }
    341         }
    342 
    343         Log.d(TAG, "hasAllApns: true");
    344         return true;
    345     }
    346 
    347     /**
    348      * Check if APN types overlap.
    349      * @param apnTypesArray1 array of APNs. Empty array indicates no APN type; "*" indicates all
    350      *                       types
    351      * @param apnTypes2 comma separated string of APN types. Empty string represents all types.
    352      * @return if any apn type matches return true, otherwise return false
    353      */
    354     private boolean apnTypesMatch(String[] apnTypesArray1, String apnTypes2) {
    355         if (ArrayUtils.isEmpty(apnTypesArray1)) {
    356             return false;
    357         }
    358 
    359         if (hasAllApns(apnTypesArray1) || TextUtils.isEmpty(apnTypes2)) {
    360             return true;
    361         }
    362 
    363         List apnTypesList1 = Arrays.asList(apnTypesArray1);
    364         String[] apnTypesArray2 = apnTypes2.split(",");
    365 
    366         for (String apn : apnTypesArray2) {
    367             if (apnTypesList1.contains(apn.trim())) {
    368                 Log.d(TAG, "apnTypesMatch: true because match found for " + apn.trim());
    369                 return true;
    370             }
    371         }
    372 
    373         Log.d(TAG, "apnTypesMatch: false");
    374         return false;
    375     }
    376 
    377     /**
    378      * Function to get Preference obj corresponding to an apnField
    379      * @param apnField apn field name for which pref is needed
    380      * @return Preference obj corresponding to passed in apnField
    381      */
    382     private Preference getPreferenceFromFieldName(String apnField) {
    383         switch (apnField) {
    384             case Telephony.Carriers.NAME:
    385                 return mName;
    386             case Telephony.Carriers.APN:
    387                 return mApn;
    388             case Telephony.Carriers.PROXY:
    389                 return mProxy;
    390             case Telephony.Carriers.PORT:
    391                 return mPort;
    392             case Telephony.Carriers.USER:
    393                 return mUser;
    394             case Telephony.Carriers.SERVER:
    395                 return mServer;
    396             case Telephony.Carriers.PASSWORD:
    397                 return mPassword;
    398             case Telephony.Carriers.MMSPROXY:
    399                 return mMmsProxy;
    400             case Telephony.Carriers.MMSPORT:
    401                 return mMmsPort;
    402             case Telephony.Carriers.MMSC:
    403                 return mMmsc;
    404             case Telephony.Carriers.MCC:
    405                 return mMcc;
    406             case Telephony.Carriers.MNC:
    407                 return mMnc;
    408             case Telephony.Carriers.TYPE:
    409                 return mApnType;
    410             case Telephony.Carriers.AUTH_TYPE:
    411                 return mAuthType;
    412             case Telephony.Carriers.PROTOCOL:
    413                 return mProtocol;
    414             case Telephony.Carriers.ROAMING_PROTOCOL:
    415                 return mRoamingProtocol;
    416             case Telephony.Carriers.CARRIER_ENABLED:
    417                 return mCarrierEnabled;
    418             case Telephony.Carriers.BEARER:
    419             case Telephony.Carriers.BEARER_BITMASK:
    420                 return mBearerMulti;
    421             case Telephony.Carriers.MVNO_TYPE:
    422                 return mMvnoType;
    423             case Telephony.Carriers.MVNO_MATCH_DATA:
    424                 return mMvnoMatchData;
    425         }
    426         return null;
    427     }
    428 
    429     /**
    430      * Disables given fields so that user cannot modify them
    431      *
    432      * @param apnFields fields to be disabled
    433      */
    434     private void disableFields(String[] apnFields) {
    435         for (String apnField : apnFields) {
    436             Preference preference = getPreferenceFromFieldName(apnField);
    437             if (preference != null) {
    438                 preference.setEnabled(false);
    439             }
    440         }
    441     }
    442 
    443     /**
    444      * Disables all fields so that user cannot modify the APN
    445      */
    446     private void disableAllFields() {
    447         mName.setEnabled(false);
    448         mApn.setEnabled(false);
    449         mProxy.setEnabled(false);
    450         mPort.setEnabled(false);
    451         mUser.setEnabled(false);
    452         mServer.setEnabled(false);
    453         mPassword.setEnabled(false);
    454         mMmsProxy.setEnabled(false);
    455         mMmsPort.setEnabled(false);
    456         mMmsc.setEnabled(false);
    457         mMcc.setEnabled(false);
    458         mMnc.setEnabled(false);
    459         mApnType.setEnabled(false);
    460         mAuthType.setEnabled(false);
    461         mProtocol.setEnabled(false);
    462         mRoamingProtocol.setEnabled(false);
    463         mCarrierEnabled.setEnabled(false);
    464         mBearerMulti.setEnabled(false);
    465         mMvnoType.setEnabled(false);
    466         mMvnoMatchData.setEnabled(false);
    467     }
    468 
    469     @Override
    470     public int getMetricsCategory() {
    471         return MetricsEvent.APN_EDITOR;
    472     }
    473 
    474     @VisibleForTesting
    475     void fillUI(boolean firstTime) {
    476         if (firstTime) {
    477             // Fill in all the values from the db in both text editor and summary
    478             mName.setText(mApnData.getString(NAME_INDEX));
    479             mApn.setText(mApnData.getString(APN_INDEX));
    480             mProxy.setText(mApnData.getString(PROXY_INDEX));
    481             mPort.setText(mApnData.getString(PORT_INDEX));
    482             mUser.setText(mApnData.getString(USER_INDEX));
    483             mServer.setText(mApnData.getString(SERVER_INDEX));
    484             mPassword.setText(mApnData.getString(PASSWORD_INDEX));
    485             mMmsProxy.setText(mApnData.getString(MMSPROXY_INDEX));
    486             mMmsPort.setText(mApnData.getString(MMSPORT_INDEX));
    487             mMmsc.setText(mApnData.getString(MMSC_INDEX));
    488             mMcc.setText(mApnData.getString(MCC_INDEX));
    489             mMnc.setText(mApnData.getString(MNC_INDEX));
    490             mApnType.setText(mApnData.getString(TYPE_INDEX));
    491             if (mNewApn) {
    492                 String numeric = mTelephonyManager.getSimOperator(mSubId);
    493                 // MCC is first 3 chars and then in 2 - 3 chars of MNC
    494                 if (numeric != null && numeric.length() > 4) {
    495                     // Country code
    496                     String mcc = numeric.substring(0, 3);
    497                     // Network code
    498                     String mnc = numeric.substring(3);
    499                     // Auto populate MNC and MCC for new entries, based on what SIM reports
    500                     mMcc.setText(mcc);
    501                     mMnc.setText(mnc);
    502                     mCurMnc = mnc;
    503                     mCurMcc = mcc;
    504                 }
    505             }
    506             int authVal = mApnData.getInteger(AUTH_TYPE_INDEX, -1);
    507             if (authVal != -1) {
    508                 mAuthType.setValueIndex(authVal);
    509             } else {
    510                 mAuthType.setValue(null);
    511             }
    512 
    513             mProtocol.setValue(mApnData.getString(PROTOCOL_INDEX));
    514             mRoamingProtocol.setValue(mApnData.getString(ROAMING_PROTOCOL_INDEX));
    515             mCarrierEnabled.setChecked(mApnData.getInteger(CARRIER_ENABLED_INDEX, 1) == 1);
    516             mBearerInitialVal = mApnData.getInteger(BEARER_INDEX, 0);
    517 
    518             HashSet<String> bearers = new HashSet<String>();
    519             int bearerBitmask = mApnData.getInteger(BEARER_BITMASK_INDEX, 0);
    520             if (bearerBitmask == 0) {
    521                 if (mBearerInitialVal == 0) {
    522                     bearers.add("" + 0);
    523                 }
    524             } else {
    525                 int i = 1;
    526                 while (bearerBitmask != 0) {
    527                     if ((bearerBitmask & 1) == 1) {
    528                         bearers.add("" + i);
    529                     }
    530                     bearerBitmask >>= 1;
    531                     i++;
    532                 }
    533             }
    534 
    535             if (mBearerInitialVal != 0 && bearers.contains("" + mBearerInitialVal) == false) {
    536                 // add mBearerInitialVal to bearers
    537                 bearers.add("" + mBearerInitialVal);
    538             }
    539             mBearerMulti.setValues(bearers);
    540 
    541             mMvnoType.setValue(mApnData.getString(MVNO_TYPE_INDEX));
    542             mMvnoMatchData.setEnabled(false);
    543             mMvnoMatchData.setText(mApnData.getString(MVNO_MATCH_DATA_INDEX));
    544             if (mNewApn && mMvnoTypeStr != null && mMvnoMatchDataStr != null) {
    545                 mMvnoType.setValue(mMvnoTypeStr);
    546                 mMvnoMatchData.setText(mMvnoMatchDataStr);
    547             }
    548         }
    549 
    550         mName.setSummary(checkNull(mName.getText()));
    551         mApn.setSummary(checkNull(mApn.getText()));
    552         mProxy.setSummary(checkNull(mProxy.getText()));
    553         mPort.setSummary(checkNull(mPort.getText()));
    554         mUser.setSummary(checkNull(mUser.getText()));
    555         mServer.setSummary(checkNull(mServer.getText()));
    556         mPassword.setSummary(starify(mPassword.getText()));
    557         mMmsProxy.setSummary(checkNull(mMmsProxy.getText()));
    558         mMmsPort.setSummary(checkNull(mMmsPort.getText()));
    559         mMmsc.setSummary(checkNull(mMmsc.getText()));
    560         mMcc.setSummary(formatInteger(checkNull(mMcc.getText())));
    561         mMnc.setSummary(formatInteger(checkNull(mMnc.getText())));
    562         mApnType.setSummary(checkNull(mApnType.getText()));
    563 
    564         String authVal = mAuthType.getValue();
    565         if (authVal != null) {
    566             int authValIndex = Integer.parseInt(authVal);
    567             mAuthType.setValueIndex(authValIndex);
    568 
    569             String[] values = getResources().getStringArray(R.array.apn_auth_entries);
    570             mAuthType.setSummary(values[authValIndex]);
    571         } else {
    572             mAuthType.setSummary(sNotSet);
    573         }
    574 
    575         mProtocol.setSummary(checkNull(protocolDescription(mProtocol.getValue(), mProtocol)));
    576         mRoamingProtocol.setSummary(
    577                 checkNull(protocolDescription(mRoamingProtocol.getValue(), mRoamingProtocol)));
    578         mBearerMulti.setSummary(
    579                 checkNull(bearerMultiDescription(mBearerMulti.getValues())));
    580         mMvnoType.setSummary(
    581                 checkNull(mvnoDescription(mMvnoType.getValue())));
    582         mMvnoMatchData.setSummary(checkNull(mMvnoMatchData.getText()));
    583         // allow user to edit carrier_enabled for some APN
    584         boolean ceEditable = getResources().getBoolean(R.bool.config_allow_edit_carrier_enabled);
    585         if (ceEditable) {
    586             mCarrierEnabled.setEnabled(true);
    587         } else {
    588             mCarrierEnabled.setEnabled(false);
    589         }
    590     }
    591 
    592     /**
    593      * Returns the UI choice (e.g., "IPv4/IPv6") corresponding to the given
    594      * raw value of the protocol preference (e.g., "IPV4V6"). If unknown,
    595      * return null.
    596      */
    597     private String protocolDescription(String raw, ListPreference protocol) {
    598         int protocolIndex = protocol.findIndexOfValue(raw);
    599         if (protocolIndex == -1) {
    600             return null;
    601         } else {
    602             String[] values = getResources().getStringArray(R.array.apn_protocol_entries);
    603             try {
    604                 return values[protocolIndex];
    605             } catch (ArrayIndexOutOfBoundsException e) {
    606                 return null;
    607             }
    608         }
    609     }
    610 
    611     private String bearerMultiDescription(Set<String> raw) {
    612         String[] values = getResources().getStringArray(R.array.bearer_entries);
    613         StringBuilder retVal = new StringBuilder();
    614         boolean first = true;
    615         for (String bearer : raw) {
    616             int bearerIndex = mBearerMulti.findIndexOfValue(bearer);
    617             try {
    618                 if (first) {
    619                     retVal.append(values[bearerIndex]);
    620                     first = false;
    621                 } else {
    622                     retVal.append(", " + values[bearerIndex]);
    623                 }
    624             } catch (ArrayIndexOutOfBoundsException e) {
    625                 // ignore
    626             }
    627         }
    628         String val = retVal.toString();
    629         if (!TextUtils.isEmpty(val)) {
    630             return val;
    631         }
    632         return null;
    633     }
    634 
    635     private String mvnoDescription(String newValue) {
    636         int mvnoIndex = mMvnoType.findIndexOfValue(newValue);
    637         String oldValue = mMvnoType.getValue();
    638 
    639         if (mvnoIndex == -1) {
    640             return null;
    641         } else {
    642             String[] values = getResources().getStringArray(R.array.mvno_type_entries);
    643             boolean mvnoMatchDataUneditable =
    644                     mReadOnlyApn || (mReadOnlyApnFields != null
    645                             && Arrays.asList(mReadOnlyApnFields)
    646                             .contains(Telephony.Carriers.MVNO_MATCH_DATA));
    647             mMvnoMatchData.setEnabled(!mvnoMatchDataUneditable && mvnoIndex != 0);
    648             if (newValue != null && newValue.equals(oldValue) == false) {
    649                 if (values[mvnoIndex].equals("SPN")) {
    650                     mMvnoMatchData.setText(mTelephonyManager.getSimOperatorName());
    651                 } else if (values[mvnoIndex].equals("IMSI")) {
    652                     String numeric = mTelephonyManager.getSimOperator(mSubId);
    653                     mMvnoMatchData.setText(numeric + "x");
    654                 } else if (values[mvnoIndex].equals("GID")) {
    655                     mMvnoMatchData.setText(mTelephonyManager.getGroupIdLevel1());
    656                 }
    657             }
    658 
    659             try {
    660                 return values[mvnoIndex];
    661             } catch (ArrayIndexOutOfBoundsException e) {
    662                 return null;
    663             }
    664         }
    665     }
    666 
    667     public boolean onPreferenceChange(Preference preference, Object newValue) {
    668         String key = preference.getKey();
    669         if (KEY_AUTH_TYPE.equals(key)) {
    670             try {
    671                 int index = Integer.parseInt((String) newValue);
    672                 mAuthType.setValueIndex(index);
    673 
    674                 String[] values = getResources().getStringArray(R.array.apn_auth_entries);
    675                 mAuthType.setSummary(values[index]);
    676             } catch (NumberFormatException e) {
    677                 return false;
    678             }
    679         } else if (KEY_PROTOCOL.equals(key)) {
    680             String protocol = protocolDescription((String) newValue, mProtocol);
    681             if (protocol == null) {
    682                 return false;
    683             }
    684             mProtocol.setSummary(protocol);
    685             mProtocol.setValue((String) newValue);
    686         } else if (KEY_ROAMING_PROTOCOL.equals(key)) {
    687             String protocol = protocolDescription((String) newValue, mRoamingProtocol);
    688             if (protocol == null) {
    689                 return false;
    690             }
    691             mRoamingProtocol.setSummary(protocol);
    692             mRoamingProtocol.setValue((String) newValue);
    693         } else if (KEY_BEARER_MULTI.equals(key)) {
    694             String bearer = bearerMultiDescription((Set<String>) newValue);
    695             if (bearer == null) {
    696                 return false;
    697             }
    698             mBearerMulti.setValues((Set<String>) newValue);
    699             mBearerMulti.setSummary(bearer);
    700         } else if (KEY_MVNO_TYPE.equals(key)) {
    701             String mvno = mvnoDescription((String) newValue);
    702             if (mvno == null) {
    703                 return false;
    704             }
    705             mMvnoType.setValue((String) newValue);
    706             mMvnoType.setSummary(mvno);
    707         } else if (KEY_PASSWORD.equals(key)) {
    708             mPassword.setSummary(starify(newValue != null ? String.valueOf(newValue) : ""));
    709         } else if (KEY_CARRIER_ENABLED.equals(key)) {
    710             // do nothing
    711         } else {
    712             preference.setSummary(checkNull(newValue != null ? String.valueOf(newValue) : null));
    713         }
    714 
    715         return true;
    716     }
    717 
    718     @Override
    719     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    720         super.onCreateOptionsMenu(menu, inflater);
    721         // If it's a new APN, then cancel will delete the new entry in onPause
    722         if (!mNewApn && !mReadOnlyApn) {
    723             menu.add(0, MENU_DELETE, 0, R.string.menu_delete)
    724                 .setIcon(R.drawable.ic_delete);
    725         }
    726         menu.add(0, MENU_SAVE, 0, R.string.menu_save)
    727             .setIcon(android.R.drawable.ic_menu_save);
    728         menu.add(0, MENU_CANCEL, 0, R.string.menu_cancel)
    729             .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
    730     }
    731 
    732     @Override
    733     public boolean onOptionsItemSelected(MenuItem item) {
    734         switch (item.getItemId()) {
    735             case MENU_DELETE:
    736                 deleteApn();
    737                 finish();
    738                 return true;
    739             case MENU_SAVE:
    740                 if (validateAndSaveApnData()) {
    741                     finish();
    742                 }
    743                 return true;
    744             case MENU_CANCEL:
    745                 finish();
    746                 return true;
    747             default:
    748                 return super.onOptionsItemSelected(item);
    749         }
    750     }
    751 
    752     @Override
    753     public void onViewCreated(View view, Bundle savedInstanceState) {
    754         super.onViewCreated(view, savedInstanceState);
    755         view.setOnKeyListener(this);
    756         view.setFocusableInTouchMode(true);
    757         view.requestFocus();
    758     }
    759 
    760     /**
    761      * Try to save the apn data when pressed the back button. An error message will be displayed if
    762      * the apn data is invalid.
    763      *
    764      * TODO(b/77339593): Try to keep the same behavior between back button and up navigate button.
    765      * We will save the valid apn data to the database when pressed the back button, but discard all
    766      * user changed when pressed the up navigate button.
    767      */
    768     @Override
    769     public boolean onKey(View v, int keyCode, KeyEvent event) {
    770         if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
    771         switch (keyCode) {
    772             case KeyEvent.KEYCODE_BACK: {
    773                 if (validateAndSaveApnData()) {
    774                     finish();
    775                 }
    776                 return true;
    777             }
    778         }
    779         return false;
    780     }
    781 
    782     /**
    783      * Add key, value to {@code cv} and compare the value against the value at index in
    784      * {@link #mApnData}.
    785      *
    786      * <p>
    787      * The key, value will not add to {@code cv} if value is null.
    788      *
    789      * @return true if values are different. {@code assumeDiff} indicates if values can be assumed
    790      * different in which case no comparison is needed.
    791      */
    792     boolean setStringValueAndCheckIfDiff(
    793             ContentValues cv, String key, String value, boolean assumeDiff, int index) {
    794         String valueFromLocalCache = mApnData.getString(index);
    795         if (VDBG) {
    796             Log.d(TAG, "setStringValueAndCheckIfDiff: assumeDiff: " + assumeDiff
    797                     + " key: " + key
    798                     + " value: '" + value
    799                     + "' valueFromDb: '" + valueFromLocalCache + "'");
    800         }
    801         boolean isDiff = assumeDiff
    802                 || !((TextUtils.isEmpty(value) && TextUtils.isEmpty(valueFromLocalCache))
    803                 || (value != null && value.equals(valueFromLocalCache)));
    804 
    805         if (isDiff && value != null) {
    806             cv.put(key, value);
    807         }
    808         return isDiff;
    809     }
    810 
    811     /**
    812      * Add key, value to {@code cv} and compare the value against the value at index in
    813      * {@link #mApnData}.
    814      *
    815      * @return true if values are different. {@code assumeDiff} indicates if values can be assumed
    816      * different in which case no comparison is needed.
    817      */
    818     boolean setIntValueAndCheckIfDiff(
    819             ContentValues cv, String key, int value, boolean assumeDiff, int index) {
    820         Integer valueFromLocalCache = mApnData.getInteger(index);
    821         if (VDBG) {
    822             Log.d(TAG, "setIntValueAndCheckIfDiff: assumeDiff: " + assumeDiff
    823                     + " key: " + key
    824                     + " value: '" + value
    825                     + "' valueFromDb: '" + valueFromLocalCache + "'");
    826         }
    827 
    828         boolean isDiff = assumeDiff || value != valueFromLocalCache;
    829         if (isDiff) {
    830             cv.put(key, value);
    831         }
    832         return isDiff;
    833     }
    834 
    835     /**
    836      * Validates the apn data and save it to the database if it's valid.
    837      *
    838      * <p>
    839      * A dialog with error message will be displayed if the APN data is invalid.
    840      *
    841      * @return true if there is no error
    842      */
    843     @VisibleForTesting
    844     boolean validateAndSaveApnData() {
    845         // Nothing to do if it's a read only APN
    846         if (mReadOnlyApn) {
    847             return true;
    848         }
    849 
    850         String name = checkNotSet(mName.getText());
    851         String apn = checkNotSet(mApn.getText());
    852         String mcc = checkNotSet(mMcc.getText());
    853         String mnc = checkNotSet(mMnc.getText());
    854 
    855         String errorMsg = validateApnData();
    856         if (errorMsg != null) {
    857             showError();
    858             return false;
    859         }
    860 
    861         ContentValues values = new ContentValues();
    862         // call update() if it's a new APN. If not, check if any field differs from the db value;
    863         // if any diff is found update() should be called
    864         boolean callUpdate = mNewApn;
    865         callUpdate = setStringValueAndCheckIfDiff(values,
    866                 Telephony.Carriers.NAME,
    867                 name,
    868                 callUpdate,
    869                 NAME_INDEX);
    870 
    871         callUpdate = setStringValueAndCheckIfDiff(values,
    872                 Telephony.Carriers.APN,
    873                 apn,
    874                 callUpdate,
    875                 APN_INDEX);
    876 
    877         callUpdate = setStringValueAndCheckIfDiff(values,
    878                 Telephony.Carriers.PROXY,
    879                 checkNotSet(mProxy.getText()),
    880                 callUpdate,
    881                 PROXY_INDEX);
    882 
    883         callUpdate = setStringValueAndCheckIfDiff(values,
    884                 Telephony.Carriers.PORT,
    885                 checkNotSet(mPort.getText()),
    886                 callUpdate,
    887                 PORT_INDEX);
    888 
    889         callUpdate = setStringValueAndCheckIfDiff(values,
    890                 Telephony.Carriers.MMSPROXY,
    891                 checkNotSet(mMmsProxy.getText()),
    892                 callUpdate,
    893                 MMSPROXY_INDEX);
    894 
    895         callUpdate = setStringValueAndCheckIfDiff(values,
    896                 Telephony.Carriers.MMSPORT,
    897                 checkNotSet(mMmsPort.getText()),
    898                 callUpdate,
    899                 MMSPORT_INDEX);
    900 
    901         callUpdate = setStringValueAndCheckIfDiff(values,
    902                 Telephony.Carriers.USER,
    903                 checkNotSet(mUser.getText()),
    904                 callUpdate,
    905                 USER_INDEX);
    906 
    907         callUpdate = setStringValueAndCheckIfDiff(values,
    908                 Telephony.Carriers.SERVER,
    909                 checkNotSet(mServer.getText()),
    910                 callUpdate,
    911                 SERVER_INDEX);
    912 
    913         callUpdate = setStringValueAndCheckIfDiff(values,
    914                 Telephony.Carriers.PASSWORD,
    915                 checkNotSet(mPassword.getText()),
    916                 callUpdate,
    917                 PASSWORD_INDEX);
    918 
    919         callUpdate = setStringValueAndCheckIfDiff(values,
    920                 Telephony.Carriers.MMSC,
    921                 checkNotSet(mMmsc.getText()),
    922                 callUpdate,
    923                 MMSC_INDEX);
    924 
    925         String authVal = mAuthType.getValue();
    926         if (authVal != null) {
    927             callUpdate = setIntValueAndCheckIfDiff(values,
    928                     Telephony.Carriers.AUTH_TYPE,
    929                     Integer.parseInt(authVal),
    930                     callUpdate,
    931                     AUTH_TYPE_INDEX);
    932         }
    933 
    934         callUpdate = setStringValueAndCheckIfDiff(values,
    935                 Telephony.Carriers.PROTOCOL,
    936                 checkNotSet(mProtocol.getValue()),
    937                 callUpdate,
    938                 PROTOCOL_INDEX);
    939 
    940         callUpdate = setStringValueAndCheckIfDiff(values,
    941                 Telephony.Carriers.ROAMING_PROTOCOL,
    942                 checkNotSet(mRoamingProtocol.getValue()),
    943                 callUpdate,
    944                 ROAMING_PROTOCOL_INDEX);
    945 
    946         callUpdate = setStringValueAndCheckIfDiff(values,
    947                 Telephony.Carriers.TYPE,
    948                 checkNotSet(getUserEnteredApnType()),
    949                 callUpdate,
    950                 TYPE_INDEX);
    951 
    952         callUpdate = setStringValueAndCheckIfDiff(values,
    953                 Telephony.Carriers.MCC,
    954                 mcc,
    955                 callUpdate,
    956                 MCC_INDEX);
    957 
    958         callUpdate = setStringValueAndCheckIfDiff(values,
    959                 Telephony.Carriers.MNC,
    960                 mnc,
    961                 callUpdate,
    962                 MNC_INDEX);
    963 
    964         values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
    965 
    966         if (mCurMnc != null && mCurMcc != null) {
    967             if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
    968                 values.put(Telephony.Carriers.CURRENT, 1);
    969             }
    970         }
    971 
    972         Set<String> bearerSet = mBearerMulti.getValues();
    973         int bearerBitmask = 0;
    974         for (String bearer : bearerSet) {
    975             if (Integer.parseInt(bearer) == 0) {
    976                 bearerBitmask = 0;
    977                 break;
    978             } else {
    979                 bearerBitmask |= ServiceState.getBitmaskForTech(Integer.parseInt(bearer));
    980             }
    981         }
    982         callUpdate = setIntValueAndCheckIfDiff(values,
    983                 Telephony.Carriers.BEARER_BITMASK,
    984                 bearerBitmask,
    985                 callUpdate,
    986                 BEARER_BITMASK_INDEX);
    987 
    988         int bearerVal;
    989         if (bearerBitmask == 0 || mBearerInitialVal == 0) {
    990             bearerVal = 0;
    991         } else if (ServiceState.bitmaskHasTech(bearerBitmask, mBearerInitialVal)) {
    992             bearerVal = mBearerInitialVal;
    993         } else {
    994             // bearer field was being used but bitmask has changed now and does not include the
    995             // initial bearer value -- setting bearer to 0 but maybe better behavior is to choose a
    996             // random tech from the new bitmask??
    997             bearerVal = 0;
    998         }
    999         callUpdate = setIntValueAndCheckIfDiff(values,
   1000                 Telephony.Carriers.BEARER,
   1001                 bearerVal,
   1002                 callUpdate,
   1003                 BEARER_INDEX);
   1004 
   1005         callUpdate = setStringValueAndCheckIfDiff(values,
   1006                 Telephony.Carriers.MVNO_TYPE,
   1007                 checkNotSet(mMvnoType.getValue()),
   1008                 callUpdate,
   1009                 MVNO_TYPE_INDEX);
   1010 
   1011         callUpdate = setStringValueAndCheckIfDiff(values,
   1012                 Telephony.Carriers.MVNO_MATCH_DATA,
   1013                 checkNotSet(mMvnoMatchData.getText()),
   1014                 callUpdate,
   1015                 MVNO_MATCH_DATA_INDEX);
   1016 
   1017         callUpdate = setIntValueAndCheckIfDiff(values,
   1018                 Telephony.Carriers.CARRIER_ENABLED,
   1019                 mCarrierEnabled.isChecked() ? 1 : 0,
   1020                 callUpdate,
   1021                 CARRIER_ENABLED_INDEX);
   1022 
   1023         values.put(Telephony.Carriers.EDITED, Telephony.Carriers.USER_EDITED);
   1024 
   1025         if (callUpdate) {
   1026             final Uri uri = mApnData.getUri() == null ? mCarrierUri : mApnData.getUri();
   1027             updateApnDataToDatabase(uri, values);
   1028         } else {
   1029             if (VDBG) Log.d(TAG, "validateAndSaveApnData: not calling update()");
   1030         }
   1031 
   1032         return true;
   1033     }
   1034 
   1035     private void updateApnDataToDatabase(Uri uri, ContentValues values) {
   1036         ThreadUtils.postOnBackgroundThread(() -> {
   1037             if (uri.equals(mCarrierUri)) {
   1038                 // Add a new apn to the database
   1039                 final Uri newUri = getContentResolver().insert(mCarrierUri, values);
   1040                 if (newUri == null) {
   1041                     Log.e(TAG, "Can't add a new apn to database " + mCarrierUri);
   1042                 }
   1043             } else {
   1044                 // Update the existing apn
   1045                 getContentResolver().update(
   1046                         uri, values, null /* where */, null /* selection Args */);
   1047             }
   1048         });
   1049     }
   1050 
   1051     /**
   1052      * Validates whether the apn data is valid.
   1053      *
   1054      * @return An error message if the apn data is invalid, otherwise return null.
   1055      */
   1056     @VisibleForTesting
   1057     String validateApnData() {
   1058         String errorMsg = null;
   1059 
   1060         String name = checkNotSet(mName.getText());
   1061         String apn = checkNotSet(mApn.getText());
   1062         String mcc = checkNotSet(mMcc.getText());
   1063         String mnc = checkNotSet(mMnc.getText());
   1064 
   1065         if (TextUtils.isEmpty(name)) {
   1066             errorMsg = getResources().getString(R.string.error_name_empty);
   1067         } else if (TextUtils.isEmpty(apn)) {
   1068             errorMsg = getResources().getString(R.string.error_apn_empty);
   1069         } else if (mcc == null || mcc.length() != 3) {
   1070             errorMsg = getResources().getString(R.string.error_mcc_not3);
   1071         } else if ((mnc == null || (mnc.length() & 0xFFFE) != 2)) {
   1072             errorMsg = getResources().getString(R.string.error_mnc_not23);
   1073         }
   1074 
   1075         if (errorMsg == null) {
   1076             // if carrier does not allow editing certain apn types, make sure type does not include
   1077             // those
   1078             if (!ArrayUtils.isEmpty(mReadOnlyApnTypes)
   1079                     && apnTypesMatch(mReadOnlyApnTypes, getUserEnteredApnType())) {
   1080                 StringBuilder stringBuilder = new StringBuilder();
   1081                 for (String type : mReadOnlyApnTypes) {
   1082                     stringBuilder.append(type).append(", ");
   1083                     Log.d(TAG, "validateApnData: appending type: " + type);
   1084                 }
   1085                 // remove last ", "
   1086                 if (stringBuilder.length() >= 2) {
   1087                     stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
   1088                 }
   1089                 errorMsg = String.format(getResources().getString(R.string.error_adding_apn_type),
   1090                         stringBuilder);
   1091             }
   1092         }
   1093 
   1094         return errorMsg;
   1095     }
   1096 
   1097     @VisibleForTesting
   1098     void showError() {
   1099         ErrorDialog.showError(this);
   1100     }
   1101 
   1102     private void deleteApn() {
   1103         if (mApnData.getUri() != null) {
   1104             getContentResolver().delete(mApnData.getUri(), null, null);
   1105             mApnData = new ApnData(sProjection.length);
   1106         }
   1107     }
   1108 
   1109     private String starify(String value) {
   1110         if (value == null || value.length() == 0) {
   1111             return sNotSet;
   1112         } else {
   1113             char[] password = new char[value.length()];
   1114             for (int i = 0; i < password.length; i++) {
   1115                 password[i] = '*';
   1116             }
   1117             return new String(password);
   1118         }
   1119     }
   1120 
   1121     /**
   1122      * Returns {@link #sNotSet} if the given string {@code value} is null or empty. The string
   1123      * {@link #sNotSet} typically used as the default display when an entry in the preference is
   1124      * null or empty.
   1125      */
   1126     private String checkNull(String value) {
   1127         return TextUtils.isEmpty(value) ? sNotSet : value;
   1128     }
   1129 
   1130     /**
   1131      * Returns null if the given string {@code value} equals to {@link #sNotSet}. This method
   1132      * should be used when convert a string value from preference to database.
   1133      */
   1134     private String checkNotSet(String value) {
   1135         return sNotSet.equals(value) ? null : value;
   1136     }
   1137 
   1138     private String getUserEnteredApnType() {
   1139         // if user has not specified a type, map it to "ALL APN TYPES THAT ARE NOT READ-ONLY"
   1140         String userEnteredApnType = mApnType.getText();
   1141         if (userEnteredApnType != null) userEnteredApnType = userEnteredApnType.trim();
   1142         if ((TextUtils.isEmpty(userEnteredApnType)
   1143                 || PhoneConstants.APN_TYPE_ALL.equals(userEnteredApnType))
   1144                 && !ArrayUtils.isEmpty(mReadOnlyApnTypes)) {
   1145             StringBuilder editableApnTypes = new StringBuilder();
   1146             List<String> readOnlyApnTypes = Arrays.asList(mReadOnlyApnTypes);
   1147             boolean first = true;
   1148             for (String apnType : PhoneConstants.APN_TYPES) {
   1149                 // add APN type if it is not read-only and is not wild-cardable
   1150                 if (!readOnlyApnTypes.contains(apnType)
   1151                         && !apnType.equals(PhoneConstants.APN_TYPE_IA)
   1152                         && !apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY)) {
   1153                     if (first) {
   1154                         first = false;
   1155                     } else {
   1156                         editableApnTypes.append(",");
   1157                     }
   1158                     editableApnTypes.append(apnType);
   1159                 }
   1160             }
   1161             userEnteredApnType = editableApnTypes.toString();
   1162             Log.d(TAG, "getUserEnteredApnType: changed apn type to editable apn types: "
   1163                     + userEnteredApnType);
   1164         }
   1165 
   1166         return userEnteredApnType;
   1167     }
   1168 
   1169     public static class ErrorDialog extends InstrumentedDialogFragment {
   1170 
   1171         public static void showError(ApnEditor editor) {
   1172             ErrorDialog dialog = new ErrorDialog();
   1173             dialog.setTargetFragment(editor, 0);
   1174             dialog.show(editor.getFragmentManager(), "error");
   1175         }
   1176 
   1177         @Override
   1178         public Dialog onCreateDialog(Bundle savedInstanceState) {
   1179             String msg = ((ApnEditor) getTargetFragment()).validateApnData();
   1180 
   1181             return new AlertDialog.Builder(getContext())
   1182                     .setTitle(R.string.error_title)
   1183                     .setPositiveButton(android.R.string.ok, null)
   1184                     .setMessage(msg)
   1185                     .create();
   1186         }
   1187 
   1188         @Override
   1189         public int getMetricsCategory() {
   1190             return MetricsEvent.DIALOG_APN_EDITOR_ERROR;
   1191         }
   1192     }
   1193 
   1194     @VisibleForTesting
   1195     ApnData getApnDataFromUri(Uri uri) {
   1196         ApnData apnData = null;
   1197         try (Cursor cursor = getContentResolver().query(
   1198                 uri,
   1199                 sProjection,
   1200                 null /* selection */,
   1201                 null /* selectionArgs */,
   1202                 null /* sortOrder */)) {
   1203             if (cursor != null) {
   1204                 cursor.moveToFirst();
   1205                 apnData = new ApnData(uri, cursor);
   1206             }
   1207         }
   1208 
   1209         if (apnData == null) {
   1210             Log.d(TAG, "Can't get apnData from Uri " + uri);
   1211         }
   1212 
   1213         return apnData;
   1214     }
   1215 
   1216     @VisibleForTesting
   1217     static class ApnData {
   1218         /**
   1219          * The uri correspond to a database row of the apn data. This should be null if the apn
   1220          * is not in the database.
   1221          */
   1222         Uri mUri;
   1223 
   1224         /** Each element correspond to a column of the database row. */
   1225         Object[] mData;
   1226 
   1227         ApnData(int numberOfField) {
   1228             mData = new Object[numberOfField];
   1229         }
   1230 
   1231         ApnData(Uri uri, Cursor cursor) {
   1232             mUri = uri;
   1233             mData = new Object[cursor.getColumnCount()];
   1234             for (int i = 0; i < mData.length; i++) {
   1235                 switch (cursor.getType(i)) {
   1236                     case Cursor.FIELD_TYPE_FLOAT:
   1237                         mData[i] = cursor.getFloat(i);
   1238                         break;
   1239                     case Cursor.FIELD_TYPE_INTEGER:
   1240                         mData[i] = cursor.getInt(i);
   1241                         break;
   1242                     case Cursor.FIELD_TYPE_STRING:
   1243                         mData[i] = cursor.getString(i);
   1244                         break;
   1245                     case Cursor.FIELD_TYPE_BLOB:
   1246                         mData[i] = cursor.getBlob(i);
   1247                         break;
   1248                     default:
   1249                         mData[i] = null;
   1250                 }
   1251             }
   1252         }
   1253 
   1254         Uri getUri() {
   1255             return mUri;
   1256         }
   1257 
   1258         void setUri(Uri uri) {
   1259             mUri = uri;
   1260         }
   1261 
   1262         Integer getInteger(int index) {
   1263             return (Integer) mData[index];
   1264         }
   1265 
   1266         Integer getInteger(int index, Integer defaultValue) {
   1267             Integer val = getInteger(index);
   1268             return val == null ? defaultValue : val;
   1269         }
   1270 
   1271         String getString(int index) {
   1272             return (String) mData[index];
   1273         }
   1274     }
   1275 }
   1276