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