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