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