Archive | VAS Apps RSS for this section

Publishing Open Source Project: Android Coming Holidays

git remote add origin git@github.com:antonrifco/Coming-Holidays.git

After finalizing fix and minor enhancement, now Coming Holidays Android App is Version 1.0. Good news, it’s also an Open Source Project now. It is hosted on https://github.com/antonrifco/Coming-Holidays
or, directly from github console: git@github.com:antonrifco/Coming-Holidays.git

Actually the application itself is quite simple. I made it only one week on after office hours. Even so, I hope by publishing it as Open Source, I can share the experience of developing Android Application with other Android die Hard fans.

Description about Coming Holidays v1.0 ( https://market.android.com/details?id=net.antonrifco.comingholidays ):

Waiting for Holidays?!? Install Coming Holidays App to track upcoming Holidays!
Coming Holidays helps users to track nearest Holidays (Public and Private one). A nice-looking countdown should inform users how near they are from a free fooling around day. Finally, an annoying daily alert at stress-peak time (2p.m) will persistently remind People that life is not Yet over. There’s still Upcoming Holidays..

About Coming Holidays Version 1.0:
-. Fix “Alert get up on random time”
-. Fix “Nearest holiday should always days after today 00:00”
-. Fix Incomplete Deleting leave problem
-. Project hosted as Open Source on https://github.com/antonrifco/Coming-Holidays yeayy!

Announcement Coming Holidays Android App Release Candidate-1

Image

Waiting for Holidays?!? Install Coming Holidays App to track upcoming Holidays!
Coming Holidays helps users to track nearest Holidays (Public and Private one). A nice-looking countdown should inform users how near they are from a free fooling-around day. Finally, an annoying daily alert at stress-peak time (2p.m) will persistently remind People that life is not Yet over. There’s still Upcoming Holidays..

What’s in this version:

Version 0.9:

  • Keep track Holidays from following countries: China, Germany, France, Hongkong, Indonesia, India, Japan, South Korea, Singapore, UK, US
  • Automatically detect location (country) based on MCC of GSM (No GPS required)
  • Track upcoming Holidays and give Countdown
  • Able to add Personal Leaves to the list of Holidays
  • Give alert for Holidays until the next 3 days. Alert Notification will start at 2p.m local time.
  • Fun!

Download at: Android Market

Analisa terhadap bug “Could not fetch content, sorry” dari Twitter SMS Telkomsel, Axis, dan Esia

Belakangan, beberapa teman yang saya follow pada jaringan sosial Twitter memposting kalimat yang unik dan serupa pada timeline mereka, yakni “Could not fetch content, sorry”

Beberapa hal seragam pada “pelaku” postingan tersebut ialah:
1. Posting dikirim via txt, sebuah aplikasi twitter SMS kerja sama antara twitter.com, third party Content Provider, dan Operator telekomunikasi.
2. Kebanyakan dari mereka merasa pernah terdaftar pada layanan di atas. Entah melalui pendaftaran REG sms, maupun paket bundling nomor prepaid.
3. Dari yang saya tahu, teman-teman yang mengalami masalah ini, mendaftar pada layanan twitter SMS dari Telkomsel, Axis, dan Esia (atau ada yang lain?)

Berikut adalah analisa blackbox yang saya pikirkan (tanpa mengetahui arsitektur solusi sesungguhnya masing2 operator):
-. Solusi twitter SMS ini dikerjakan oleh third party dari Operator telekomunikasi yang bekerja sama dengan twitter.com
-. Sms MO (request) yang masuk dari pelanggan (melalui suatu shortcode) dilanjutkan operator ke Content Provider via API HTTP
-. Sebagian atau seluruh solusi SMS gateway dari Operator ini menggunakan Kannel

Dari mana sesungguhnya text “Could not fetch content, sorry” ini berasal?

Text tersebut sebenarnya berasal dari Kannel. Ini merupakan known bug dari aplikasi SMS gateway tersebut ketika berusaha untuk mem-forward isi sms ke sebuah HTTP URL, namun webserver-nya me-redirect request ke alamat lain. Sampai saat ini, Kannel belum bisa menangani use case tersebut ( www.kannel.org/pipermail/users/2006-August/000356.html)

Jadi, untuk menuju pada solusinya, ketiga Operator tersebut (atau ada yang lain?) harus menganalisa apakah use case yang disebutkan di atas terdapat pada layanan Twitter SMS ini.

-just my 2cents-
CMIIW

How to send MMS using SOAP MM7

Here’s an article describing how to implement MM7 connection to your service provider’s MMS center.

Perhaps for the start, I would like to tell the history of MM7 implementation. At first most vendors implement MM7 connection to send MMS content using their own proprietary library. E.g. One of biggest vendor uses PAP (Push Application Protocol) to send standard MMS content from VAS App to the subscriber. Basically it’s built on standard MIME Message class.

Good news, nowadays many vendor has dealt to implement their MM7 connection from VAS App using standard SOAP connection. Moreover, under this SOAP implementation, they can support SMIL class message. It’s a standard Markup Language to implement layouting in MMS content delivery. Also, using this SMIL implementation, we can present multi pages MMS that showed one after another with predefined duration.

Here’s the example of SMIL tag:

<smil xmlns=”http://www.w3.org/2001/SMIL20/Language”&gt;

<head>

<layout>      <root-layout width=”160″ height=”120″/>

<region id=”upper” top=”0″ left=”0″ />      <region id=”lower” top=”100″ left=”0″ />

</layout>

</head>

<body>

<par dur=”3s”>      <text region=”lower” src=”text0.txt” />      <img region=”upper” src=”zzz.JPG” />      <audio src=”01000000478.mid” />    </par>

<par dur=”5s”>      <text region=”lower” src=”text3.txt” />      <img region=”upper” src=”zzz.JPG” />      <audio src=”01000000478.mid” />    </par>

</body></smil>

For the art of software programming, I will put my example code here in Java from scratch. Actually you can code SOAP Message in Java using SAAJ standard library.

At first you need to make sure that you have variables that define the connection to the MMSC. They are MMSC IP, Port, VASID, VASPID, and Password. It can be defined from file configuration.

String mmsc_ip = Utility.getConfiguration(“MmscSoap.ip”); int mmsc_port = Integer.parseInt(Utility.getConfiguration(“MmscSoap.port”)); String mmsc_pwd = Utility.getConfiguration(“MmscSoap.pwd”); String mmsc_vaspid = Utility.getConfiguration(“MmscSoap.vaspid”); String mmsc_vasid = Utility.getConfiguration(“MmscSoap.vasid”);

Basically, SOAP connection is a like a HTTP socket connection using POST method. So, we need to define the HTTP header:

String header = “POST /vas_soap HTTP/1.1\n” +

“Host: “+mmsc_ip+”:”+mmsc_port+”\n” +

“Authorization: Basic “+toBASE64(mmsc_pwd,0)+”\n” +

“User-Agent: mm7submit/0.1\n” +   “Content-Type: multipart/related; boundary=\”–_=_NextPart_21B2_1EE0_E97.2593\”; type=\”text/xml\”; start=\”<mm7submit@localhost>\”\n” +

“Content-Length: “+data.length()+”\n” +

“Accept: */*\n” +

“SOAPAction: \”\”\n\n”;

You can specify either the connection is using HTTP/1.1 or HTTP/1.0. Also you can add more HTTP header there, depends on the server’s need.

MMS contents are simply the texts, multimedia contents (audio, video, and picture), and SMIL tags that defined how the contents presented. Multimedia contents are supposed to be sent as format Base64 Encoding. So, we’re going to put

import sun.misc.BASE64Encoder;

in top of the file.

For the contents’ location, this tutorial will search the contents based on the URL specified in the SMIL tags. Normally, SMIL tag should not be filled with URL. But, to simplify the solution, this code will accept SMIL with URL. Then it will string-process the SMIL to extracts the URL of the contents. Then, will put the content name based on the hashcode of the URL specified.

<img region=”upper” src=”http://www.google.com/images/logos/ps_logo2.png” />

will be replaced with:

<img region=”upper” src=”c63893e3754e335896165ed94d6ce67e” />

The code will differs multimedia into three different types (audio, video, and image). Then it will assume the Content-Type based on the suffix of the URL path.

The code will string-process the smil to get contents’ URL:

smil = URLDecoder.decode(smil);

Vector<String> gambars = new Vector<String>();

Pattern p = Pattern.compile( “<img[ *]region=.[^<>’\”]*.[ *]src=[‘\”]([^’\”<>]*)[‘\”][ *]/>”,  Pattern.DOTALL);
Matcher matcher = p.matcher(smil);

while (matcher.find()) {

String temp = matcher.group(1);

if(!gambars.contains(temp))

gambars.add(temp);

}

Vector<String> suaras = new Vector<String>();

p = Pattern.compile(“<audio[ *]src=[‘\”]([^’\”<>]*)[‘\”][ *]/>”, Pattern.DOTALL);
matcher = p.matcher(smil);

while (matcher.find()) {

String temp = matcher.group(1);

if(!suaras.contains(temp))

suaras.add(temp);

}

Vector<String> videos = new Vector<String>();

p = Pattern.compile(“<video[ *]src=[‘\”]([^’\”<>]*)[‘\”][ *]/>”, Pattern.DOTALL);
matcher = p.matcher(smil);

while (matcher.find()) {

String temp = matcher.group(1);

if(!videos.contains(temp))

videos.add(temp);

}

And here’s how we build the main HTTP body:

String data = “”+ “—-_=_NextPart_21B2_1EE0_E97.2593\n” +

“Content-Type: application/xml\n” +

“Content-ID: <mm7submit@localhost>\n” + “\n” +

“<?xml version=\”1.0\” encoding=\”UTF-8\”?>\n” +

“<SOAP-ENV:Envelope xmlns:SOAP-ENV=\”http://schemas.xmlsoap.org/soap/envelope/\” xmlns=\”http://www.3gpp.org/ftp/Specs/archive/23_series/23.140/schema/REL-5-MM7-1-2\”>\n” +

“<SOAP-ENV:Header>\n” +

” <TransactionID SOAP-ENV:mustUnderstand=\”1\”>vas00001</TransactionID>\n” +

“</SOAP-ENV:Header>\n” +

“<SOAP-ENV:Body>\n” +

“<SubmitReq>\n” +

“<MM7Version>5.3.0</MM7Version>\n” +

“<SenderIdentification>\n” +

” <VASPID>”+mmsc_vaspid+”</VASPID>\n” +

” <VASID>”+mmsc_vasid+”</VASID>\n” +

” <SenderAddress><Number>”+ sender +”</Number></SenderAddress>\n” +

” </SenderIdentification>\n” +

” <Recipients>\n” +

” <To><Number>”+to+”</Number></To>\n” +

” </Recipients>\n” + ” <MessageClass>Informational</MessageClass>\n” +

” <DeliveryReport>false</DeliveryReport>\n” +

” <ReadReply>false</ReadReply>\n” +

” <Priority>Normal</Priority>\n” +

” <Subject>”+subject+”</Subject>\n” +

” <ChargedParty>Sender</ChargedParty>\n” +

” <Content href=\”cid:content@localhost\” allowAdaptations=\”true\”/>\n” +

“</SubmitReq>\n” + “</SOAP-ENV:Body>\n” +

“</SOAP-ENV:Envelope>\n\n” + “—-_=_NextPart_21B2_1EE0_E97.2593\n” +

“Content-Type: multipart/mixed; boundary=\”—-_=_NextPart_220A_232_2517.E38\”\n” +

“Content-ID: <content@localhost>\n” + “\n”;

String texts[] = text.split(“<next/>”);

for(int g = 0; g < texts.length; g++){

data+=”——_=_NextPart_220A_232_2517.E38\n” +

“Content-Type: text/plain; charset=\”us-ascii\”; Name=text”+g+”.txt\n” +

“Content-ID: <text”+g+”.txt>\n” +

“Content-Location: text”+g+”.txt\n\n” +

texts[g]+”\n” + “\n”;

}
String gambar_name[] = null;

int max_depth = 0;

if(gambars != null){

Log.getLogger().debug(“Parse image…”);

gambar_name = new String[gambars.size()];

for(int g = 0; g < gambars.size(); g++){

if(max_depth == g) max_depth++;

URL sourceURL1 = null;

try {

String url = gambars.get(g).replaceAll(” ” , “+”);

sourceURL1 = new URL(url);

byte[] byteArray = null;

HttpClient http = new HttpClient();

http.getParams().setSoTimeout(timeout);

if (proxyHost != null && proxyPort != null) {

Log.getLogger().info(“Using proxy = ” + proxyHost + “:” + proxyPort);

http.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort));

}

HttpMethod method = new GetMethod(url);

http.getParams().setParameter(“http.socket.timeout”, timeout);

http.getParams().setParameter(“http.connection.timeout”, timeout);

method.getParams().setParameter(“http.socket.timeout”, timeout);

method.getParams().setParameter(“http.connection.timeout”, timeout);

try {

int statusCode = http.executeMethod(method);

if (statusCode != HttpStatus.SC_OK && statusCode != 202) {

byteArray = null; Log.getLogger().debug(“Status HTTP:”+ statusCode);

} else { byteArray = method.getResponseBody(); }

} catch (Exception e) { Log.getLogger().error(“Error in httpGet”, e); byteArray = null; }

finally { method.releaseConnection(); }

String filename = sourceURL1.getFile();

BASE64Encoder encoder = new BASE64Encoder();

if(filename.contains(“/”)) filename = filename.substring(filename.lastIndexOf(“/”)+1);

gambar_name[g] = Integer.toString(filename.hashCode());

smil = replace(smil, url, gambar_name[g]);

String format = filename.substring(filename.lastIndexOf(“.”)).toLowerCase();

String type = “image/png”;

if(format.equals(“.gif”)) type = “image/gif”;

else if(format.equals(“.jpg”) || format.equals(“.jpeg”)) type = “image/jpeg”;

else if(format.equals(“.png”)) type = “image/png”;

else if(format.equals(“.tiff”)) type = “image/tiff”;

data+=”——_=_NextPart_220A_232_2517.E38\n” +

“Content-Type: “+type+”;

Name=”+gambar_name[g]+”\n” +

“Content-Transfer-Encoding: base64\n” +

“Content-ID: <“+gambar_name[g]+”>\n” +

“Content-Location: “+gambar_name[g]+”\n\n” +

encoder.encode(byteArray)+”\n” + “\n”;

} catch (Exception e) {// TODO Auto-generated catch block e.printStackTrace(); }

}

}

String suara_name[] = null;

if(suaras != null){

suara_name = new String[suaras.size()];

for(int g = 0; g < suaras.size(); g++){

if(max_depth == g) max_depth++;

URL sourceURL1 = null;

try {

String url = suaras.get(g).replaceAll(” ” , “+”);

sourceURL1 = new URL(url);

byte[] byteArray = null;

HttpClient http = new HttpClient();

http.getParams().setSoTimeout(timeout);

if (proxyHost != null && proxyPort != null) {

Log.getLogger().info(“Using proxy = ” + proxyHost + “:” + proxyPort);

http.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort));

}

HttpMethod method = new GetMethod(url);

http.getParams().setParameter(“http.socket.timeout”, timeout);

http.getParams().setParameter(“http.connection.timeout”, timeout);

method.getParams().setParameter(“http.socket.timeout”, timeout);

method.getParams().setParameter(“http.connection.timeout”, timeout);

try {

int statusCode = http.executeMethod(method);

if (statusCode != HttpStatus.SC_OK && statusCode != 202) {

byteArray = null;

Log.getLogger().debug(“Status HTTP:”+ statusCode);

} else { byteArray = method.getResponseBody(); }

} catch (Exception e) { Log.getLogger().error(“Error in httpGet”, e); byteArray = null; }

finally { method.releaseConnection(); }

String filename = sourceURL1.getFile();

BASE64Encoder encoder = new BASE64Encoder();

if(filename.contains(“/”)) filename = filename.substring(filename.lastIndexOf(“/”)+1);

suara_name[g] = Integer.toString(filename.hashCode());

smil = replace(smil, suaras.get(g), suara_name[g]);

String format = filename.substring(filename.lastIndexOf(“.”)).toLowerCase();

String type = “audio/wav”;

if(format.equals(“.wav”)) type = “audio/wav”;

else if(format.equals(“.mid”) || format.equals(“.midi”)) type = “audio/midi”;

else if(format.equals(“.mp3”)) type = “audio/mp3”;

else if(format.equals(“.3gp”)) type = “audio/3gpp”;

else if(format.equals(“.aac”)) type = “audio/aac”;

else if(format.equals(“.amr”)) type = “audio/amr”;

else if(format.equals(“.amr-wb”)) type = “audio/amr-wb”;

else if(format.equals(“.mpeg”) || format.equals(“.mpg”)) type = “audio/mpeg”;

else if(format.equals(“.mp4”)) type = “audio/mp4”;

else if(format.equals(“.rmv”) || format.equals(“.rmvb”)) type = “audio/rmv”;

else continue;

data+=”——_=_NextPart_220A_232_2517.E38\n” +

“Content-Type: “+type+”; Name=”+filename+”\n” +

“Content-Transfer-Encoding: base64\n” +

“Content-ID: <“+filename+”>\n” +

“Content-Location: “+filename+”\n\n” +

encoder.encode(byteArray)+”\n” + “\n”;

} catch (MalformedURLException e) {// TODO Auto-generated catch block e.printStackTrace(); }

catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

String video_name[] = null;

if(videos != null) {

video_name = new String[videos.size()];

for(int g = 0; g < videos.size(); g++) {

if(max_depth == g) max_depth++;

URL sourceURL1 = null;

try {

String url = videos.get(g).replaceAll(” ” , “+”);

sourceURL1 = new URL(url);

byte[] byteArray = null;

HttpClient http = new HttpClient();

http.getParams().setSoTimeout(timeout);

if (proxyHost != null && proxyPort != null) {

Log.getLogger().info(“Using proxy = ” + proxyHost + “:” + proxyPort);

http.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort));

}

HttpMethod method = new GetMethod(url);

http.getParams().setParameter(“http.socket.timeout”, timeout);

http.getParams().setParameter(“http.connection.timeout”, timeout);

method.getParams().setParameter(“http.socket.timeout”, timeout);

method.getParams().setParameter(“http.connection.timeout”, timeout);

try {

int statusCode = http.executeMethod(method);

if (statusCode != HttpStatus.SC_OK && statusCode != 202) {

byteArray = null;

Log.getLogger().debug(“Status HTTP:”+ statusCode);

} else { byteArray = method.getResponseBody(); }

}

catch (Exception e) { Log.getLogger().error(“Error in httpGet”, e); byteArray = null; }

finally { method.releaseConnection(); }

String filename = sourceURL1.getFile();

BASE64Encoder encoder = new BASE64Encoder();

if(filename.contains(“/”)) filename = filename.substring(filename.lastIndexOf(“/”)+1);

video_name[g] = Integer.toString(filename.hashCode());

smil = replace(smil, videos.get(g), video_name[g]);

String format = filename.substring(filename.lastIndexOf(“.”)).toLowerCase();

String type = “video/mpeg”;

if(format.equals(“mpeg”) || format.equals(“mpg”)) type = “video/mpeg”;

else if(format.equals(“.3gp”) || format.equals(“.3gpp”)) type = “video/3gpp”;

else if(format.equals(“.mp4”)) type = “video/mp4”;

else if(format.equals(“.rmv”) || format.equals(“.rmvb”)) type = “video/rmv”;

else continue;

data+=”——_=_NextPart_220A_232_2517.E38\n” +

“Content-Type: “+type+”; Name=”+filename+”\n” +

“Content-Transfer-Encoding: base64\n” +

“Content-ID: <“+filename+”>\n” +

“Content-Location: “+filename+”\n\n” +

encoder.encode(byteArray)+”\n” + “\n”;

} catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); }

catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }

}

}

if(use_smil != null && !use_smil.equals(“”) && !use_smil.equals(“false”)) {

if(use_smil.toLowerCase().equals(“default”)){

smil = “<smil><head><layout><root-layout/><region id=\”upper\” top=\”0\” left=\”0\” /><region id=\”lower\” top=\”100\” left=\”0\” /><region id=\”lowest\” top=\”200\” left=\”0\” /></layout></head><body>”;

for(int g = 0; g < max_depth; g++) {

smil += “<par dur=\”3s\”>”;

if(g < texts.length) smil += “<text region=\”lower\” src=\”text”+g+”.txt\” />”;

if(gambar_name != null && g < gambar_name.length) smil += “<img region=\”upper\” src=\””+gambar_name[g]+”\” />”;

if(video_name != null && g < video_name.length) smil += “<video region=\”lowest\” src=”+video_name[g]+” />”;

if(suara_name != null && g < suara_name.length) smil += “<audio src=”+suara_name[g]+” />”;

smil += “</par>”;

}

smil += “</body></smil>”;

}

data += “——_=_NextPart_220A_232_2517.E38\n” +

“Content-Type: application/smil; charset=\”us-ascii\”; Name=smil4.smil\n” +

“Content-ID: <smil4.smil>\n” +

“Content-Location: smil4.smil\n” + “\n” +

“<?xml version=\”1.0\” encoding=\”utf-8\”?>” +

“<!DOCTYPE smil PUBLIC \”-//3GPP//DTD SMIL rel5//EN\” \”http://www.3gpp.org/SMIL20/PSS5\”>” + smil + “\n”;

}

data += “——_=_NextPart_220A_232_2517.E38–\n” + “—-_=_NextPart_21B2_1EE0_E97.2593–\n\n”;

Finally, here’s the code to submit the header + contents (data) to the MMSC using HTTP Socket SOAP:

String dat;

try {

Socket so = new Socket(mmsc_ip, mmsc_port);

BufferedOutputStream bos = new BufferedOutputStream(so.getOutputStream());

String msg = header + data;

BufferedReader br2 = new BufferedReader(new InputStreamReader(so.getInputStream()));

bos.write(msg.getBytes());

bos.flush();

dat = “”;

Log.getLogger().debug(“MmsSOAP request :”+msg);

String temp;

while((temp = br2.readLine()) != null){

dat += temp;

}

so.close();

try {

String a = dat.substring(dat.indexOf(“<StatusCode>”) + 12, dat.indexOf(“</StatusCode>”));

Log.getLogger().info(“MmsSOAP response :”+a);

ret = Integer.parseInt(a);

} catch (Exception e) {  // TODO Auto-generated catch block  ret = -9;  e.printStackTrace();  } }

catch (UnknownHostException e1) { // TODO Auto-generated catch block e1.printStackTrace(); }

catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); }

For the response, normally MMSC will reply response String 1000 as Success.