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