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