Related
Hi All,
I am attempting to generate a signal at a dynamically chosen frequency (i.e. 50-20000 Hz approx.) I am starting to get frustrated. I've read the documentation over and over again, looked at numerous examples and forum posts but have not resolved the problem.
The code works BUT the signal is not stable going up and down slightly.
Code:
final float frequency = freq;
float increment = (float)((2*Math.PI) * frequency / 44100); // angular increment for each sample
float angle = 0;
AndroidAudioDevice device = new AndroidAudioDevice( );
float samples[] = new float[1024];
while(threadIsRunning)
{
for( int i = 0; i < samples.length; i++ )
{
samples[i] = (float)Math.sin( angle );
angle += increment;
}
device.writeSamples( samples );
}
and the the AndroidAudioDevice Class is as follows:
Code:
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
public class AndroidAudioDevice {
AudioTrack track;
short[] buffer = new short[1024];
public AndroidAudioDevice( )
{
int minSize =AudioTrack.getMinBufferSize( 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT );
track = new AudioTrack( AudioManager.STREAM_MUSIC, 44100,
AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
minSize, AudioTrack.MODE_STREAM);
track.play();
}
public void writeSamples(float[] samples)
{
fillBuffer( samples );
track.write( buffer, 0, samples.length );
}
private void fillBuffer( float[] samples )
{
if( buffer.length < samples.length )
buffer = new short[samples.length];
for( int i = 0; i < samples.length; i++ )
buffer[i] = (short)(samples[i] * Short.MAX_VALUE);
}
public void releaseTrack(){
track.release();
}
}
Where is the problem here? Is there an alternative way of solving this problem?
I would very much appreciate if you were able to help, even if its only a pointer in the right direction! Have lost a lot of hours over this puppy.
-Thanks in advance
do you really need a wavelike form? why not just plot each point as Y-axis and keep the x as time...just do it really fast and you get a wave-sorta.....
-hope i helped
? I'm not exactly sure what you mean. I am essentially plotting a wave function, just filling a buffer "x-axis" with "y-axis" the sine values.
You actually are not creating a perfect sin wave because the samples array does not have a complete period.
According your code you need frequency * 44100 samples to have the entire period.
Your actual implementation create a wave that does not match the end of samples array with the beginning of the next samples array sent to the device, it creates a little peak and it is what you notice.
Could you please expand on that Alerias.The way I see it the samples don't contain a full sine wave but they do keep going where they left of from one sample to the next. And I don't think there should be a peak changing from one sample to the next.
Code:
angle += increment;
The angle Variable is continuously and uniformly incremented. So if the sample a's last entry is
Code:
samples[i] = (float)Math.sin( angle );
The first entry of the next sample will be
Code:
samples[i] = (float)Math.sin( angle +increment);
I therefore don't see a discontinuity. The angle variable does not get re-initiated to 0.
Thanks a lot,
FlyingSwissman
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
I have used content provider in my project and override the applyBatch() for writting data into the database :
@override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
final Context context = getContext();
final SQLiteDatabase db = getDatabase(context);
db.beginTransaction();
try {
final int numOperations = operations.size();
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
for (int i = 0; i < numOperations; i++) {
results = operations.get(i).apply(this, results, i);
}
db.setTransactionSuccessful();
return results;
} finally {
db.endTransaction();
}
}
If there is any writing operation to the database, applyBatch() method get called and in same time if I will make query from database(using CursorLoader), or call apply batch from different activity ANR comes and the following exception will also comes :
W/SQLiteConnectionPool(18025): The connection pool for database '+data+data+com.abc.xyz+databases+XYZ' has been unable to grant a connection to thread 8073 (pool-17-thread-3) with flags 0x2 for 64.105 seconds.
W/SQLiteConnectionPool(18025): Connections: 0 active, 1 idle, 0 available.
The above issue will not come if I remove the methode db.beginTrasaction() and db.endTrasaction() from applyBatch(). But in that case reading/writing time becomes large. I have used methods db.beginTrasaction() and db.endTrasaction() which reduces database access time to ten times.
Is there any approach, so that SQLite DataBse response quickly and there is no ANR ?
I have the following code that successfully updates parts of my layout from values in an SQLite table
Code:
helper = new TaskDBHelper(Overview.this);
SQLiteDatabase sqlDB = helper.getReadableDatabase();
Cursor cursor = sqlDB.query(TaskContract.TABLE,
new String[]{TaskContract.Columns._ID, TaskContract.Columns.TASK, TaskContract.Columns.BAL, TaskContract.Columns.IP, TaskContract.Columns.STATUS},
null, null, null, null, null);
listAdapter = new SimpleCursorAdapter(
this,
R.layout.sum_view,
cursor,
new String[]{TaskContract.Columns.TASK, TaskContract.Columns.BAL, TaskContract.Columns.IP, TaskContract.Columns.STATUS},
new int[]{R.id.taskTextView, R.id.txtData, R.id.txtIP, R.id.txtStatus},
0
);
this.setListAdapter(listAdapter);
I am trying to set an image based on the value of one of the fields. The status column will return 1, 2 or 3 and I have 3 images that correspond, to display. Can anyone help me and explain how I can fit this into what I am already doing please?
calnaughtonjnr said:
I have the following code that successfully updates parts of my layout from values in an SQLite table
Code:
helper = new TaskDBHelper(Overview.this);
SQLiteDatabase sqlDB = helper.getReadableDatabase();
Cursor cursor = sqlDB.query(TaskContract.TABLE,
new String[]{TaskContract.Columns._ID, TaskContract.Columns.TASK, TaskContract.Columns.BAL, TaskContract.Columns.IP, TaskContract.Columns.STATUS},
null, null, null, null, null);
listAdapter = new SimpleCursorAdapter(
this,
R.layout.sum_view,
cursor,
new String[]{TaskContract.Columns.TASK, TaskContract.Columns.BAL, TaskContract.Columns.IP, TaskContract.Columns.STATUS},
new int[]{R.id.taskTextView, R.id.txtData, R.id.txtIP, R.id.txtStatus},
0
);
this.setListAdapter(listAdapter);
I am trying to set an image based on the value of one of the fields. The status column will return 1, 2 or 3 and I have 3 images that correspond, to display. Can anyone help me and explain how I can fit this into what I am already doing please?
Click to expand...
Click to collapse
can u explain more .
but what i understand is , u need to implement custom adapter so it will be good to set
hisee said:
can u explain more .
but what i understand is , u need to implement custom adapter so it will be good to set
Click to expand...
Click to collapse
The code I used works great and one of the textboxes it fills will be a number. 1, 2 or 3. I want to display an image in the row also. I have 3 images in my drawables folder (one.png, two.png, three.png). I want to display the correct one depending on the number that was returned to R.id.txtStatus
What i would do is as follows:
Create a new class named Item for example which contains five attributes: four strings which contain the texts for the four textviews and one int where you save the image number.
Then you need a custom implementation of ArrayAdapter, using an arraylist of Items (or however you name your holder class).
In the adapter you can then assign the textviews their corresponding contents by using item.getStatus etc and you check the int that contains the image number and assign the imageview the correct image.
Edit:
Here is the implementation i am using in my app with "Row" as my custom holder class, i don't use an imageview but you can easily add this:
package com.masrepus.vplanapp;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.io.Serializable;
import java.util.ArrayList;
/**
* Created by samuel on 27.07.14.
*/
public class MySimpleArrayAdapter extends ArrayAdapter implements Serializable {
private final ArrayList<Row> list;
/**
* Constructor for the custom arrayadapter
*
* @param activity used for method-calls that require a context parameter
* @param list a Row ArrayList that has to be parsed into a listview
*/
public MySimpleArrayAdapter(Activity activity, ArrayList<Row> list) {
super(activity, R.layout.vplan_list, list);
this.list = list;
}
/**
* Puts the klasse, stunde and status attributes of a row object into the right textviews in a listview item
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
ViewHolder view;
if (rowView == null) {
//get a new instance of the row layout view
rowView = View.inflate(getContext(), R.layout.vplanlist_element, null);
//hold the view objects in an object, that way they don't need to be "re- found"
view = new ViewHolder();
view.klasseView = (TextView) rowView.findViewById(R.id.grade);
view.stundeView = (TextView) rowView.findViewById(R.id.stunde);
view.statusView = (TextView) rowView.findViewById(R.id.status);
rowView.setTag(view);
} else {
view = (ViewHolder) rowView.getTag();
}
//put data to the views
Row item = list.get(position);
view.klasseView.setText(item.getKlasse());
view.stundeView.setText(item.getStunde());
view.statusView.setText(item.getStatus());
return rowView;
}
/**
* Used to distribute klasse, stunde, status to the right textviews
*/
protected static class ViewHolder {
protected TextView klasseView;
protected TextView stundeView;
protected TextView statusView;
}
}
Click to expand...
Click to collapse
--------------------
Phone: Nexus 4
OS: rooted Lollipop LRX21T
Bootloader: unlocked
Recovery: TWRP 2.8.2.0
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;
}