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