OSDN Git Service

Merge changes from topic "am-e8b13d24-dee0-4f84-89d4-8a7d110f6ec1" into oc-dev am...
[android-x86/packages-apps-Settings.git] / src / com / android / settings / ApnEditor.java
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;
18
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Resources;
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.v14.preference.MultiSelectListPreference;
32 import android.support.v14.preference.SwitchPreference;
33 import android.support.v7.preference.EditTextPreference;
34 import android.support.v7.preference.ListPreference;
35 import android.support.v7.preference.Preference;
36 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
37 import android.telephony.CarrierConfigManager;
38 import android.telephony.ServiceState;
39 import android.telephony.SubscriptionManager;
40 import android.telephony.TelephonyManager;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.view.KeyEvent;
44 import android.view.Menu;
45 import android.view.MenuInflater;
46 import android.view.MenuItem;
47 import android.view.View;
48 import android.view.View.OnKeyListener;
49
50 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
51 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
52 import com.android.internal.telephony.PhoneConstants;
53 import com.android.internal.util.ArrayUtils;
54
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Set;
60
61 import static android.app.Activity.RESULT_OK;
62 import static android.content.Context.TELEPHONY_SERVICE;
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 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 }