Related
In Googles sample Notepad app (the final version using NotepadV3Solution), if you select Add Note (launching the NoteEdit activity), then just hit the back button, a blank line is then inserted into the Notepad activity, and it cannot be deleted. Does anyone know why? Below is the java code for this activity...
package com.android.demo.notepad3;
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class NoteEdit extends Activity {
private EditText mTitleText;
private EditText mBodyText;
private Long mRowId;
private NotesDbAdapter mDbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentView(R.layout.note_edit);
mTitleText = (EditText) findViewById(R.id.title);
mBodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
populateFields();
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setResult(RESULT_OK);
finish();
}
});
}
private void populateFields() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor(note);
mTitleText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
@Override
protected void onPause() {
super.onPause();
saveState();
}
@Override
protected void onResume() {
super.onResume();
populateFields();
}
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
}
When you hit the back button the onpause function is called which calls savestate. In savestate it doesn't check to see it fields title and body are blank. So it writes a record with blank or null values.
________________________________
http://ron-droid.blogspot.com
I'm still trying to figure all this java out. Any chance you could tell me specifically what I need to add to fix it?
greydarrah said:
I'm still trying to figure all this java out. Any chance you could tell me specifically what I need to add to fix it?
Click to expand...
Click to collapse
Modify the saveState() function to check to see if the body or titles are empty.
You can do something like:
Code:
if (title.isEmpty() || body.isEmpty())
return;
Which will make the save state not needlessly update or add to the db if a field is blank. This disallows you to add blank notes though (ie. notes with only a title, no body). Though I'm OK with that. But the save-state is where you want to start, because that's what's happening.
And for the love of Christ please wrap your code in code tags (select the code, hit the # sign in the top menu bar).
I'd do something like this;
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
If (title == null || title == "") return;
________________________________
http://ron-droid.blogspot.com
rigman said:
I'd do something like this;
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
If (title == null || title == "") return;
________________________________
http://ron-droid.blogspot.com
Click to expand...
Click to collapse
I'm sorry for being such an idiot, but when I put:
If (title == null || title == "") return;
in saveState, I get red mark right after the closing paren (just before return. If I hold my cursor over the red mark, it says syntax error, insert ";" to complete statement. Any idea why Eclipse doesn't like this?
Edit...A little more to the story:
The first error I got was this...
The method fi(boolean) is undefined fo rthe type NoteEdit
Quick fix: create method If(boolean)
So I did the quick fix. Now I'm thinking that I should add a ";" at the end if the if statement and put the return; in the new If(boolean) mentod. It would look like this:
Code:
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
If (title == null || title == "");
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
private void If(boolean b) {
// TODO Auto-generated method stub
return;
}
Is that correct, or am I totally retarded?
Well, I'm not sure what's going on in the code above, but it doesn't work...still inserts blank lines. I wish I knew more about what I'm doing.
If anyone can help me figure this out, I'm ONLY concerned with the note title (if it equals "", don't save the blank line). I don't care if the body has text in it or not.
greydarrah said:
Well, I'm not sure what's going on in the code above, but it doesn't work...still inserts blank lines. I wish I knew more about what I'm doing.
If anyone can help me figure this out, I'm ONLY concerned with the note title (if it equals "", don't save the blank line). I don't care if the body has text in it or not.
Click to expand...
Click to collapse
I'm sorry, I thought isEmpty() was a standard function of the String library in Java.
Aside from that, can I ask what your issue is with my solution?
If you don't want to check to see if the body is empty, don't. If you are only concerned with whether or not the title is empty, use Java's built in string functionality. So in your saveState function, put this after:
Code:
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (title.length() == 0) // if string length is 0
{
return;
}
What this will do, is if the title is empty (ie. == "") it will return from the saveState function, without updating or adding the note to the database. You CANNOT use the "==" operator, as that will run a comparison of the objects (to see if they're the same string instance), ie.:
Code:
String a = "hi";
String b = "hi";
if (a == b); // false
if (a.equals(b)); // true ([I]this calls the comparison operator on the strings, which is built into the String class[/I])
if (a == a); // true (they're the same object)
If you're checking to see if a string is empty, you can do:
Code:
if (a.equals("")) ... // checks if it's the equivalent of the empty string
OR
if (a.length() == 0) ... // Checks if size of string is 0
Either will work. Hopefully that clears it up for you, you can check if the String is empty, or you can see if it equals the empty string.
It sounds like you're new to programming in general, is this the case? Or are you just new to Android? If you are more accustomed to another language I can possibly (probably) give you better analogies from there...unless it's some weird language .
Syndacate said:
I'm sorry, I thought isEmpty() was a standard function of the String library in Java.
Aside from that, can I ask what your issue is with my solution?
If you don't want to check to see if the body is empty, don't. If you are only concerned with whether or not the title is empty, use Java's built in string functionality. So in your saveState function, put this after:
Code:
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (title.length() == 0) // if string length is 0
{
return;
}
Click to expand...
Click to collapse
Thanks so much. This worked perfectly. As to my programming, I'm a long time VB programmer (since VB 3), but brand new to java. In these early stages, I'm having issues getting my mind wrapped around it, but I'll get there.
I'm thankful to everyone that responded to this thread. This is how a forum should work.
greydarrah said:
As to my programming, I'm a long time VB programmer (since VB 3), but brand new to java.
Click to expand...
Click to collapse
That explains your syntax error problems above. Unlike VB, Java is case-sensitive, and you used an upper-case "If" instead of the correct lower-case "if". The compiler did not recognize this as a keyword and assumed you wanted to use a method called "If(boolean)" but forgot to define it.
So, change all capital letters in all keywords to lower case, and remove the auto-generated "If(boolean)" method again.
Cheers
tadzio
greydarrah said:
Thanks so much. This worked perfectly. As to my programming, I'm a long time VB programmer (since VB 3), but brand new to java. In these early stages, I'm having issues getting my mind wrapped around it, but I'll get there.
I'm thankful to everyone that responded to this thread. This is how a forum should work.
Click to expand...
Click to collapse
Ah, I see. Haven't used VB in forever, barely remember it, haha. Glad it works for you, though.
I trying to develop an application that can send AT commands directly to modem, from a native application interface (or possibly via terminal).
There are many obstacles here, but the main one is that the modem AT interface is rarely available through a simple serial device, which is why all other AT command apps always fail for the Samsung Galaxy class devices, which use a modified IPC protocol over sockets. However, by using the built-in RIL_OEM_HOOK_RAW and RIL_OEM_HOOK_STR we should be able to do this.
I then found this document for the Ublox LISA-U2 UMTS/HSPA voice and data modules, which show an example Android App, including most of the code used in that App. However, I cannot get this to compile in Eclipse. I think the reason is that there are many cosmetic changes in the AOS, while there are many typos in the code in that document.
I would very much like to get this or something very similar to work. I'm mainly developing on the Samsung Galaxy S2 (GB).
Here is a picture (from that document).
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Any help with this would be very very much appreciated!
It will also benefit the rest of the community as it will provide a foundation
for my future development of many other things to come, for free!
BTW. The BP used in that device is the very popular Intel/Infineon XGOLD-626...
When trying to run the code shown below in Eclipse (using GB 2.3.3+),
it fails with the following errors:The import com.android.internal.telephony cannot be resolved
The import android.os.AsyncResult cannot be resolvedIt seem like it doesn't recognize these imports:
Code:
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import android.os.AsyncResult;
The java code is:
Code:
[SIZE=2]
/*=========================================================
Demo App Code by Ublox, modified copy and paste from:
http://www.u-blox.com/images/downloads/Product_Docs/AndroidRIL_Source_Code_ApplicationNote_%283G.G2-CS-11003%29.pdf
=========================================================== */
package com.testapp.sat;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import android.view.View.OnKeyListener;
import android.view.KeyEvent;
import android.os.Message;
import android.os.Handler;
import android.os.AsyncResult;
import android.util.Log;
import android.app.AlertDialog;
public class RilOemHookTest extends Activity
{
private static final String LOG_TAG = "RILOemHookTestApp";
private RadioButton mRadioButtonAPI1 = null;
private RadioGroup mRadioGroupAPI = null;
private Phone mPhone = null;
private EditText CmdRespText = null;
private static final int EVENT_RIL_OEM_HOOK_CMDRAW_COMPLETE = 1300;
private static final int EVENT_RIL_OEM_HOOK_CMDSTR_COMPLETE = 1400;
private static final int EVENT_UNSOL_RIL_OEM_HOOK_RAW = 500;
private static final int EVENT_UNSOL_RIL_OEM_HOOK_STR = 600;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.riloemhook_layout);
mRadioButtonAPI1 = (RadioButton) findViewById(R.id.radio_api1);
mRadioGroupAPI = (RadioGroup) findViewById(R.id.radio_group_api);
// Initially turn on first button.
mRadioButtonAPI1.toggle();
//Get our main phone object.
// mPhone = PhoneFactory.getDefaultPhone();
//Register for OEM raw notification.
// mPhone.mCM.setOnUnsolOemHookRaw(mHandler, EVENT_UNSOL_RIL_OEM_HOOK_RAW, null);
//Capture text edit key press
CmdRespText = (EditText) findViewById(R.id.edit_cmdstr);
CmdRespText.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
//If the event is a key-down event on the "enter" button
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
//Perform action on key press
Toast.makeText(RilOemHookTest.this,
CmdRespText.getText(), Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
});
}
@Override
public void onPause()
{
super.onPause();
log("onPause()");
//Unregister for OEM raw notification.
// mPhone.mCM.unSetOnUnsolOemHookRaw(mHandler);
}
@Override
public void onResume()
{
super.onResume();
log("onResume()");
//Register for OEM raw notification.
// mPhone.mCM.setOnUnsolOemHookRaw(mHandler, EVENT_UNSOL_RIL_OEM_HOOK_RAW, null);
}
public void onRun(View view)
{
//Get the checked button
int idButtonChecked = mRadioGroupAPI.getCheckedRadioButtonId();
//Get the response field
CmdRespText = (EditText) findViewById(R.id.edit_response);
byte[] oemhook = null;
switch(idButtonChecked)
{
case R.id.radio_api1:
oemhook = new byte[1];
oemhook[0] = (byte)0xAA;
break;
case R.id.radio_api2:
oemhook = new byte[2];
oemhook[0] = (byte)0xBB;
oemhook[1] = (byte)0x55;
break;
case R.id.radio_api3:
//Send OEM notification (just echo the data bytes)
oemhook = new byte[7];
oemhook[0] = (byte)0xCC;
oemhook[1] = (byte)0x12;
oemhook[2] = (byte)0x34;
oemhook[3] = (byte)0x56;
oemhook[4] = (byte)0x78;
oemhook[5] = (byte)0x9A;
oemhook[6] = (byte)0xBC;
break;
case R.id.radio_api4:
//Send OEM command string
break;
default:
log("unknown button selected");
break;
}
if (idButtonChecked!=R.id.radio_api4) {
Message msg =
mHandler.obtainMessage(EVENT_RIL_OEM_HOOK_CMDRAW_COMPLETE);
mPhone.invokeOemRilRequestRaw(oemhook, msg);
CmdRespText.setText("");
} else {
//Copy string from EditText and add carriage return
String[] oemhookstring = { ((EditText)
findViewById(R.id.edit_cmdstr)).getText().toString()+'\r' } ;
//Create message
Message msg =
mHandler.obtainMessage(EVENT_RIL_OEM_HOOK_CMDSTR_COMPLETE);
//Send request
mPhone.invokeOemRilRequestStrings(oemhookstring, msg);
CmdRespText = (EditText) findViewById(R.id.edit_response);
CmdRespText.setText("---Wait response---");
}
}
private void logRilOemHookResponse(AsyncResult ar) {
log("received oem hook response");
String str = new String("");
if (ar.exception != null) {
log("Exception:" + ar.exception);
str += "Exception:" + ar.exception + "\n\n";
}
if (ar.result != null)
{
byte[] oemResponse = (byte[])ar.result;
int size = oemResponse.length;
log("oemResponse length=[" + Integer.toString(size) + "]");
str += "oemResponse length=[" + Integer.toString(size) + "]" + "\n";
if (size > 0) {
for (int i=0; i<size; i++) {
byte myByte = oemResponse[i];
int myInt = (int)(myByte & 0xFF);
log("oemResponse[" + Integer.toString(i) + "]=[0x" +
Integer.toString(myInt,16) + "]");
str += "oemResponse[" + Integer.toString(i) + "]=[0x" +
Integer.toString(myInt,16) + "]" + "\n";
}
}
} else {
log("received NULL oem hook response");
str += "received NULL oem hook response";
}
// Display message box
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(str);
builder.setPositiveButton("OK", null);
AlertDialog alert = builder.create();
alert.show();
}
private void logRilOemHookResponseString(AsyncResult ar) {
log("received oem hook string response");
String str = new String("");
CmdRespText = (EditText) findViewById(R.id.edit_response);
if (ar.exception != null) {
log("Exception:" + ar.exception);
str += "Exception:" + ar.exception + "\n\n";
}
if (ar.result != null) {
String[] oemStrResponse = (String[])ar.result;
int sizeStr = oemStrResponse.length;
log("oemResponseString[0] [" + oemStrResponse[0] + "]");
CmdRespText.setText( "" + oemStrResponse[0] );
} else {
log("received NULL oem hook response");
CmdRespText.setText( "No response or error received" );
}
}
private void log(String msg) {
Log.d(LOG_TAG, "[RIL_HOOK_OEM_TESTAPP] " + msg);
}
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_RIL_OEM_HOOK_CMDRAW_COMPLETE:
log("EVENT_RIL_OEM_HOOK_CMDRAW_COMPLETE");
ar = (AsyncResult) msg.obj;
logRilOemHookResponse(ar);
break;
case EVENT_RIL_OEM_HOOK_CMDSTR_COMPLETE:
log("EVENT_RIL_OEM_HOOK_CMDSTR_COMPLETE");
ar = (AsyncResult) msg.obj;
logRilOemHookResponseString(ar);
break;
case EVENT_UNSOL_RIL_OEM_HOOK_RAW:
break;
case EVENT_UNSOL_RIL_OEM_HOOK_STR:
break;
}
}
};
}
[/SIZE]
------------------ EDIT ---------------------
Of course they aren't recognized! They are internal packages not available outside the standard AOS API's. But I'm very stubborn and have now spent 2 days trying to use those damn packages anyway! I managed! I will post an update here and a whole new thread on how that is done, once I can get some other bugs out of the way...
new thread posted?
Here is my new thread on how to use and import internal packages into your project,
including instructions for hacking Eclipse ADT plugin, in order to allow for using
the com.android.internal classes.
"[APP][DEV][GUIDE] Using the Android Java Internal/Hidden API classes"
It is work in progress (WIP), and include a lot of manual file manipulations.
As such it is closely related to this thread. So keep an eye open for updates.
EDIT: 2014-01-06
That thread still work for API-17 JAR's!!
So, I managed to get it to compile but not without two main issues.
0) I got it to run on an SGS2 running GB2.3.4, but it FC's after being run.
1) I kept on getting AlertDialog builder complaints like:
"the constructor AlertDialog.Builder is undefined" so I had to comment out all code related to that. I searched for fixes, but since I'm not a Java programmer, I could not resolve this in the proper way...
2) Then I ran into some other undocumented errors from my own built (according to instructions) android.jar, so I just used the inazaruk's pre-made one, and it compiled. Here are his files:
https://github.com/inazaruk/android-sdk/tree/master/platforms
The main point is that we should not expect to blindly use the Ublox code and think it will work. Instead we need to understand the following:
a) How to properly use the RIL constants
Code:
[SIZE=2]RILConstants.java:
int RIL_REQUEST_OEM_HOOK_RAW = 59;
int RIL_REQUEST_OEM_HOOK_STRINGS = 60;
int RIL_UNSOL_OEM_HOOK_RAW = 1028;
my code:
EVENT_RIL_OEM_HOOK_CMDRAW_COMPLETE
EVENT_RIL_OEM_HOOK_CMDSTR_COMPLETE
EVENT_UNSOL_RIL_OEM_HOOK_RAW
EVENT_UNSOL_RIL_OEM_HOOK_STR
[/SIZE]
b) What the various bytecodes sent, are actually doing, and how to make them do what we want.
Code:
[SIZE=2] case R.id.radio_api1:
oemhook = new byte[1];
oemhook[0] = (byte)0xAA;
break;
case R.id.radio_api2:
oemhook = new byte[2];
oemhook[0] = (byte)0xBB;
oemhook[1] = (byte)0x55;
break;
case R.id.radio_api3:
//Send OEM notification (just echo the data bytes)
oemhook = new byte[7];
oemhook[0] = (byte)0xCC;
oemhook[1] = (byte)0x12;
oemhook[2] = (byte)0x34;
oemhook[3] = (byte)0x56;
oemhook[4] = (byte)0x78;
oemhook[5] = (byte)0x9A;
oemhook[6] = (byte)0xBC;
break;
[/SIZE]
c) If we can simplify the code to just send ONE hard-coded AT command and read the response.
d) alternatively use a completely different method, that will undoubtedly work, but will be device dependent. I'm talking about the (Samsung modified) IPC modem-communication protocols, as discussed in my older threads...
e) Find out how to use a local shell + device to send AT commands directly to BP.
To monitor the radio and related messages from the App (on a Sumsung) , you can use:
Code:
[SIZE=2]adb shell logcat AndroidRuntime:* ActivityManager:* dalvikvm:* DataRouter:E NetdConnector:D *:s
[/SIZE]
I've done similar work in my SprintDiagnostics app included in our Eos project. Its in the sprint gnex thread. There's no way to make this a user app as you have to be on the Phone looper thread. Meaning you have to declare the phone process in the manifest for the activity calling the raw ril requests. I can grab msl, write prl, and some other good stuff. And enable diagnostic mode based on shell work from Autoprime.
Edit: http://git.teameos.org/eos/device/samsung/toroplus.git/tree/SprintDiagnostics?h=jellybean
bigrushdog said:
I've done similar work in my SprintDiagnostics app included in our Eos project. Its in the sprint gnex thread. There's no way to make this a user app as you have to be on the Phone looper thread. Meaning you have to declare the phone process in the manifest for the activity calling the raw ril requests. I can grab msl, write prl, and some other good stuff. And enable diagnostic mode based on shell work from Autoprime.
Edit: http://git.teameos.org/eos/device/samsung/toroplus.git/tree/SprintDiagnostics?h=jellybean
Click to expand...
Click to collapse
Hi! Thanks for interesting info, but unfortunately I don't understand all you're saying. But I got many more questions that answers.
First of all, what thread are you referring to? (Could you give a link?) Second, how do you do that declaration? (Code example?) BTW. I looked briefly at the code and must admit it seem a bit cryptic, as I didn't quite find the where AT's goes or are created. Third, what is "msl"? Do you have an APK to try on? Finally, what specific modem (cellular) processor are you running in that device? LTE?
Screenshots?
Thanks in advance.
Hi There
Forgive me if I'm oversimplifying it here but wouldn't minicom or some similar terminal program do this? I believe minicom is included in busybox.
Surely you could just wrap that around using JNI
AT Commands in android are sent natively by the vendor implemented ril library, normally set in global properties as rild.libpath. It may be worth stepping away from the JAVA code for a moment and looking at the rild and reference-ril sources in the aosp source. The atchannel.c is where the "magic" happens. Also If you haven't already, read PDK Telephony Documentation
When you get down to it, all you really want to do is read and write from a tty character device so doing it "raw" is always an option.
Also to monitor the Radio log in android use adb logcat -b radio , You will be enlightened as you watch the AT commands fly by!
trevd said:
Forgive me if I'm oversimplifying it here but ...
Also to monitor the Radio log in android use adb logcat -b radio , You will be enlightened as you watch the AT commands fly by!
Click to expand...
Click to collapse
No, you are missing the entire point of this thread. We are trying to circumvent actually using the vendor RIL, by tunneling the commands via the OEM_RAW requests. That is because it is often filtering out non-standard OEM AT commands that are not part of the common standard. These are all dependent on the BP/CP and and varies widely from device to device. In addition on many devices the direct modem serial is either not possible (because there is no such connection/transport) or it has been blocked/disabled. The Java way of circumventing this, is what this thread is all about. In addition the "-b radio" logcat doesn't fetch ATs sent by vendor specific OEM IPCs, for example. This is the case for the SGS2 (GT-I9100) and many other devices.
E:V:A said:
No, you are missing the entire point of this thread.
Click to expand...
Click to collapse
Ah, Okay...If I had a SGS2 too play with I'd probably join in on this little adventure, but I don't
trevd said:
Ah, Okay...If I had a SGS2 too play with I'd probably join in on this little adventure, but I don't
Click to expand...
Click to collapse
Well again... The point is that: RIL_OEM_HOOK_RAW and RIL_OEM_HOOK_STR are (internally) accessible for all RIL's. Which means that you CAN join in!
E:V:A said:
Hi! Thanks for interesting info, but unfortunately I don't understand all you're saying. But I got many more questions that answers.
First of all, what thread are you referring to? (Could you give a link?) Second, how do you do that declaration? (Code example?) BTW. I looked briefly at the code and must admit it seem a bit cryptic, as I didn't quite find the where AT's goes or are created. Third, what is "msl"? Do you have an APK to try on? Finally, what specific modem (cellular) processor are you running in that device? LTE?
Screenshots?
Thanks in advance.
Click to expand...
Click to collapse
My apologies for not responding sooner. I forgot to subscribe ;( . I'd like to try and clear up my previous post as well as try to shed some light on this topic. First, I'm by no means a telephony expert. I've read some of your other threads and found them remarkable. I write Java and do feature development in a AOSP project. I currently am doing telephony work on Samsun Sprint CDMA devices including galaxy nexus, nexus s, and working on s3. As you know, those use the VIA chipset. However, working through the Android radio layer makes that irrelevant, for the most part. I've also worked with Autoprime on some telephony/modem/ril matters.
First some code and background on what I have done on Sprint Galaxy Nexus. These are some code fragments from my CDMATools app.
Manifest:
Code:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.teameos.settings.device"
android:sharedUserId="android.uid.system"
android:versionCode="1"
android:versionName="1.0" >
Of importance if the android.uid.system uid. This allows the app to access the phone process, which is required for PhoneFactory.getDefaultPhone(); The following activity queries ril for a Master Subsidy Lock code (msl)
Code:
<activity
android:name=".MslActivity"
android:label="@string/msl_activity"
android:process="com.android.phone" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
And the actual method which is used to query the radio...
Code:
private void checkMSLCode()
{
ByteArrayOutputStream bytearrayoutputstream;
DataOutputStream dataoutputstream;
bytearrayoutputstream = new ByteArrayOutputStream();
dataoutputstream = new DataOutputStream(bytearrayoutputstream);
try {
dataoutputstream.writeByte(main_cmd_hidden);
dataoutputstream.writeByte(OEM_SUB_CMD_GET_MSL);
dataoutputstream.writeShort(device_short);
if (isTuna) {
dataoutputstream.writeByte(OEM_SUB_RAW_MSL);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mPhone.invokeOemRilRequestRaw(bytearrayoutputstream.toByteArray(),
mHandler.obtainMessage(GET_MSL_DONE));
}
A snipit of the handler code to receive and process incoming messages from radio.
Code:
mHandler = new Handler() {
public void handleMessage(Message message) {
Log.i(LOG_TAG, "MSL response incoming!!");
AsyncResult asyncresult = (AsyncResult) message.obj;
if (asyncresult.exception != null)
{
Log.i(LOG_TAG, "AsyncResult Exception Occur!!!");
} else
if (asyncresult.result == null)
{
Log.i(LOG_TAG, "ar.result == NULL! - No answer for MSL response");
} else
{
byte abyte0[] = (byte[]) (byte[]) asyncresult.result;
String s = new String("");
for (int j = 0; j < 6; j++)
s = (new StringBuilder()).append(s).append((char) abyte0[j]).toString();
Intent intent = new Intent().setAction(getString(R.string.msl_action));
intent.putExtra(getString(R.string.msl_key), Integer.parseInt(s));
mContext.sendBroadcast(intent);
finish();
}
}
};
I have been able to get the raw commands from RE'ing various device proprietary apk's. Of importance to this topic is this checkMSLCode() function. This is the equivalent of raw AT commands, but being passed though the Android radio layer. From looking at the code for that AT command application you posted in the OP, it uses a similar structure. In fact, if time permits, i'm confident i can expand on it and make it somewhat more robust. One problem is see is finding an effective way to interpret the AT command responses from the radio. Requests often get a response in different forms, thus requiring a unique handler for each function. Of course, we could always do a Object.toString() to the log and try to decipher it. I hope this info sheds some light on the topic. I'll post when I come up with more.
Edit: also I should note, i build this apk inside of my AOSP repo, so it sees all the internal api automatically. I'm now looking at using reflection to do some telephony stuff, but it's proving rather tricky.
Very nice! (That was an understatement!)
I just wish I knew how to apply that...
Regarding the raw command (and their appropriate responses), I think there is both code and documents available for this, for Qualcomm chips. I know nothing about the VIA chipsets, but a whole lot more about the Qualcomm MSM stuff... Many CDMA devices use just Qualcomm, so perhaps you've been across some of these. I bet we could get this working on them, somehow.
Since, just as you said, the RIL should take care of much of the details. We just need to apply the command requests and responses. But my Java App making skills are really not even worthy a try at the moment.
Somebody else who'd care to join us, to give this a try?
As an aside and a note of reference, mostly to myself. (For a professional OEM programmer, this may be obvious, but me it's not. ) After having dug through some of the QC MSM code, I've come to the conclusion that QC like to use what they (?) call SMD (Shared memory Device) for doing their IPC stuff, which include talking to modem. While for Infineon/Intel modem, we know that Samsung have developed their own (non-standard) IPC protocol version for this communication...
While rewriting my Cdmatools app making it fragment based, I used reflection to access all the hidden apis. I'll fork the source for the at command app and apply the reflected classes.
I also got access to my Samsung Galaxy S3 via Reflection... although I had to go AOSP and sign with they same system key to run as looper. But now I just need help with what to send........ via RAW/String
bigrushdog said:
While rewriting my Cdmatools app making it fragment based, I used reflection to access all the hidden apis. I'll fork the source for the at command app and apply the reflected classes.
Click to expand...
Click to collapse
Excellent. Where can we download this?
Have you been able to make demo App or something?
enigma99a said:
I also got access to my Samsung Galaxy S3 via Reflection... although I had to go AOSP and sign with they same system key to run as looper. But now I just need help with what to send........ via RAW/String
Click to expand...
Click to collapse
Can you elaborate? Do you need info on what AT commands to send, or are you asking about the protocol?
PS. I would be great if you could say something more about how you did it, so that perhaps other interested people can join in. We need more people on this, so that we can start working on THIS project.
my apologies that i've been unable to continue efforts on that project. My aosp project consumes all of my development time and then some. Here is the source for CDMATools. I was looking for ways to monetize it. However, due to android's permissions scheme, it's not gonna happen lol. so here ya go.
www.miuimods.com/bigrushdog/CDMATools.tar.bz2
compile in eclipse then sign with aosp platorm test keys. I use some libraries that require it be compiled in eclipse. and because of the internal telephony imports, it must have the platform signature. Also, because it's compiled in eclipse, i use reflection to access internal telephony. Of interest will be the Phone service class. everything else is just ui. Look at how i structure the reflection. You can reflect all the internal telephony calls in the same fashion. any issues, just hit me. but my time now is very limited as I have a large team to manage and my plate is more than full. Good luck!
feel free to post on github, distribute, or whatever.
So I have not yet had time to modify OP app or using bigrushdog's CDMAtools. But I will soon. In the meantime I just post the internals for the _OEM_HOOK_ statements.
The code base is here:
https://android.googlesource.com/platform/frameworks/base.git/
So from the JellyBean RIL.java:
Code:
[SIZE=2]public void invokeOemRilRequestRaw(byte[] data, Message response) {
RILRequest rr = RILRequest.obtain(RIL_REQUEST_OEM_HOOK_RAW, response);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+ "[" + IccUtils.bytesToHexString(data)
+ "]");
rr.mp.writeByteArray(data);
send(rr);
}
public void invokeOemRilRequestStrings(String[] strings, Message response) {
RILRequest rr = RILRequest.obtain(RIL_REQUEST_OEM_HOOK_STRINGS, response);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
rr.mp.writeStringArray(strings);
send(rr);
}[/SIZE]
And the expected response can be found in the ril.h:
Code:
[SIZE=2]#define [B]RIL_REQUEST_DATA_REGISTRATION_STATE[/B] 21
/**
* RIL_REQUEST_DATA_REGISTRATION_STATE
*
* Request current DATA registration state
*
* "data" is NULL
* "response" is a "char **"
* ((const char **)response)[0] is registration state 0-5 from TS 27.007 10.1.20 AT+CGREG
* ((const char **)response)[1] is LAC if registered or NULL if not
* ((const char **)response)[2] is CID if registered or NULL if not
* ((const char **)response)[3] indicates the available data radio technology,
* valid values as defined by RIL_RadioTechnology.
* ((const char **)response)[4] if registration state is 3 (Registration
* denied) this is an enumerated reason why
* registration was denied. See 3GPP TS 24.008,
* Annex G.6 "Additonal cause codes for GMM".
* 7 == GPRS services not allowed
* 8 == GPRS services and non-GPRS services not allowed
* 9 == MS identity cannot be derived by the network
* 10 == Implicitly detached
* 14 == GPRS services not allowed in this PLMN
* 16 == MSC temporarily not reachable
* 40 == No PDP context activated
* ((const char **)response)[5] The maximum number of simultaneous Data Calls that can be
* established using RIL_REQUEST_SETUP_DATA_CALL.
*
* [/SIZE]The values at offsets 6..10 are [COLOR=Blue][B]optional LTE[/B][/COLOR] location information in decimal.[SIZE=2]
* If a value is unknown that value may be NULL. If all values are NULL,
* none need to be present.
* ((const char **)response)[6] is TAC, a 16-bit Tracking Area Code.
* ((const char **)response)[7] is CID, a 0-503 Physical Cell Identifier.
* ((const char **)response)[8] is ECI, a 28-bit E-UTRAN Cell Identifier.
* ((const char **)response)[9] is CSGID, a 27-bit Closed Subscriber Group Identity.
* [COLOR=Red]((const char **)response)[10] is TADV, a 6-bit timing advance value.[/COLOR]
*
* LAC and CID are in hexadecimal format.
* valid LAC are 0x0000 - 0xffff
* valid CID are 0x00000000 - 0x0fffffff
*
* Please note that registration state 4 ("unknown") is treated
* as "out of service" in the Android telephony system
*
* Valid errors:
* SUCCESS
* RADIO_NOT_AVAILABLE
* GENERIC_FAILURE
*/
#define [B]RIL_REQUEST_OEM_HOOK_RAW[/B] 59
/**
* RIL_REQUEST_OEM_HOOK_RAW
*
* This request reserved for OEM-specific uses. It passes raw byte arrays
* back and forth.
*
* It can be invoked on the Java side from
* com.android.internal.telephony.Phone.invokeOemRilRequestRaw()
*
* "data" is a char * of bytes copied from the byte[] data argument in java
* "response" is a char * of bytes that will returned via the
* caller's "response" Message here:
* (byte[])(((AsyncResult)response.obj).result)
*
* An error response here will result in
* (((AsyncResult)response.obj).result) == null and
* (((AsyncResult)response.obj).exception) being an instance of
* com.android.internal.telephony.gsm.CommandException
*
* Valid errors:
* All
*/
#define [B]RIL_REQUEST_OEM_HOOK_STRINGS[/B] 60
/**
* RIL_REQUEST_OEM_HOOK_STRINGS
*
* This request reserved for OEM-specific uses. It passes strings
* back and forth.
*
* It can be invoked on the Java side from
* com.android.internal.telephony.Phone.invokeOemRilRequestStrings()
*
* "data" is a const char **, representing an array of null-terminated UTF-8
* strings copied from the "String[] strings" argument to
* invokeOemRilRequestStrings()
*
* "response" is a const char **, representing an array of null-terminated UTF-8
* stings that will be returned via the caller's response message here:
*
* (String[])(((AsyncResult)response.obj).result)
*
* An error response here will result in
* (((AsyncResult)response.obj).result) == null and
* (((AsyncResult)response.obj).exception) being an instance of
* com.android.internal.telephony.gsm.CommandException
*
* Valid errors:
* All
*/
[SIZE=2]#define [COLOR=Purple][B]RIL_REQUEST_RADIO_POWER[/B][/COLOR] 23
/**
* RIL_REQUEST_RADIO_POWER
*
* Toggle radio on and off (for "airplane" mode)
* If the radio is is turned off/on the radio modem subsystem
* is expected return to an initialized state. For instance,
* any voice and data calls will be terminated and all associated
* lists emptied.
*
* "data" is int *
* ((int *)data)[0] is > 0 for "Radio On"
* ((int *)data)[0] is == 0 for "Radio Off"
*
* "response" is NULL
*
* Turn radio on if "on" > 0
* Turn radio off if "on" == 0
*
* Valid errors:
* SUCCESS
* RADIO_NOT_AVAILABLE
* GENERIC_FAILURE
*/
[/SIZE]
[/SIZE]
The last one seem to imply that there could be other "bytes" to be read from that data[] array... Some suggestions have been the Transmitted power... But this was assuming a Qualcomm based modem, so there is no telling if there is another standard there. We don't know...
Here are links to two highly relevant threads on Gmane/GoogleGroups with code excerpts similar to what we need...
http://article.gmane.org/gmane.comp.handhelds.android.ndk/10555
http://article.gmane.org/gmane.comp.handhelds.android.platform/8436
https://groups.google.com/forum/?fromgroups=#!topic/android-platform/tVyNMnXtcEI
With a link to Google Phone G1 Field Test:
http://phoneftd.blogspot.com/2009/03/google-g1-phone-field-test.html
Hey guys,
I've been making an app for my ROM (SkyDragon) and I want to include a news section, which will retrieve info from the web using a XML file.
But, I've tried many things and they won't work. So could anyone post a quick working parsing code here that will put the items in a listview, so I can experiment a bit with it? It'd be widely appreciated
I might reccommend using a library like Jackson 2.0+. I have not had the experience to use it with XML, but rather JSON, but it does appear to be just as easy to do so (at least since 2.0).
You would set up a POJO (Plain Old Java Object) class to represent the structure of the xml data, for instance:
Code:
public class Simple
{
private int x, y;
public int getX(){ return x; }
public int getY(){ return y; }
public void setX(int x){ this.x = x; }
public void setY(int y){ this.y = y; }
}
would represent xml like:
Code:
<Simple>
<x>1</x>
<y>2</y>
</Simple>
and to build the object you would use the library as such:
Code:
ObjectMapper xmlMapper = new XmlMapper();
Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>", Simple.class);
Thanks, will try it. But it can't be that simple, right? I mean, every tutorial is pretty big, and uses multiple activities.
Sent from my awesome fridge
Well like I said, I haven't actually used it for XML but rather JSON, but it really was that simple for me. What I listed is of course a very simple example, but scaling it up really just requires mapping your xml source to a POJO. The hardest part about your use will be that you don't control the XML.
Here is an example right out of a project of mine that worked great. "request.result" was basically a String object that contained the JSON response from a restful web service that I did not control but knew the structure of (by examination). Truly it is just these 2 lines of code to parse the response and after that you have an object that is easy to use.
Code:
ObjectMapper mapper = new ObjectMapper();
inventory = mapper.readValue(request.result, PlayerInventory.class);
Unfortunately this service no longer exists so I cannot get you an example response, but below is the POJO that I used to map it.
Code:
package com.mcdermotsoft;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class PlayerInventory
{
private int ok;
private Map<String,Slot> contents;
public PlayerInventory(){}
public int getOk() {
return ok;
}
public Map<String,Slot> getContents() {
return contents;
}
public void setOk(int ok) {
this.ok = ok;
}
public void setContents(Map<String,Slot> contents) {
this.contents = contents;
}
}
"@JsonIgnoreProperties(ignoreUnknown = true)" is important because I really didn't care to map everything from the response so this annotation tells the Jackson parser to ignore anything it can't map instead of throwing an exception (don't remember what the exception is but you might run into it yourself).
Hi all, im having a go at developing a simple app. i have little experience with Java and Android development. i have a little test app at the moment and have created a new class, im trying to create a new instance of this class on a button click. it fails to do so, i cant for the life of me see why so.. can someone shed any light on this?
Thanks
Debuging this shows it hitting the "LocationFactory locationf = new LocationFactory();" line and throwing an exception-
"java.lang.NullPointerException"
Main
Code:
package com.example.testapp;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;
import java.io.IOException;
public class MainActivity extends Activity {
private static final Context Context = null;
protected static final String TAG = null;
[user=439709]@override[/user]
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
[user=439709]@override[/user]
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void mainButton(View view) throws IOException {
try {
LocationFactory locationf = new LocationFactory();
Toast.makeText(this, locationf.getAddress(),Toast.LENGTH_LONG).show();
} catch (Exception e)
{
Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
}
}
}
Class
Code:
package com.example.testapp;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import java.io.IOException;
import java.util.Locale;
import java.util.List;
public class LocationFactory
{
private static final Context Context = null;
Geocoder geocoder = new Geocoder(Context, Locale.getDefault());
LocationManager manager = (LocationManager) Context.getSystemService(android.content.Context.LOCATION_SERVICE);
public double Latitude = 0.0;
public double Longitude = 0.0;
public LocationFactory()
{
}
public String getAddress() throws IOException
{
String ReturnAddress = "";
String Address = "", City = "", Country = "";
List<Address> addresses = null;
if(manager.isProviderEnabled(LocationManager.GPS_PROVIDER))
{
// Use GPS Radio Location
Location GPSlocation = manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
Latitude = GPSlocation.getLatitude();
Longitude = GPSlocation.getLongitude();
}
else
{
// Use Cell Tower Location
Location NETlocation = manager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Latitude = NETlocation.getLatitude();
Longitude = NETlocation.getLongitude();
}
if(Latitude > 0 && Longitude > 0)
{
addresses = geocoder.getFromLocation(Latitude, Longitude, 1);
if(!addresses.isEmpty())
{
Address = addresses.get(0).getAddressLine(0);
City = addresses.get(0).getAddressLine(1);
Country = addresses.get(0).getAddressLine(2);
}
}
ReturnAddress = Address + " " + City + " " + Country;
return ReturnAddress;
}
}
I don't see anywhere in your code where you are calling the mainButton(View view) method. In the Android lifecycle, the onCreate method is the equivalent of a normal Java program's main() method, which means that code execution begins with the first line of onCreate(). Not knowing what you're trying to do, a good start would be to call your mainButton() method AFTER setContentView() in onCreate().
Side note: your mainButton() method has a View parameter that is never used. Is there a reason for that?
Android activity lifecycle: http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
You have to use an intent on that button click, use the method onClickListener and define the intent in the androidmanifest.xml
e.g
Code:
Button button = (Button) findViewById(R.id.[B]button[/B]) // replace latter button with actual id defined in main xml.
button.setOnClickListener(new View.OnClickListener() {
[user=439709]@override[/user]
public void onClick(View arg0) {
// TODO Auto-generated method stub
startActivity(new Intent("[B]com.example.packagename.CLASSNAME[/B]")); // this should be your own package name.
}
});
Also define this in android manifest under the <application> and </application>
Code:
<activity
android:name=".[B]CLASSNAME[/B]"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="[B]com.example.packagename.CLASSNAME[/B]" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Change the values of BOLD text according to your own values.
I tried to help you as far as I understood your question. Please let me know if you face any problem I would be more than happy to help you. Rest I am also in the learning phase so you can always PM me if you face any problem.
Hit thanks if I have helped you in any way.
coolbud012 said:
You have to use an intent on that button click, use the method onClickListener and define the intent in the androidmanifest.xml
Click to expand...
Click to collapse
Nope! He didn't say that he wanted to launch a new Activity when the button is clicked. He wants to create a new instance of his LocationFactory Class.
jpepin said:
Nope! He didn't say that he wanted to launch a new Activity when the button is clicked. He wants to create a new instance of his LocationFactory Class.
Click to expand...
Click to collapse
Oops yeah right read that now...I thought he want to start an activity... Anyways tried to delete my reply but not getting an option to delete.
There are many flaws in his code. And the other thing is if its his first app and if he has low level of programming experience then according to me it would be a next to impossible app for him, as per his code and what he is trying to implement.
I think he should rather start up with small apps, understand things and then move on to complex apps.
P.S - its just my opinion
Click to expand...
Click to collapse
Agreed that he should start small...which is exactly why your suggestion for creating and handling Intents makes no sense. Before that, he should first understand the activity lifecycle. Until then, he can just stick to trivial single-activity apps to gain experience.
OP: This code should be placed in the onCreate method:
Code:
Button button = (Button) findViewById(R.id.your_button_ID_here)
button.setOnClickListener(new View.OnClickListener() {
[user=439709]@override[/user]
public void onClick(View arg0) {
mainButton(); // get rid of the View parameter in this method...it's not needed
}
});
This will cause a new instance of your LocationFactory to be created, and will also cause your Toast message to be displayed.
thanks for the replies. yes you are right in that i am inexperienced, but this is just a test app for me to play around with and learn on. i tend to learn better by doing rather than constantly reading. thanks for your suggestions ill look into them
osmorgan said:
thanks for the replies. yes you are right in that i am inexperienced, but this is just a test app for me to play around with and learn on. i tend to learn better by doing rather than constantly reading. thanks for your suggestions ill look into them
Click to expand...
Click to collapse
I also believe in the same, I also keep on doing experiments and testing things out.
What I would suggest is that start with a small app and understand the insights on how android works and all...
Thanks
Potential Solution
Alright, I think I've found your problem. Have a look at where you define your variables in your LocationManager class:
Code:
private static final Context Context = null;
Geocoder geocoder = new Geocoder(Context, Locale.getDefault());
LocationManager manager = (LocationManager) Context.getSystemService(android.content.Context.LOCATION_SERVICE);
This is your problem:
Code:
Context Context = null;
If your context is null, and you use it to create a geocoder and call Context.getSystemService, you'll hit a null pointer. You're trying to access an object (the Context) that doesn't even exist
I'd recommend you pass the context in the LocationManager constructor and then instantiate your objects there. That's standard java procedure.
Code:
private Context mContext = null;
Geocoder geocoder = null;
LocationManager manager = null;
public double Latitude = 0.0;
public double Longitude = 0.0;
public LocationFactory(Context context)
{
this.mContext = context;
this.geocoder = new Geocoder(context, Locale.getDefault());
this.manager = (LocationManager) Context.getSystemService(android.content.Context.LOCATION_SERVICE);
}
I also renamed Context to mContext - it's generally a good idea to keep the instance's name separate from the class name.
Try that - it should work. Please feel free to ask any more questions - this is how I learned, and I think it's the best way!
Hello,
I create that thread to present you a tutorial learning you to save data with SQLite on Android. This tutorial is also available in video on Youtube :
Learn to save data with SQLite on Android
On Android, there are several solutions to persist data between users’ sessions. One solution is to use a relational database to persist data and then to be able to query easily these data. In standard, Android SDK comes with a SQLite implementation. Biggest advantage of SQLite integration to Android OS is the fact that there is no need to to setup the database. So, no administration of this database. SQLite is embedded in standard and each application can have its SQLite database.
The only job that developers must make is to define SQL tables and statements for creating and updating data. Access to an SQLite database involves accessing the file system. So, it can be a slow operation. To avoid ANR (Application Not Responding) errors, it’s recommended to perform database operations asynchronously.
When an application creates and uses a SQLite database, it will be saved by default in the directory : DATA/data/APP_PACKAGE/databases/FILENAME .
1. Architecture
All classes needed to manage databases in Android SDK are contained in the package android.database . The package android.database.sqlite contains the SQLite specific classes.
SQLite API is centered around 2 main classes :
SQLiteOpenHelper that is an helper class to extend to manage database operations.
SQLiteDatabase that is the base class for working with a SQLite database in Android.
2. SQLiteOpenHelper
When you want to work with a SQLite database in Android, you must extend SQLiteOpenHelper class. In the constructor of your subclass you call the super() method of SQLiteOpenHelper, specifying the database name and the current database version.
You need also to override the following methods :
onCreate() that is called when database is accessed but not yet created.
onUpgrade() called when you choose to increment the version number of the database. In this method you can manage the migration process between two databases versions.
Both methods get and SQLiteDatabase instance in parameter which is the way to communicate with the database.
Furthermore, SQLiteOpenHelper provides 2 methods to get access to an SQLiteDatabase instance object respectively in read and in write modes :
getReadableDatabase() for read mode.
getWriteableDatabase() for write mode.
3. SQLiteDatabase
SQLiteDatabase is the class used to communicate with a SQLite database. It exposes several methods to interact with database like insert(), update() or delete().
In addition, it lets you to make queries via rawQuery() to queries made directly in SQL or via query() method. This last method provides a structured interface for specifying a SQL query.
4. Practice
Now, you know theory about SQLite in Android context. We can put in practice all the concepts. To achieve that, we’re going to make a database with a players table letting us to store NBA players.
To start, we create a simple Player Java POJO :
Code:
public class Player {
private int id;
private String name;
private String position;
private int height;
public Player() {
}
public Player(int id, String name, String position, int height) {
this.id = id;
this.name = name;
this.position = position;
this.height = height;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return name + " - " + position + " - " + height + " cm";
}
}
Then, we must create the SQLiteOpenHelper extended class to manage our application database. Code is here :
Code:
package com.ssaurel.samples.sqlite;
import java.util.LinkedList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class SQLiteDatabaseHandler extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "PlayersDB";
private static final String TABLE_NAME = "Players";
private static final String KEY_ID = "id";
private static final String KEY_NAME = "name";
private static final String KEY_POSITION = "position";
private static final String KEY_HEIGHT = "height";
private static final String[] COLUMNS = { KEY_ID, KEY_NAME, KEY_POSITION,
KEY_HEIGHT };
public SQLiteDatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String CREATION_TABLE = "CREATE TABLE Players ( "
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "name TEXT, "
+ "position TEXT, " + "height INTEGER )";
db.execSQL(CREATION_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// you can implement here migration process
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
this.onCreate(db);
}
public void deleteOne(Player player) {
// Get reference to writable DB
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, "id = ?", new String[] { String.valueOf(player.getId()) });
db.close();
}
public Player getPlayer(int id) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, // a. table
COLUMNS, // b. column names
" id = ?", // c. selections
new String[] { String.valueOf(id) }, // d. selections args
null, // e. group by
null, // f. having
null, // g. order by
null); // h. limit
if (cursor != null)
cursor.moveToFirst();
Player player = new Player();
player.setId(Integer.parseInt(cursor.getString(0)));
player.setName(cursor.getString(1));
player.setPosition(cursor.getString(2));
player.setHeight(Integer.parseInt(cursor.getString(3)));
return player;
}
public List<Player> allPlayers() {
List<Player> players = new LinkedList<Player>();
String query = "SELECT * FROM " + TABLE_NAME;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
Player player = null;
if (cursor.moveToFirst()) {
do {
player = new Player();
player.setId(Integer.parseInt(cursor.getString(0)));
player.setName(cursor.getString(1));
player.setPosition(cursor.getString(2));
player.setHeight(Integer.parseInt(cursor.getString(3)));
players.add(player);
} while (cursor.moveToNext());
}
return players;
}
public void addPlayer(Player player) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_NAME, player.getName());
values.put(KEY_POSITION, player.getPosition());
values.put(KEY_HEIGHT, player.getHeight());
// insert
db.insert(TABLE_NAME,null, values);
db.close();
}
public int updatePlayer(Player player) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_NAME, player.getName());
values.put(KEY_POSITION, player.getPosition());
values.put(KEY_HEIGHT, player.getHeight());
int i = db.update(TABLE_NAME, // table
values, // column/value
"id = ?", // selections
new String[] { String.valueOf(player.getId()) });
db.close();
return i;
}
}
Database is created in the constructor of the extended class. Players table is created in the onCreate() method thanks to a SQL statement.
In our class, we add methods to add a new player, to delete an existing one, to update and then a method to get all the players in the table. In this last method, we use a Cursor object to iterate on rows and then build equivalent Player instances.
To use our class to create some players then display on a simple ListView, we can use the following code :
Code:
public class MainActivity extends Activity {
private SQLiteDatabaseHandler db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// create our sqlite helper class
db = new SQLiteDatabaseHandler(this);
// create some players
Player player1 = new Player(1, "Lebron James", "F", 203);
Player player2 = new Player(2, "Kevin Durant", "F", 208);
Player player3 = new Player(3, "Rudy Gobert", "C", 214);
// add them
db.addPlayer(player1);
db.addPlayer(player2);
db.addPlayer(player3);
// list all players
List<Player> players = db.allPlayers();
if (players != null) {
String[] itemsNames = new String[players.size()];
for (int i = 0; i < players.size(); i++) {
itemsNames[i] = players.get(i).toString();
}
// display like string instances
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, itemsNames));
}
}
}
Execution result can be seen here :
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
SQLite implementation in Android is simple and really powerful. You can now use it in your Android application to persist data.
Don't hesitate to give it a try and give me your feedbacks about this tutorial.
Thanks.
Sylvain
Hey, I have made a preview for SQLite database earlier this month for my friend.
If anyone's interested then it's there at https://www.GitHub.com/Fifa2151/SQLiteTest
Thanks,
Raj.
Sent from my Pixel using Tapatalk
sylsau said:
Hello,
I create that thread to present you a tutorial learning you to save data with SQLite on Android. This tutorial is also available in video on Youtube :
Learn to save data with SQLite on Android
On Android, there are several solutions to persist data between users’ sessions. One solution is to use a relational database to persist data and then to be able to query easily these data. In standard, Android SDK comes with a SQLite implementation. Biggest advantage of SQLite integration to Android OS is the fact that there is no need to to setup the database. So, no administration of this database. SQLite is embedded in standard and each application can have its SQLite database.
The only job that developers must make is to define SQL tables and statements for creating and updating data. Access to an SQLite database involves accessing the file system. So, it can be a slow operation. To avoid ANR (Application Not Responding) errors, it’s recommended to perform database operations asynchronously.
When an application creates and uses a SQLite database, it will be saved by default in the directory : DATA/data/APP_PACKAGE/databases/FILENAME .
1. Architecture
All classes needed to manage databases in Android SDK are contained in the package android.database . The package android.database.sqlite contains the SQLite specific classes.
SQLite API is centered around 2 main classes :
SQLiteOpenHelper that is an helper class to extend to manage database operations.
SQLiteDatabase that is the base class for working with a SQLite database in Android.
2. SQLiteOpenHelper
When you want to work with a SQLite database in Android, you must extend SQLiteOpenHelper class. In the constructor of your subclass you call the super() method of SQLiteOpenHelper, specifying the database name and the current database version.
You need also to override the following methods :
onCreate() that is called when database is accessed but not yet created.
onUpgrade() called when you choose to increment the version number of the database. In this method you can manage the migration process between two databases versions.
Both methods get and SQLiteDatabase instance in parameter which is the way to communicate with the database.
Furthermore, SQLiteOpenHelper provides 2 methods to get access to an SQLiteDatabase instance object respectively in read and in write modes :
getReadableDatabase() for read mode.
getWriteableDatabase() for write mode.
3. SQLiteDatabase
SQLiteDatabase is the class used to communicate with a SQLite database. It exposes several methods to interact with database like insert(), update() or delete().
In addition, it lets you to make queries via rawQuery() to queries made directly in SQL or via query() method. This last method provides a structured interface for specifying a SQL query.
4. Practice
Now, you know theory about SQLite in Android context. We can put in practice all the concepts. To achieve that, we’re going to make a database with a players table letting us to store NBA players.
To start, we create a simple Player Java POJO :
Code:
public class Player {
private int id;
private String name;
private String position;
private int height;
public Player() {
}
public Player(int id, String name, String position, int height) {
this.id = id;
this.name = name;
this.position = position;
this.height = height;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return name + " - " + position + " - " + height + " cm";
}
}
Then, we must create the SQLiteOpenHelper extended class to manage our application database. Code is here :
Code:
package com.ssaurel.samples.sqlite;
import java.util.LinkedList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class SQLiteDatabaseHandler extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "PlayersDB";
private static final String TABLE_NAME = "Players";
private static final String KEY_ID = "id";
private static final String KEY_NAME = "name";
private static final String KEY_POSITION = "position";
private static final String KEY_HEIGHT = "height";
private static final String[] COLUMNS = { KEY_ID, KEY_NAME, KEY_POSITION,
KEY_HEIGHT };
public SQLiteDatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String CREATION_TABLE = "CREATE TABLE Players ( "
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "name TEXT, "
+ "position TEXT, " + "height INTEGER )";
db.execSQL(CREATION_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// you can implement here migration process
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
this.onCreate(db);
}
public void deleteOne(Player player) {
// Get reference to writable DB
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, "id = ?", new String[] { String.valueOf(player.getId()) });
db.close();
}
public Player getPlayer(int id) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, // a. table
COLUMNS, // b. column names
" id = ?", // c. selections
new String[] { String.valueOf(id) }, // d. selections args
null, // e. group by
null, // f. having
null, // g. order by
null); // h. limit
if (cursor != null)
cursor.moveToFirst();
Player player = new Player();
player.setId(Integer.parseInt(cursor.getString(0)));
player.setName(cursor.getString(1));
player.setPosition(cursor.getString(2));
player.setHeight(Integer.parseInt(cursor.getString(3)));
return player;
}
public List<Player> allPlayers() {
List<Player> players = new LinkedList<Player>();
String query = "SELECT * FROM " + TABLE_NAME;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
Player player = null;
if (cursor.moveToFirst()) {
do {
player = new Player();
player.setId(Integer.parseInt(cursor.getString(0)));
player.setName(cursor.getString(1));
player.setPosition(cursor.getString(2));
player.setHeight(Integer.parseInt(cursor.getString(3)));
players.add(player);
} while (cursor.moveToNext());
}
return players;
}
public void addPlayer(Player player) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_NAME, player.getName());
values.put(KEY_POSITION, player.getPosition());
values.put(KEY_HEIGHT, player.getHeight());
// insert
db.insert(TABLE_NAME,null, values);
db.close();
}
public int updatePlayer(Player player) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_NAME, player.getName());
values.put(KEY_POSITION, player.getPosition());
values.put(KEY_HEIGHT, player.getHeight());
int i = db.update(TABLE_NAME, // table
values, // column/value
"id = ?", // selections
new String[] { String.valueOf(player.getId()) });
db.close();
return i;
}
}
Database is created in the constructor of the extended class. Players table is created in the onCreate() method thanks to a SQL statement.
In our class, we add methods to add a new player, to delete an existing one, to update and then a method to get all the players in the table. In this last method, we use a Cursor object to iterate on rows and then build equivalent Player instances.
To use our class to create some players then display on a simple ListView, we can use the following code :
Code:
public class MainActivity extends Activity {
private SQLiteDatabaseHandler db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// create our sqlite helper class
db = new SQLiteDatabaseHandler(this);
// create some players
Player player1 = new Player(1, "Lebron James", "F", 203);
Player player2 = new Player(2, "Kevin Durant", "F", 208);
Player player3 = new Player(3, "Rudy Gobert", "C", 214);
// add them
db.addPlayer(player1);
db.addPlayer(player2);
db.addPlayer(player3);
// list all players
List<Player> players = db.allPlayers();
if (players != null) {
String[] itemsNames = new String[players.size()];
for (int i = 0; i < players.size(); i++) {
itemsNames[i] = players.get(i).toString();
}
// display like string instances
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, itemsNames));
}
}
}
Execution result can be seen here :
SQLite implementation in Android is simple and really powerful. You can now use it in your Android application to persist data.
Don't hesitate to give it a try and give me your feedbacks about this tutorial.
Thanks.
Sylvain
Click to expand...
Click to collapse
Awesome guide @sylsau...
Also, do you know how to make a flashify type app but only for a specific zip?
When you want to work with a SQLite database in Android, you must extend SQLiteOpenHelper class. In the constructor of your subclass you call the super() method of SQLiteOpenHelper, specifying the database name and the current database version.
Click to expand...
Click to collapse
You don't actually need to use a subclass of SQLiteOpenHelper you can use the SQliteDatabase's open????? methods.
Furthermore, SQLiteOpenHelper provides 2 methods to get access to an SQLiteDatabase instance object respectively in read and in write modes :
Click to expand...
Click to collapse
Actually, in either case, except if the database cannot be opened, for write, both getReadableDatabase and getWritableDatabase will open a database that can be written to. As per :-
Create and/or open a database. This will be the same object returned by getWritableDatabase() unless some problem, such as a full disk, requires the database to be opened read-only. In that case, a read-only database object will be returned. If the problem is fixed, a future call to getWritableDatabase() may succeed, in which case the read-only database object will be closed and the read/write object will be returned in the future.
Click to expand...
Click to collapse
as per developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper#getReadableDatabase()
On occasions people new to SQLite sometimes wonder why no database exists after they have instantiated the subclass of SQLiteOpenHelper (aka the DatabaseHelper). This is because the database is only created when either getWritableDatabase or getReadableDatabase is called. With a single line added to the constructor, the constructor will create the database (and thus invoke the onCreate method) e.g.
Code:
public SQLiteDatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.getWritableDatabse();
}
AUTOINCREMENT is perhaps the most commonly misused keyword (perhaps wrongly named). It does not make the column automatically generate a unique ID. It is INTEGER PRIMARY KEY that does this, as it make the column an alias of the **rowid**.
Rather AUTOINCREMENT compliments INTEGER PRIMARY KEY adding a constraint that the generated ID must be larger that any ID that exists or have existed. However, this is a moot point as it's only when the largest possible ID has been assigned (9223372036854775807) that it comes into play (other than without AUTOINCREMENT a deleted highest ID will be resused). At this point a table with AUTOINCREMENT will then fail with an SQLITE_FULL exception (without AUTOINCREMENT will attempt to assign a free lower ID rather than fail). However, AUTOINCREMENT has overheads (using a limited test I came up with an 8-12% degradation in performance when inserting). This is due to a changed algorithm being used that utilises another table sqlite_sequence that stores the highest allocated ID.
The SQLite documentation states :-
The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.
Click to expand...
Click to collapse
sqlite.org/autoinc.html
There are a few issues with the code, such as :-
You should always close Cursors when finished with them (not doing so may result in too many databases /database objects open exception ).
Checking a Cursor for null after a Cursor is returned from a call to an SQLiteDatabase method that returns a Cursor serves no purpose. A valid Cursor will always be returned. If there is no data then using a Cursor moveTo????? method will return false is the move cannot be made, alternately the getCount() method will return the number of rows in the Cursor.
If there were now rows in the Players table, the the code would fail with an error when an attempt is made to retrieve data at
Code:
player.setId(Integer.parseInt(cursor.getString(0)));
Issues regarding mis-calculated column offsets can be reduced by taking advantage of the Cursor's **getColumnIndex** method.
As such, as an example, the getPlayer method would be improved if changed to :-
Code:
public Player getPlayer(int id) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, // a. table
COLUMNS, // b. column names
" id = ?", // c. selections
new String[] { String.valueOf(id) }, // d. selections args
null, // e. group by
null, // f. having
null, // g. order by
null); // h. limit
Player player = new Player(); //<<<<<<<<<< Always have a Player to return (should check for default player to indicated )
if (cursor.moveToFirst()) {
player.setId(Integer.parseInt(cursor.getString(cursor.getColumnIndex(KEY_ID))));
player.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME)));
player.setPosition(cursor.getString(cursor.getColumnIndex(KEY_POSITION)));
player.setHeight(Integer.parseInt(cursor.getString(cursor.getColumnIndex(KEY_HEIGHT))));
}
cursor.close(); //<<<<<<<<<< Important to close a Cursor
return player;
}