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