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
17package com.android.messaging.ui.appsettings;
18
19import android.app.AlertDialog;
20import android.app.Dialog;
21import android.content.ContentValues;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.database.Cursor;
25import android.database.sqlite.SQLiteDatabase;
26import android.os.AsyncTask;
27import android.os.Bundle;
28import android.preference.EditTextPreference;
29import android.preference.Preference;
30import android.preference.PreferenceFragment;
31import android.provider.Telephony;
32import android.support.v4.app.NavUtils;
33import android.view.KeyEvent;
34import android.view.Menu;
35import android.view.MenuInflater;
36import android.view.MenuItem;
37
38import com.android.messaging.R;
39import com.android.messaging.datamodel.data.ParticipantData;
40import com.android.messaging.sms.ApnDatabase;
41import com.android.messaging.sms.BugleApnSettingsLoader;
42import com.android.messaging.ui.BugleActionBarActivity;
43import com.android.messaging.ui.UIIntents;
44import com.android.messaging.util.PhoneUtils;
45
46public 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
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
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
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
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
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
172        public void onCreate(Bundle icicle) {
173            super.onCreate(icicle);
174
175        }
176
177        @Override
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
232        public void onDestroy() {
233            super.onDestroy();
234            if (mCursor != null) {
235                mCursor.close();
236                mCursor = null;
237            }
238        }
239
240        @Override
241        public void onResume() {
242            super.onResume();
243            getPreferenceScreen().getSharedPreferences()
244            .registerOnSharedPreferenceChangeListener(this);
245        }
246
247        @Override
248        public void onPause() {
249            getPreferenceScreen().getSharedPreferences()
250            .unregisterOnSharedPreferenceChangeListener(this);
251            super.onPause();
252        }
253
254        public void setSubId(final int subId) {
255            mSubId = subId;
256        }
257
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
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
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
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         */
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
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
422        private String checkNull(String value) {
423            if (value == null || value.length() == 0) {
424                return sNotSet;
425            } else {
426                return value;
427            }
428        }
429
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
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
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