Hlasovanie cez SMS na WEB stránku
Uvažovali ste už niekedy o tom, ako funguje SMS hlasovanie? V tomto článku si ukážeme, ako sa relatívne jednoducho dá implementovať SMS hlasovanie na váš WEB.
Pár dní dozadu, keď som vyberal tému na nasledujúci článok, som pri počúvaní svojho obľúbeného internetového rádia započul, že zrušili možnosť hlasovať v hitparáde prostredníctvom SMS, nakoľko nastal nejaký problém s niektorým z našich mobilných operátorov. Po nejakom čase ma napadlo, že v prípade, ak SMS-ky majú slúžiť len na hlasovanie a nemajú byť spoplatnené, teoreticky nepotrebujeme mať nejakú formu dohody s operátormi, ani nejaké špeciálne a drahé zariadenie. Otvoril som šuplík v pracovnom stolíku, našiel som tam 3 staré mobilné telefóny s Android-om (ktoré sú síce morálne zastaralé, niektoré rozbité no stále funkčné) a nápad bol na svete.V tomto článku si ukážeme, ako si zo starého mobilu spraviť zariadenie na SMS hlasovanie a prepojíme ho s naším WEB-om.
- Android Studio
- WEB server s podporou PHP
- MySQL databázu
a samozrejme nejaký editor kódu :-)
Sending POST data in Android - stackoverflow.com > link <
Android Network Security Configuration - Codelab > link <
Diskusia
Čo budeme potrebovať?
- Mobilný telefón s Android-om a aktívnou SIM kartou- Android Studio
- WEB server s podporou PHP
- MySQL databázu
a samozrejme nejaký editor kódu :-)
Technické riešenie
Ako už z úvodu článku vyplýva, bude riešenie pozostávať z dvoch častí: WEB stránka a Android aplikácia.WEB bude napojený na MySQL databázu, v ktorej budeme evidovať jednotlivé hlasy a okrem stránky na zobrazenie aktuálneho stavu bude obsahovať aj dátový mostík, ktorým sa budú dáta plniť.Dáta bude plniť Android-ová aplikácia, ktorá bude samotné SMSky prijímať a kontrolovať a platné hlasy posunie ďalej.WEBová časť
Začneme vytvorením nového priečinka pre projekt a v ňom vytvoríme 4 súbory: index.php, add.php, config.php a dblink.php.Ako prvý si vyplníme config.php, do ktorého zadefinujeme prístupové údaje do MySQL databázy:Zdrojový kód:
<?php define("dbserver","localhost");
define("dbname","smsvote");
define("dbuser","--uzivatel--");
define("dbpassword", "--heslo--");?>
Význam jednotlivých údajov je zrejme každému jasný. Máme tam adresu databázového servra, názov databázy, meno a heslo používateľa.V databáze vytvoríme tabuľku records, do ktorej sa budú jednotlivé záznamy zapisovať:define("dbname","smsvote");
define("dbuser","--uzivatel--");
define("dbpassword", "--heslo--");?>
Zdrojový kód:
CREATE TABLE records (
ID INT NOT NULL AUTO_INCREMENT,
PhoneNumber varchar(20) COLLATE utf8_slovak_ci NOT NULL,
VoteValue varchar(1) COLLATE utf8_slovak_ci NOT NULL,
PRIMARY KEY (ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_slovak_ci;
Pokračujeme kódom dblink.php, ktorý bude obsahovať triedu DBLink slúžiacu na komunikáciu s databázou:ID INT NOT NULL AUTO_INCREMENT,
PhoneNumber varchar(20) COLLATE utf8_slovak_ci NOT NULL,
VoteValue varchar(1) COLLATE utf8_slovak_ci NOT NULL,
PRIMARY KEY (ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_slovak_ci;
Zdrojový kód:
<?phpinclude_once("config.php");class DBLink { private static $SQLLink = null; public static function Connect() { try { $dbuser = dbuser;
$dbpassword = dbpassword;
$dbserver = dbserver;
$dbname = dbname; $dsn = "mysql:dbname=$dbname;host=$dbserver;charset=utf8"; DBLink::$SQLLink = new PDO($dsn, $dbuser, $dbpassword);
if (DBLink::$SQLLink) {
DBLink::$SQLLink->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} else {
echo "Chyba pripojenia";
return false;
}
} catch (Exception $e) {
echo "Doslo k chybe " . $e->getMessage();
return false;
} return true;
} //Vratit aktualny stav
public static function GetActualState() {
$Ret = []; $SQL = "SELECT VoteValue, COUNT(*) as Cnt FROM records WHERE VoteValue in (\"A\", \"B\", \"C\", \"D\") GROUP BY VoteValue ORDER BY VoteValue";
$SQLCmd = DBLink::$SQLLink->prepare($SQL);
if ($SQLCmd->execute()) {
while ($Data = $SQLCmd->fetch(PDO::FETCH_ASSOC)) {
$Ret[$Data["VoteValue"]] = $Data["Cnt"];
}
} return $Ret;
} public static function AddRecord($Phone, $VoteValue) {
try {
$SQL = "INSERT INTO records (PhoneNumber, VoteValue) VALUES (:PhoneNumber,:VoteValue)";
$SQLCmd = DBLink::$SQLLink->prepare($SQL);
$SQLCmd->bindParam(":PhoneNumber", $Phone, PDO::PARAM_STR);
$SQLCmd->bindParam(":VoteValue", $VoteValue, PDO::PARAM_STR);
return $SQLCmd->execute();
} catch (Exception $e) {
return false;
}
}
}
?>
V triede DBLink máme funkciu Connect, ktorou sa pomocou údajov v config.php pripojíme k databáze, GetActualState ktorá nám vráti aktuálny stav hlasovania do poľa a nakoniec AddRecord, ktorá vloží nový hlas do tabuľky records v databáze.Do súboru index.php vložíme kód na vykreslenie aktuálneho stavu do stránky.$dbpassword = dbpassword;
$dbserver = dbserver;
$dbname = dbname; $dsn = "mysql:dbname=$dbname;host=$dbserver;charset=utf8"; DBLink::$SQLLink = new PDO($dsn, $dbuser, $dbpassword);
if (DBLink::$SQLLink) {
DBLink::$SQLLink->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} else {
echo "Chyba pripojenia";
return false;
}
} catch (Exception $e) {
echo "Doslo k chybe " . $e->getMessage();
return false;
} return true;
} //Vratit aktualny stav
public static function GetActualState() {
$Ret = []; $SQL = "SELECT VoteValue, COUNT(*) as Cnt FROM records WHERE VoteValue in (\"A\", \"B\", \"C\", \"D\") GROUP BY VoteValue ORDER BY VoteValue";
$SQLCmd = DBLink::$SQLLink->prepare($SQL);
if ($SQLCmd->execute()) {
while ($Data = $SQLCmd->fetch(PDO::FETCH_ASSOC)) {
$Ret[$Data["VoteValue"]] = $Data["Cnt"];
}
} return $Ret;
} public static function AddRecord($Phone, $VoteValue) {
try {
$SQL = "INSERT INTO records (PhoneNumber, VoteValue) VALUES (:PhoneNumber,:VoteValue)";
$SQLCmd = DBLink::$SQLLink->prepare($SQL);
$SQLCmd->bindParam(":PhoneNumber", $Phone, PDO::PARAM_STR);
$SQLCmd->bindParam(":VoteValue", $VoteValue, PDO::PARAM_STR);
return $SQLCmd->execute();
} catch (Exception $e) {
return false;
}
}
}
?>
Zdrojový kód:
<?phpinclude_once("dblink.php");DBLink::Connect();
$State = DBLink::GetActualState();?>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
Aktuálne výsledky: <p />
<b>A</b> <?php if (array_key_exists("A", $State)) { echo $State["A"]; } else { echo "0"; } ?><br />
<b>B</b> <?php if (array_key_exists("B", $State)) { echo $State["B"]; } else { echo "0"; }?><br />
<b>C</b> <?php if (array_key_exists("C", $State)) { echo $State["C"]; } else { echo "0"; }?><br />
<b>D</b> <?php if (array_key_exists("D", $State)) { echo $State["D"]; } else { echo "0"; }?>
</body>
</html>
V kóde sme si include-li dblink.php s ktorého následne zavoláme Connect a pomocou GetActualState vrátime aktuálny stav hlasovania do premennej $State. Obsah premennej následne vypíšeme do stránky.Ako posledný si vyplníme súbor add.php, ktorý bude slúžiť zápis nových hlasov:$State = DBLink::GetActualState();?>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
Aktuálne výsledky: <p />
<b>A</b> <?php if (array_key_exists("A", $State)) { echo $State["A"]; } else { echo "0"; } ?><br />
<b>B</b> <?php if (array_key_exists("B", $State)) { echo $State["B"]; } else { echo "0"; }?><br />
<b>C</b> <?php if (array_key_exists("C", $State)) { echo $State["C"]; } else { echo "0"; }?><br />
<b>D</b> <?php if (array_key_exists("D", $State)) { echo $State["D"]; } else { echo "0"; }?>
</body>
</html>
Zdrojový kód:
<?php$Password = filter_input(INPUT_POST, "pw", FILTER_SANITIZE_SPECIAL_CHARS);
$Phone = filter_input(INPUT_POST, "phone", FILTER_SANITIZE_SPECIAL_CHARS);
$VoteValue = filter_input(INPUT_POST, "value", FILTER_SANITIZE_SPECIAL_CHARS);if ($Password != "heslo")
{
echo "Access Denied";
exit;
}if ($Phone == "")
{
echo "No phone number";
exit;
}switch ($VoteValue)
{
case "A":
case "B":
case "C":
case "D":
{
include_once("dblink.php");
DBLink::Connect();
if (DBLink::AddRecord($Phone, $VoteValue))
{
echo "OK";
}
else
{
echo "Action failed";
}
}break;
default:
{
echo "Invalid value";
}break;
}?>
Ako už z kódu vyplýva, údaje do skriptu "pritečú" metódou POST. Pre jednoduchosť kódu sa nejako extra nezaoberám bezpečnosťou, samotný request bude autorizovaný zadaním hesla v premennej "pw". V reálnom kóde by sme mohli použiť nejaký login, plávajúce kódy alebo niečo podobné. Skontrolujeme správnosť údajov a následne zavoláme funkciu AddRecord, ktorou hlas zapíšeme.Stránku môžeme otestovať otvorením v prehliadači a funkčnosť pridávania nových záznamov napríklad pomocou programu Postman.$Phone = filter_input(INPUT_POST, "phone", FILTER_SANITIZE_SPECIAL_CHARS);
$VoteValue = filter_input(INPUT_POST, "value", FILTER_SANITIZE_SPECIAL_CHARS);if ($Password != "heslo")
{
echo "Access Denied";
exit;
}if ($Phone == "")
{
echo "No phone number";
exit;
}switch ($VoteValue)
{
case "A":
case "B":
case "C":
case "D":
{
include_once("dblink.php");
DBLink::Connect();
if (DBLink::AddRecord($Phone, $VoteValue))
{
echo "OK";
}
else
{
echo "Action failed";
}
}break;
default:
{
echo "Invalid value";
}break;
}?>
Test request-u v Postman
Stránka s výsledkami
Mobilná aplikácia
Stránku už máme vytvorenú, pokračujeme Android-ovou aplikáciou.Android má z bezpečnostných dôvodov obmedzenia, ktoré limitujú možnosti aplikácii pracovať s SMSkami. Preto je našu aplikáciu potrebné nastaviť ako štandardnú na správu SMS. (Keďže sa jedná o starý mobil, ktorý bude slúžiť len ako SMS brána, by to nemalo vadiť).Otvoríme si Android Studio a vytvoríme si nový projekt sk.codeblog.smsvote s jednou hlavnou Activity-ou, nazvanou MainActivity.Do súboru AndroidManifest.xml vložíme potrebné oprávnenia a zadefinujeme časti, ktoré od nás Android bude vyžadovať.Zdrojový kód:
...
<uses-feature android:name="android.hardware.telephony" android:required="true" /> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
... <application
...
android:networkSecurityConfig="@xml/network_security_config"
> ... <!-- BroadcastReceiver prijimajuci SMS spravy-->
<receiver android:name=".SmsReceiver"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver> <!-- BroadcastReceiver prijimajuci MMS spravy-->
<receiver android:name=".MmsReceiver"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver> <!-- Activity na odosielanie SMS/MMS-->
<activity android:name=".SendToActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</activity> <!-- Service na "quick response"-->
<service android:name=".RespondViaMessage"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service> </application>
Zadefinovali sme si BroadcastReceivery pre prichádzajúceho SMS a MMS správy, Activity pre odosielanie správ a servis pre "quick response". Reálne budeme využívať len BroadcastReceiver pre SMSky, avšak aplikácia musí (aspoň formálne) všetky tieto možnosti obsluhovať, aby ju systém vyhodnotil ako vhodnú na zvolenie za štandardnú aplikáciu pre SMS.V časti application sme si nalinkovali súbor network_security_config.xml, ktorý bude obsahovať nastavenia bezpečnosti pre sieť. Toto nastavenie môžete vynechať v prípade, ak budete pristupovať na WEB pomocou zabezpečného protokolu https. Android z bezpečnostných dôvodov nedovolí nešifrovanú komunikáciu, pokiaľ to aplikácia nemá explicitne povolené. Ja mám stránku na localhost-e kde šifrovanie nepoužívam, čiže musím takúto komunikáciu povolit.Pre povolenie takejto komunikácie vytvoríme súbor network_security_config.xml v priečinku res/xml s nasledujúcim kódom:<uses-feature android:name="android.hardware.telephony" android:required="true" /> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
... <application
...
android:networkSecurityConfig="@xml/network_security_config"
> ... <!-- BroadcastReceiver prijimajuci SMS spravy-->
<receiver android:name=".SmsReceiver"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver> <!-- BroadcastReceiver prijimajuci MMS spravy-->
<receiver android:name=".MmsReceiver"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver> <!-- Activity na odosielanie SMS/MMS-->
<activity android:name=".SendToActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</activity> <!-- Service na "quick response"-->
<service android:name=".RespondViaMessage"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service> </application>
Zdrojový kód:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:android="http://schemas.android.com/apk/res/android">
<base-config cleartextTrafficPermitted="true" >
</base-config>
</network-security-config>
Pokračujeme vytvorením súborov s výkonným kódom (Rátam s tým, že MainActivity.java už bola vytvorená pri založení projektu.): Constants.java, Functions.java, SmsReceiver.java, MmsReceiver.java, PostRequest.java, RespondViaMessage.java a SendToActivity.java. Najskôr si vyplníme tie súbory, ktoré budú obsahovať "prázdny kód", resp kód nevyhnutný na to, aby mohla byť aplikácia skompilovaná a obsahovala to, čo máme definované v AndroidManifest.xml.SendToActivity.java<network-security-config xmlns:android="http://schemas.android.com/apk/res/android">
<base-config cleartextTrafficPermitted="true" >
</base-config>
</network-security-config>
Zdrojový kód:
package sk.codeblog.smsvote;import android.app.Activity;public class SendToActivity extends Activity {}
MmsReceiver.javaZdrojový kód:
package sk.codeblog.smsvote;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;public class MmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) { }
}
RespondViaMessage.javaimport android.content.Context;
import android.content.Intent;public class MmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) { }
}
Zdrojový kód:
package sk.codeblog.smsvote;import android.app.IntentService;
import android.content.Intent;public class RespondViaMessage extends IntentService {
public RespondViaMessage() {
super(RespondViaMessage.class.getName()); setIntentRedelivery(false);
} @Override
protected void onHandleIntent(Intent intent) { }
}
Pokračujeme si zadefinovaním konštant v súbore Constants.java, ktoré budeme ďalej používať ďalej:import android.content.Intent;public class RespondViaMessage extends IntentService {
public RespondViaMessage() {
super(RespondViaMessage.class.getName()); setIntentRedelivery(false);
} @Override
protected void onHandleIntent(Intent intent) { }
}
Zdrojový kód:
package sk.codeblog.smsvote;public class Constants {
public static final String ACTION_VOTE_RECEIVED = "SK.CODEBLOG.SMSVOTE.ACTION_VOTE_RECEIVED";
public static final String VALUE_PHONE_NUMBER = "SK.CODEBLOG.SMSVOTE.VALUE_PHONE_NUMBER";
public static final String VALUE_MESSAGE = "SK.CODEBLOG.SMSVOTE.VALUE_VOTE";
public static final String VALUE_ISVALID = "SK.CODEBLOG.SMSVOTE.VALUE_ISVALID"; public static final String PDUS = "pdus"; public static final String VOTE_URL ="http://adresa/smsvote/add.php"; //<- Sem vyplnte adresu Vasej stranky
public static final String VOTE_PASSWORD = "heslo";
public static final String VOTE_KEY = "CODEBLOG ";
public static final String VOTE_SUCCESS_RESPONSE = "OK";}
Prvé štyri konštanty budú slúžiť na obsluhu Intentu, ktorým budeme komunikovať s MainActivity, PDUS využijeme pri práci z SMS správami ostatné využijeme pri komunikácii so stránkou add.php.Keď už hovoríme o komunikácii so stránkou, zadefinujeme si kód súboru PostRequest.java, ktorý nám túto komunikáciu zabezpečí.public static final String ACTION_VOTE_RECEIVED = "SK.CODEBLOG.SMSVOTE.ACTION_VOTE_RECEIVED";
public static final String VALUE_PHONE_NUMBER = "SK.CODEBLOG.SMSVOTE.VALUE_PHONE_NUMBER";
public static final String VALUE_MESSAGE = "SK.CODEBLOG.SMSVOTE.VALUE_VOTE";
public static final String VALUE_ISVALID = "SK.CODEBLOG.SMSVOTE.VALUE_ISVALID"; public static final String PDUS = "pdus"; public static final String VOTE_URL ="http://adresa/smsvote/add.php"; //<- Sem vyplnte adresu Vasej stranky
public static final String VOTE_PASSWORD = "heslo";
public static final String VOTE_KEY = "CODEBLOG ";
public static final String VOTE_SUCCESS_RESPONSE = "OK";}
Zdrojový kód:
package sk.codeblog.smsvote;import android.content.Context;
import android.os.AsyncTask;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;public class PostRequest extends AsyncTask <Void, Void, Void> { private Context context; public String votePhone = "";
public String voteValue = ""; public PostRequest(Context context)
{
this.context = context;
} @Override
protected Void doInBackground(Void... arrays) { String data = "pw=" + URLEncoder.encode(Constants.VOTE_PASSWORD)
+ "&phone=" + URLEncoder.encode(votePhone)
+ "&value=" + URLEncoder.encode(voteValue); OutputStream out = null;
BufferedReader in = null; try {
URL url = new URL(Constants.VOTE_URL);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
out = new BufferedOutputStream(urlConnection.getOutputStream()); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
writer.write(data);
writer.flush();
writer.close();
out.close(); int resCode = urlConnection.getResponseCode();
if (resCode >= 200 && resCode <= 299)
{
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
}
else {
in = new BufferedReader(new InputStreamReader(urlConnection.getErrorStream()));
} String Res = in.readLine(); urlConnection.connect(); Boolean isValid = (Res.equals(Constants.VOTE_SUCCESS_RESPONSE)); if (isValid) {
Functions.sendInfoToApp(this.context, isValid, votePhone, "Pridaný hlas pre voľbu: " + voteValue);
}
else
{
String errMsg = "Hlas sa nepodarilo pridať: " + Res;
Functions.sendInfoToApp(this.context, isValid, votePhone, errMsg);
} } catch (Exception e) {
e.printStackTrace();
} return null;
}
}
Kód obsahuje triedu PostRequest, ktorá asynchrónne vyskladá dáta a vykoná POST request na našu stránku. Na základe vrátených dát odošle informáciu o akcii do MainActivity pomocou metódy sendInfoToApp v triede Functions, ktorú si definujeme v súbore Functions.java:import android.os.AsyncTask;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;public class PostRequest extends AsyncTask <Void, Void, Void> { private Context context; public String votePhone = "";
public String voteValue = ""; public PostRequest(Context context)
{
this.context = context;
} @Override
protected Void doInBackground(Void... arrays) { String data = "pw=" + URLEncoder.encode(Constants.VOTE_PASSWORD)
+ "&phone=" + URLEncoder.encode(votePhone)
+ "&value=" + URLEncoder.encode(voteValue); OutputStream out = null;
BufferedReader in = null; try {
URL url = new URL(Constants.VOTE_URL);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
out = new BufferedOutputStream(urlConnection.getOutputStream()); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
writer.write(data);
writer.flush();
writer.close();
out.close(); int resCode = urlConnection.getResponseCode();
if (resCode >= 200 && resCode <= 299)
{
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
}
else {
in = new BufferedReader(new InputStreamReader(urlConnection.getErrorStream()));
} String Res = in.readLine(); urlConnection.connect(); Boolean isValid = (Res.equals(Constants.VOTE_SUCCESS_RESPONSE)); if (isValid) {
Functions.sendInfoToApp(this.context, isValid, votePhone, "Pridaný hlas pre voľbu: " + voteValue);
}
else
{
String errMsg = "Hlas sa nepodarilo pridať: " + Res;
Functions.sendInfoToApp(this.context, isValid, votePhone, errMsg);
} } catch (Exception e) {
e.printStackTrace();
} return null;
}
}
Zdrojový kód:
package sk.codeblog.smsvote;import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsManager;public class Functions { public static void saveVote(Context context, String Phone, String Value)
{
PostRequest PR =new PostRequest(context);
PR.votePhone = Phone;
PR.voteValue = Value;
PR.execute();
} public static void sendSMS(String address, String body)
{
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(address, null, body, null, null);
} public static void sendInfoToApp(Context context, Boolean isValid, String phoneNumber, String message)
{
try { Intent i = new Intent(Constants.ACTION_VOTE_RECEIVED);
i.putExtra(Constants.VALUE_ISVALID, isValid);
i.putExtra(Constants.VALUE_PHONE_NUMBER, phoneNumber);
i.putExtra(Constants.VALUE_MESSAGE, message); PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, android.app.PendingIntent.FLAG_ONE_SHOT);
pi.send(); } catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}}
Kód okrem metódy sendInfoToApp obsahuje aj sendSMS a saveVote. Metóda sendInfoToApp vytvorí Intent, ktorý obsahuje informáciu o konkrétnom hlase a pomocou PendingIntent sa odošle do MainActivity. Metóda saveVote pomocou objektu PostRequest vykoná request na našu stránku a metóda sendSMS odošle SMS správu na zadané číslo.Pokračujeme kódom súboru SmsReceiver.java:import android.content.Context;
import android.content.Intent;
import android.telephony.SmsManager;public class Functions { public static void saveVote(Context context, String Phone, String Value)
{
PostRequest PR =new PostRequest(context);
PR.votePhone = Phone;
PR.voteValue = Value;
PR.execute();
} public static void sendSMS(String address, String body)
{
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(address, null, body, null, null);
} public static void sendInfoToApp(Context context, Boolean isValid, String phoneNumber, String message)
{
try { Intent i = new Intent(Constants.ACTION_VOTE_RECEIVED);
i.putExtra(Constants.VALUE_ISVALID, isValid);
i.putExtra(Constants.VALUE_PHONE_NUMBER, phoneNumber);
i.putExtra(Constants.VALUE_MESSAGE, message); PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, android.app.PendingIntent.FLAG_ONE_SHOT);
pi.send(); } catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}}
Zdrojový kód:
package sk.codeblog.smsvote;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;public class SmsReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras(); if (extras == null) {
return;
} Object[] smsExtras = (Object[]) extras.get(Constants.PDUS); for (Object smsExtra : smsExtras) {
byte[] smsBytes = (byte[]) smsExtra; SmsMessage smsMessage = SmsMessage.createFromPdu(smsBytes); String body = smsMessage.getMessageBody().toUpperCase();
String phoneNumber = smsMessage.getOriginatingAddress(); Boolean isValid = false; if (body.startsWith(Constants.VOTE_KEY))
{
String voteValue = body.substring(Constants.VOTE_KEY.length());
switch (voteValue)
{
case "A":
case "B":
case "C":
case "D":
{
Functions.saveVote(context, phoneNumber, voteValue);
Functions.sendSMS(phoneNumber, "Dakujeme za Vas hlas :-)");
isValid = true;
}break;
}
} if (!isValid)
{
String errMsg = "Neplatny format SMS hlasu.";
Functions.sendInfoToApp(context, false, phoneNumber, errMsg);
Functions.sendSMS(phoneNumber, errMsg);
}
}
}
}
Súbor obsahuje kód, ktorý spracováva prichádzajúce SMS správy. Najskôr načíta dáta zo správy a overí ich správnosť. Správa musí začínať kľúčovím slovom CODEBLOG a následne obsahovať voľbu odosielateľa (A, B, C alebo D). V prípade, ak je obsah správy v poriadku, uloží tento hlas pomocou saveVote, v opačnom prípade len odošle informáciu o tomto hlase do MainActivity. V oboch prípadoch odošle odpovednú SMS hlasujúcemu.Ostala nám už len MainActivity, ktorá bude obsahovať BroadcastReceiver na naše Intenty, ktoré odošleme z funkcie Functions.sendInfoToApp a jeden TextView, v ktorom budeme zobrazovať informáciu o poslednom spracovanom hlase.Nasledujúci kód pridáme do activity_main.xml:import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;public class SmsReceiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras(); if (extras == null) {
return;
} Object[] smsExtras = (Object[]) extras.get(Constants.PDUS); for (Object smsExtra : smsExtras) {
byte[] smsBytes = (byte[]) smsExtra; SmsMessage smsMessage = SmsMessage.createFromPdu(smsBytes); String body = smsMessage.getMessageBody().toUpperCase();
String phoneNumber = smsMessage.getOriginatingAddress(); Boolean isValid = false; if (body.startsWith(Constants.VOTE_KEY))
{
String voteValue = body.substring(Constants.VOTE_KEY.length());
switch (voteValue)
{
case "A":
case "B":
case "C":
case "D":
{
Functions.saveVote(context, phoneNumber, voteValue);
Functions.sendSMS(phoneNumber, "Dakujeme za Vas hlas :-)");
isValid = true;
}break;
}
} if (!isValid)
{
String errMsg = "Neplatny format SMS hlasu.";
Functions.sendInfoToApp(context, false, phoneNumber, errMsg);
Functions.sendSMS(phoneNumber, errMsg);
}
}
}
}
Zdrojový kód:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> <TextView
android:id="@+id/lbl_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Čakám na hlasujúceho"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
MainActivity.java<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> <TextView
android:id="@+id/lbl_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Čakám na hlasujúceho"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Zdrojový kód:
package sk.codeblog.smsvote;import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;public class MainActivity extends AppCompatActivity { private TextView lbl_info = null; private BroadcastReceiver myBroadcastReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Boolean isValid = intent.getBooleanExtra(Constants.VALUE_ISVALID, false);
String phoneNumber = intent.getStringExtra(Constants.VALUE_PHONE_NUMBER);
String message = intent.getStringExtra(Constants.VALUE_MESSAGE);
lbl_info.setTextColor(isValid ? Color.WHITE : Color.RED);
lbl_info.setText(String.format("%s: %s", phoneNumber, message));
}
}; @Override
protected void onResume() {
super.onResume(); IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Constants.ACTION_VOTE_RECEIVED);
registerReceiver(myBroadcastReceiver, intentFilter);
} @Override
protected void onPause() {
super.onPause(); unregisterReceiver(myBroadcastReceiver);
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lbl_info = (TextView) findViewById(R.id.lbl_info); if(ActivityCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS}, 1);
}
}
}
Pri vytvorení Activity otestujeme, či máme povolené odosielanie SMS, ak nie, vypýtame oprávnenie od používateľa. V onResume si zaregistrujeme Receiver na naše Intenty a v kóde Receiveru spracujeme ich dáta a zobrazíme výsledok v TextView.Aplikáciu môžeme skompilovať a nahrať na zariadenie. Po inštalácii nesmieme zabudnúť nastaviť aplikáciu ako štandardnú pre správu SMS (dá sa to nájst v nastaveniach, samotná cesta k nastaveniu sa od verzie systému líši).Funkčnosť môžeme otestovať aj v emulátore, kde pomocou okna Extended controls odošleme do emulátora SMS správu.import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;public class MainActivity extends AppCompatActivity { private TextView lbl_info = null; private BroadcastReceiver myBroadcastReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Boolean isValid = intent.getBooleanExtra(Constants.VALUE_ISVALID, false);
String phoneNumber = intent.getStringExtra(Constants.VALUE_PHONE_NUMBER);
String message = intent.getStringExtra(Constants.VALUE_MESSAGE);
lbl_info.setTextColor(isValid ? Color.WHITE : Color.RED);
lbl_info.setText(String.format("%s: %s", phoneNumber, message));
}
}; @Override
protected void onResume() {
super.onResume(); IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Constants.ACTION_VOTE_RECEIVED);
registerReceiver(myBroadcastReceiver, intentFilter);
} @Override
protected void onPause() {
super.onPause(); unregisterReceiver(myBroadcastReceiver);
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lbl_info = (TextView) findViewById(R.id.lbl_info); if(ActivityCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS}, 1);
}
}
}
Stránka s výsledkami 2
Aplikácia v emulátore
Záverom
Ak sme postupovali správne, tak náš príklad riešenia SMS hlasovania je hotový. Samotné riešenie je okresané len na to nevyhnutné a je tu široký priestor na rozšírenie. Zdrojový kód riešenia môžete sťahovať z > link <Použité zdroje
KitKatSms - Yoshiaki Nakanishi > link <Sending POST data in Android - stackoverflow.com > link <
Android Network Security Configuration - Codelab > link <
Žiadne príspevky v diskusii.
Na prispievanie do diskusie musíte byť prihlásený.