Monday, December 29, 2014

Android dev study notes - 1


  • Dailer
------ code snippet ------
       String number = tv.getText().toString();  // get No from an edittext
    Intent it = new Intent(Intent.ACTION_DIAL);
    it.setData(Uri.parse("tel:"+number));
    this.startActivity(it);

------- permission ------
<uses-permission android:name="android.permission.CALL_PHONE"/>
  • SMS
------ code snippet ------
        String mNo = evMobileNo.getText().toString();
String content = evSMSContent.getText().toString();
SmsManager sm = SmsManager.getDefault();
ArrayList<String> text = sm.divideMessage(content);  // just in case message is too long
for (String s : text) {
sm.sendTextMessage(mNo, null, s, null, null);
}
Toast.makeText(MainActivity.this, "sent success",
Toast.LENGTH_LONG).show();

------- permission ------
 <uses-permission android:name="android.permission.SEND_SMS" />
  • Save/Read File to/from memory card

public void savePrivateMode(String fileName, String fileContent)
throws Exception {
FileOutputStream out = context.openFileOutput(fileName,
Context.MODE_PRIVATE);
out.write(fileContent.getBytes());
out.close();
}

The mode is Context.MODE_PRIVATE, only the app who created the file is allowed to access the file, no one else is allowed to access it.
The file permission is -rw-rw----
If save the same file name again and again, the content will be overwrited.

And the location of this file is /data/data/<app package name>/files/<file name> in the memory card.

public void saveAppendMode(String fileName, String fileContent)
throws Exception {
FileOutputStream out = context.openFileOutput(fileName,
Context.MODE_APPEND);
out.write(fileContent.getBytes());
out.close();
}

The mode is Context.MODE_APPEND, only the app who created the file is allowed to access the file, no one else is allowed to access it.
The file permission is -rw-rw----
If save the same file name again and again, the content will be appended.

And the location of this file is /data/data/<app package name>/files/<file name> in the memory card.

public String read(String fileName) throws Exception {
FileInputStream input = context.openFileInput(fileName);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
                // omit i/o close() method
return new String(out.toByteArray());
}

This method is allowed to read file under  /data/data/<app package name>/files/<file name> at the same app.

If another app needs to access this file in this app, the code is as follows,

public String read(String fileName) throws Exception {
                String fn = "/data/data/<package name>/files/"+fileName;
                File file = new File(fn);
                // should NOT be  context.openFileInput(fileName); it is for memory card
FileInputStream input = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
                // omit i/o close() method
return new String(out.toByteArray());
}

If the mode is Context.MODE_PRIVATE or Context.MODE_APPEND, cannot read file from external app, unless the external app set the mode as Context.MODE_WORLD_READABLE when saving the file.
If an app is open the file to be read/write for external apps, when save a file, the file mode need to set as Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE


  • Save/Read File to/from SD card
before read/write file to/from SD card, need to check the card is available or not by the following,

if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))

file location:
File file = new File(Environment.getExternalStorageDirectory(),fileName);

Enviroment.getExternalStorageDirectory() points to /mnt/sdcard for current android os, and points to /sdcard for very older os version.
  • SharedPreferences
let's say user chooses preferred color and font size for UI setting, then save such info to SharedPreferences.

public void save(String color, int fontSize) {
SharedPreferences sf = context.getSharedPreferences("custprefer",
Context.MODE_PRIVATE);
Editor edit = sf.edit();
edit.putString("color", color);
edit.putInt("fontSize", fontSize);
edit.commit();
}

in the end, a new file custprefer.xml will be generated at folder /data/data/<package>/shared_prefs/custprefer.xml  as follows,

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="fontSize" value="12" />
<string name="color">red</string>
</map>

  • SQLite
package com.example.dbdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "demo.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person(pid integer primary key autoincrement,name varchar(20))");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

From the constructor, set db version is 1, and db file name is demo.db, onCreate() will be triggered if demo.db does not exit.

This is a test class to trigger onCreate() method.
package com.example.dbdemo.test;

import com.example.dbdemo.DBHelper;

import android.test.AndroidTestCase;

public class DBHelperTester extends AndroidTestCase {

public void testDB() throws Exception {
DBHelper helper = new DBHelper(this.getContext());
helper.getWritableDatabase();
}
}

then the new demo.db file will be found in folder /data/data/<package>/databases/demo.db, use the tools sqlite expert to check the table is created successfully.

Now change above code to make the version to 2 in the constructor,and add some db changes in method onUpgrade() which will be triggered if db version changes

package com.example.dbdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {

public DBHelper(Context context) {
super(context, "demo.db", null, 2);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table person(pid integer primary key autoincrement,name varchar(20))");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("create table teacher(tid integer primary key autoincrement,name varchar(20))");
db.execSQL("alter table person add address varchar(20) null");
}

}

run above test method again, found a new table and a new field are created.

This is a simple class to insert a record

public class MyDbService {

private DBHelper dbHelper;

public MyDbService(DBHelper dbHelper) {
this.dbHelper = dbHelper;
}

public void save() {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL("insert into person (name) values (?)",
new Object[] { "testuser" });
}
}
  • TBC

Spring MVC restful tips - 406 error, cannot put email in the url


  • 406 not acceptable error

need to add the following configuration

<mvc:annotation-driven
content-negotiation-manager="contentNegotiationManager">
</mvc:annotation-driven>

<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
<!-- resolve not acceptable issue -->
<bean id="jacksonMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>text/xml;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>

this is jackson dependencies:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${jackson.version}</version>
</dependency>

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<spring.core.version>3.2.4.RELEASE</spring.core.version>
<log.version>1.2.17</log.version>
<hibernate.version>3.6.10.Final</hibernate.version>
<jackson.version>1.9.13</jackson.version>
</properties>


  • avoid URI using dots to be truncated
  @RequestMapping( value = "/users/{email:.+}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE )