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