1 /*
2  * Copyright (C) 2015 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.messaging.ui.appsettings;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.content.ContentValues;
22 import android.content.Intent;
23 import android.content.SharedPreferences;
24 import android.database.Cursor;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.preference.EditTextPreference;
29 import android.preference.Preference;
30 import android.preference.PreferenceFragment;
31 import android.provider.Telephony;
32 import androidx.core.app.NavUtils;
33 import android.view.KeyEvent;
34 import android.view.Menu;
35 import android.view.MenuInflater;
36 import android.view.MenuItem;
37 
38 import com.android.messaging.R;
39 import com.android.messaging.datamodel.data.ParticipantData;
40 import com.android.messaging.sms.ApnDatabase;
41 import com.android.messaging.sms.BugleApnSettingsLoader;
42 import com.android.messaging.ui.BugleActionBarActivity;
43 import com.android.messaging.ui.UIIntents;
44 import com.android.messaging.util.PhoneUtils;
45 
46 public class ApnEditorActivity extends BugleActionBarActivity {
47     private static final int ERROR_DIALOG_ID = 0;
48     private static final String ERROR_MESSAGE_KEY = "error_msg";
49     private ApnEditorFragment mApnEditorFragment;
50 
51     @Override
onCreate(Bundle savedInstanceState)52     protected void onCreate(Bundle savedInstanceState) {
53         super.onCreate(savedInstanceState);
54 
55         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
56 
57         // Display the fragment as the main content.
58         mApnEditorFragment = new ApnEditorFragment();
59         mApnEditorFragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
60                 ParticipantData.DEFAULT_SELF_SUB_ID));
61         getFragmentManager().beginTransaction()
62             .replace(android.R.id.content, mApnEditorFragment)
63             .commit();
64     }
65 
66     @Override
onOptionsItemSelected(final MenuItem item)67     public boolean onOptionsItemSelected(final MenuItem item) {
68         switch (item.getItemId()) {
69             case android.R.id.home:
70                 NavUtils.navigateUpFromSameTask(this);
71                 return true;
72         }
73         return super.onOptionsItemSelected(item);
74     }
75 
76     @Override
onCreateDialog(int id, Bundle args)77     protected Dialog onCreateDialog(int id, Bundle args) {
78 
79         if (id == ERROR_DIALOG_ID) {
80             String msg = args.getString(ERROR_MESSAGE_KEY);
81 
82             return new AlertDialog.Builder(this)
83                 .setPositiveButton(android.R.string.ok, null)
84                 .setMessage(msg)
85                 .create();
86         }
87 
88         return super.onCreateDialog(id);
89     }
90 
91     @Override
onKeyDown(int keyCode, KeyEvent event)92     public boolean onKeyDown(int keyCode, KeyEvent event) {
93         switch (keyCode) {
94             case KeyEvent.KEYCODE_BACK: {
95                 if (mApnEditorFragment.validateAndSave(false)) {
96                     finish();
97                 }
98                 return true;
99             }
100         }
101         return super.onKeyDown(keyCode, event);
102     }
103 
104     @Override
onPrepareDialog(int id, Dialog dialog, Bundle args)105     protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
106         super.onPrepareDialog(id, dialog);
107 
108         if (id == ERROR_DIALOG_ID) {
109             final String msg = args.getString(ERROR_MESSAGE_KEY);
110 
111             if (msg != null) {
112                 ((AlertDialog) dialog).setMessage(msg);
113             }
114         }
115     }
116 
117     public static class ApnEditorFragment extends PreferenceFragment implements
118         SharedPreferences.OnSharedPreferenceChangeListener {
119 
120         private static final String SAVED_POS = "pos";
121 
122         private static final int MENU_DELETE = Menu.FIRST;
123         private static final int MENU_SAVE = Menu.FIRST + 1;
124         private static final int MENU_CANCEL = Menu.FIRST + 2;
125 
126         private EditTextPreference mMmsProxy;
127         private EditTextPreference mMmsPort;
128         private EditTextPreference mName;
129         private EditTextPreference mMmsc;
130         private EditTextPreference mMcc;
131         private EditTextPreference mMnc;
132         private static String sNotSet;
133 
134         private String mCurMnc;
135         private String mCurMcc;
136 
137         private Cursor mCursor;
138         private boolean mNewApn;
139         private boolean mFirstTime;
140         private String mCurrentId;
141 
142         private int mSubId;
143 
144         /**
145          * Standard projection for the interesting columns of a normal note.
146          */
147         private static final String[] sProjection = new String[] {
148             Telephony.Carriers._ID,         // 0
149             Telephony.Carriers.NAME,        // 1
150             Telephony.Carriers.MMSC,        // 2
151             Telephony.Carriers.MCC,         // 3
152             Telephony.Carriers.MNC,         // 4
153             Telephony.Carriers.NUMERIC,     // 5
154             Telephony.Carriers.MMSPROXY,    // 6
155             Telephony.Carriers.MMSPORT,     // 7
156             Telephony.Carriers.TYPE,        // 8
157         };
158 
159         private static final int ID_INDEX = 0;
160         private static final int NAME_INDEX = 1;
161         private static final int MMSC_INDEX = 2;
162         private static final int MCC_INDEX = 3;
163         private static final int MNC_INDEX = 4;
164         private static final int NUMERIC_INDEX = 5;
165         private static final int MMSPROXY_INDEX = 6;
166         private static final int MMSPORT_INDEX = 7;
167         private static final int TYPE_INDEX = 8;
168 
169         private SQLiteDatabase mDatabase;
170 
171         @Override
onCreate(Bundle icicle)172         public void onCreate(Bundle icicle) {
173             super.onCreate(icicle);
174 
175         }
176 
177         @Override
onActivityCreated(Bundle savedInstanceState)178         public void onActivityCreated(Bundle savedInstanceState) {
179             super.onActivityCreated(savedInstanceState);
180 
181             addPreferencesFromResource(R.xml.apn_editor);
182 
183             setHasOptionsMenu(true);
184 
185             sNotSet = getResources().getString(R.string.apn_not_set);
186             mName = (EditTextPreference) findPreference("apn_name");
187             mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
188             mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
189             mMmsc = (EditTextPreference) findPreference("apn_mmsc");
190             mMcc = (EditTextPreference) findPreference("apn_mcc");
191             mMnc = (EditTextPreference) findPreference("apn_mnc");
192 
193             final Intent intent = getActivity().getIntent();
194 
195             mFirstTime = savedInstanceState == null;
196             mCurrentId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_APN_ROW_ID);
197             mNewApn = mCurrentId == null;
198 
199             mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
200 
201             if (mNewApn) {
202                 fillUi();
203             } else {
204                 // Do initial query not on the UI thread
205                 new AsyncTask<Void, Void, Void>() {
206                     @Override
207                     protected Void doInBackground(Void... params) {
208                         if (mCurrentId != null) {
209                             String selection = Telephony.Carriers._ID + " =?";
210                             String[] selectionArgs = new String[]{ mCurrentId };
211                             mCursor = mDatabase.query(ApnDatabase.APN_TABLE, sProjection, selection,
212                                     selectionArgs, null, null, null, null);
213                         }
214                         return null;
215                     }
216 
217                     @Override
218                     protected void onPostExecute(Void result) {
219                         if (mCursor == null) {
220                             getActivity().finish();
221                             return;
222                         }
223                         mCursor.moveToFirst();
224 
225                         fillUi();
226                     }
227                 }.execute((Void) null);
228             }
229         }
230 
231         @Override
onDestroy()232         public void onDestroy() {
233             super.onDestroy();
234             if (mCursor != null) {
235                 mCursor.close();
236                 mCursor = null;
237             }
238         }
239 
240         @Override
onResume()241         public void onResume() {
242             super.onResume();
243             getPreferenceScreen().getSharedPreferences()
244             .registerOnSharedPreferenceChangeListener(this);
245         }
246 
247         @Override
onPause()248         public void onPause() {
249             getPreferenceScreen().getSharedPreferences()
250             .unregisterOnSharedPreferenceChangeListener(this);
251             super.onPause();
252         }
253 
setSubId(final int subId)254         public void setSubId(final int subId) {
255             mSubId = subId;
256         }
257 
fillUi()258         private void fillUi() {
259             if (mNewApn) {
260                 mMcc.setText(null);
261                 mMnc.setText(null);
262                 String numeric = PhoneUtils.get(mSubId).getSimOperatorNumeric();
263                 // MCC is first 3 chars and then in 2 - 3 chars of MNC
264                 if (numeric != null && numeric.length() > 4) {
265                     // Country code
266                     String mcc = numeric.substring(0, 3);
267                     // Network code
268                     String mnc = numeric.substring(3);
269                     // Auto populate MNC and MCC for new entries, based on what SIM reports
270                     mMcc.setText(mcc);
271                     mMnc.setText(mnc);
272                     mCurMnc = mnc;
273                     mCurMcc = mcc;
274                 }
275                 mName.setText(null);
276                 mMmsProxy.setText(null);
277                 mMmsPort.setText(null);
278                 mMmsc.setText(null);
279             } else if (mFirstTime) {
280                 mFirstTime = false;
281                 // Fill in all the values from the db in both text editor and summary
282                 mName.setText(mCursor.getString(NAME_INDEX));
283                 mMmsProxy.setText(mCursor.getString(MMSPROXY_INDEX));
284                 mMmsPort.setText(mCursor.getString(MMSPORT_INDEX));
285                 mMmsc.setText(mCursor.getString(MMSC_INDEX));
286                 mMcc.setText(mCursor.getString(MCC_INDEX));
287                 mMnc.setText(mCursor.getString(MNC_INDEX));
288             }
289 
290             mName.setSummary(checkNull(mName.getText()));
291             mMmsProxy.setSummary(checkNull(mMmsProxy.getText()));
292             mMmsPort.setSummary(checkNull(mMmsPort.getText()));
293             mMmsc.setSummary(checkNull(mMmsc.getText()));
294             mMcc.setSummary(checkNull(mMcc.getText()));
295             mMnc.setSummary(checkNull(mMnc.getText()));
296         }
297 
298         @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)299         public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
300             super.onCreateOptionsMenu(menu, inflater);
301             // If it's a new APN, then cancel will delete the new entry in onPause
302             if (!mNewApn) {
303                 menu.add(0, MENU_DELETE, 0, R.string.menu_delete_apn)
304                     .setIcon(R.drawable.ic_delete_small_dark);
305             }
306             menu.add(0, MENU_SAVE, 0, R.string.menu_save_apn)
307                 .setIcon(android.R.drawable.ic_menu_save);
308             menu.add(0, MENU_CANCEL, 0, R.string.menu_discard_apn_change)
309                 .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
310         }
311 
312         @Override
onOptionsItemSelected(MenuItem item)313         public boolean onOptionsItemSelected(MenuItem item) {
314             switch (item.getItemId()) {
315                 case MENU_DELETE:
316                     deleteApn();
317                     return true;
318 
319                 case MENU_SAVE:
320                     if (validateAndSave(false)) {
321                         getActivity().finish();
322                     }
323                     return true;
324 
325                 case MENU_CANCEL:
326                     getActivity().finish();
327                     return true;
328 
329                 case android.R.id.home:
330                     getActivity().onBackPressed();
331                     return true;
332             }
333             return super.onOptionsItemSelected(item);
334         }
335 
336         @Override
onSaveInstanceState(Bundle icicle)337         public void onSaveInstanceState(Bundle icicle) {
338             super.onSaveInstanceState(icicle);
339             if (validateAndSave(true) && mCursor != null) {
340                 icicle.putInt(SAVED_POS, mCursor.getInt(ID_INDEX));
341             }
342         }
343 
344         /**
345          * Check the key fields' validity and save if valid.
346          * @param force save even if the fields are not valid, if the app is
347          *        being suspended
348          * @return true if the data was saved
349          */
validateAndSave(boolean force)350         private boolean validateAndSave(boolean force) {
351             final String name = checkNotSet(mName.getText());
352             final String mcc = checkNotSet(mMcc.getText());
353             final String mnc = checkNotSet(mMnc.getText());
354 
355             if (getErrorMsg() != null && !force) {
356                 final Bundle bundle = new Bundle();
357                 bundle.putString(ERROR_MESSAGE_KEY, getErrorMsg());
358                 getActivity().showDialog(ERROR_DIALOG_ID, bundle);
359                 return false;
360             }
361 
362             // Make database changes not on the UI thread
363             new AsyncTask<Void, Void, Void>() {
364                 @Override
365                 protected Void doInBackground(Void... params) {
366                     ContentValues values = new ContentValues();
367 
368                     // Add a dummy name "Untitled", if the user exits the screen without adding a
369                     // name but entered other information worth keeping.
370                     values.put(Telephony.Carriers.NAME, name.length() < 1 ?
371                             getResources().getString(R.string.untitled_apn) : name);
372                     values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
373                     values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
374                     values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));
375 
376                     values.put(Telephony.Carriers.TYPE, BugleApnSettingsLoader.APN_TYPE_MMS);
377 
378                     values.put(Telephony.Carriers.MCC, mcc);
379                     values.put(Telephony.Carriers.MNC, mnc);
380 
381                     values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
382 
383                     if (mCurMnc != null && mCurMcc != null) {
384                         if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
385                             values.put(Telephony.Carriers.CURRENT, 1);
386                         }
387                     }
388 
389                     if (mNewApn) {
390                         mDatabase.insert(ApnDatabase.APN_TABLE, null, values);
391                     } else {
392                         // update the APN
393                         String selection = Telephony.Carriers._ID + " =?";
394                         String[] selectionArgs = new String[]{ mCurrentId };
395                         int updated = mDatabase.update(ApnDatabase.APN_TABLE, values,
396                                 selection, selectionArgs);
397                     }
398                     return null;
399                 }
400             }.execute((Void) null);
401 
402             return true;
403         }
404 
deleteApn()405         private void deleteApn() {
406             // Make database changes not on the UI thread
407             new AsyncTask<Void, Void, Void>() {
408                 @Override
409                 protected Void doInBackground(Void... params) {
410                     // delete the APN
411                     String where = Telephony.Carriers._ID + " =?";
412                     String[] whereArgs = new String[]{ mCurrentId };
413 
414                     mDatabase.delete(ApnDatabase.APN_TABLE, where, whereArgs);
415                     return null;
416                 }
417             }.execute((Void) null);
418 
419             getActivity().finish();
420         }
421 
checkNull(String value)422         private String checkNull(String value) {
423             if (value == null || value.length() == 0) {
424                 return sNotSet;
425             } else {
426                 return value;
427             }
428         }
429 
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)430         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
431             Preference pref = findPreference(key);
432             if (pref != null) {
433                 pref.setSummary(checkNull(sharedPreferences.getString(key, "")));
434             }
435         }
436 
getErrorMsg()437         private String getErrorMsg() {
438             String errorMsg = null;
439 
440             String name = checkNotSet(mName.getText());
441             String mcc = checkNotSet(mMcc.getText());
442             String mnc = checkNotSet(mMnc.getText());
443 
444             if (name.length() < 1) {
445                 errorMsg = getString(R.string.error_apn_name_empty);
446             } else if (mcc.length() != 3) {
447                 errorMsg = getString(R.string.error_mcc_not3);
448             } else if ((mnc.length() & 0xFFFE) != 2) {
449                 errorMsg = getString(R.string.error_mnc_not23);
450             }
451 
452             return errorMsg;
453         }
454 
checkNotSet(String value)455         private String checkNotSet(String value) {
456             if (value == null || value.equals(sNotSet)) {
457                 return "";
458             } else {
459                 return value;
460             }
461         }
462     }
463 }
464