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