mirror of
https://github.com/cwinfo/envayasms.git
synced 2024-12-04 12:35:32 +00:00
add support for incoming call notifications; fix intermittent NullPointerException when receiving MMS; fix MMS parts with missing filename; allow server to send error messages to be shown in app log (requires updating server library and using EnvayaSMS::get_error_xml()); send device manufacturer/model/sdk version in HTTP User-Agent header; add device_status notification for send_limit_exceeded; add send_status constant for cancelled messages; 30 sec timeouts for outgoing messages; send network type (MOBILE or WIFI) to server; send age (i.e. delay) of incoming message to server; fix crashing bug with checking connectivity; save MMS parts in example server
This commit is contained in:
parent
d93ff76231
commit
f53ccc3cc9
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
local.properties
|
||||||
|
bin
|
||||||
|
gen
|
||||||
|
nbandroid
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.envaya.sms"
|
package="org.envaya.sms"
|
||||||
android:versionCode="17"
|
android:versionCode="18"
|
||||||
android:versionName="2.0.4">
|
android:versionName="2.0.5">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="4" />
|
<uses-sdk android:minSdkVersion="4" />
|
||||||
|
|
||||||
@ -10,6 +10,7 @@
|
|||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_MMS" />
|
<uses-permission android:name="android.permission.RECEIVE_MMS" />
|
||||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||||
@ -22,13 +23,14 @@
|
|||||||
|
|
||||||
<application android:name="org.envaya.sms.App"
|
<application android:name="org.envaya.sms.App"
|
||||||
android:icon="@drawable/icon" android:label="@string/app_name">
|
android:icon="@drawable/icon" android:label="@string/app_name">
|
||||||
<activity android:name=".ui.Main" android:label="@string/app_name">
|
|
||||||
|
<activity android:name=".ui.LogView" android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".ui.Help" android:label="EnvayaSMS : Help">
|
<activity android:name=".ui.Help" android:label="EnvayaSMS : Help">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@ -81,6 +83,9 @@
|
|||||||
<receiver android:name=".receiver.DequeueOutgoingMessageReceiver">
|
<receiver android:name=".receiver.DequeueOutgoingMessageReceiver">
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".receiver.OutgoingMessageTimeout">
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".receiver.OutgoingMessagePoller">
|
<receiver android:name=".receiver.OutgoingMessagePoller">
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
17
ant.properties
Normal file
17
ant.properties
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# This file is used to override default values used by the Ant build system.
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems, as it is
|
||||||
|
# integral to the build system of your project.
|
||||||
|
|
||||||
|
# This file is only used by the Ant script.
|
||||||
|
|
||||||
|
# You can use this to override default values such as
|
||||||
|
# 'source.dir' for the location of your java source folder and
|
||||||
|
# 'out.dir' for the location of your output folder.
|
||||||
|
|
||||||
|
# You can also use it define how the release builds are signed by declaring
|
||||||
|
# the following properties:
|
||||||
|
# 'key.store' for the location of your keystore and
|
||||||
|
# 'key.alias' for the name of the key to use.
|
||||||
|
# The password will be asked during the build when you use the 'release' target.
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
# This file is used to override default values used by the Ant build system.
|
|
||||||
#
|
|
||||||
# This file must be checked in Version Control Systems, as it is
|
|
||||||
# integral to the build system of your project.
|
|
||||||
|
|
||||||
# This file is only used by the Ant script.
|
|
||||||
|
|
||||||
# You can use this to override default values such as
|
|
||||||
# 'source.dir' for the location of your java source folder and
|
|
||||||
# 'out.dir' for the location of your output folder.
|
|
||||||
|
|
||||||
# You can also use it define how the release builds are signed by declaring
|
|
||||||
# the following properties:
|
|
||||||
# 'key.store' for the location of your keystore and
|
|
||||||
# 'key.alias' for the name of the key to use.
|
|
||||||
# The password will be asked during the build when you use the 'release' target.
|
|
||||||
|
|
164
build.xml
164
build.xml
@ -1,79 +1,85 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project name="EnvayaSMS" default="help">
|
<project name="EnvayaSMS" default="help">
|
||||||
|
|
||||||
<!-- The local.properties file is created and updated by the 'android'
|
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||||
tool.
|
It contains the path to the SDK. It should *NOT* be checked into
|
||||||
It contains the path to the SDK. It should *NOT* be checked into
|
Version Control Systems. -->
|
||||||
Version Control Systems. -->
|
<property file="local.properties" />
|
||||||
<property file="local.properties" />
|
|
||||||
|
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||||
<!-- The build.properties file can be created by you and is never touched
|
'android' tool to add properties to it.
|
||||||
by the 'android' tool. This is the place to change some of the
|
This is the place to change some Ant specific build properties.
|
||||||
default property values used by the Ant rules.
|
Here are some properties you may want to change/update:
|
||||||
Here are some properties you may want to change/update:
|
|
||||||
|
source.dir
|
||||||
source.dir
|
The name of the source directory. Default is 'src'.
|
||||||
The name of the source directory. Default is 'src'.
|
out.dir
|
||||||
out.dir
|
The name of the output directory. Default is 'bin'.
|
||||||
The name of the output directory. Default is 'bin'.
|
|
||||||
|
For other overridable properties, look at the beginning of the rules
|
||||||
Properties related to the SDK location or the project target should
|
files in the SDK, at tools/ant/build.xml
|
||||||
be updated using the 'android' tool with the 'update' action.
|
|
||||||
|
Properties related to the SDK location or the project target should
|
||||||
This file is an integral part of the build system for your
|
be updated using the 'android' tool with the 'update' action.
|
||||||
application and should be checked into Version Control Systems.
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
-->
|
application and should be checked into Version Control Systems.
|
||||||
<property file="build.properties" />
|
|
||||||
|
-->
|
||||||
<!-- The default.properties file is created and updated by the 'android'
|
<property file="ant.properties" />
|
||||||
tool, as well as ADT.
|
|
||||||
This file is an integral part of the build system for your
|
<!-- The project.properties file is created and updated by the 'android'
|
||||||
application and should be checked into Version Control Systems. -->
|
tool, as well as ADT.
|
||||||
<property file="default.properties" />
|
|
||||||
|
This contains project specific properties such as project target, and library
|
||||||
|
dependencies. Lower level build properties are stored in ant.properties
|
||||||
<!-- Required pre-setup import -->
|
(or in .classpath for Eclipse projects).
|
||||||
<import file="${sdk.dir}/tools/ant/pre_setup.xml" />
|
|
||||||
|
This file is an integral part of the build system for your
|
||||||
|
application and should be checked into Version Control Systems. -->
|
||||||
<!-- extension targets. Uncomment the ones where you want to do custom work
|
<loadproperties srcFile="project.properties" />
|
||||||
in between standard targets -->
|
|
||||||
<!--
|
<!-- quick check on sdk.dir -->
|
||||||
<target name="-pre-build">
|
<fail
|
||||||
</target>
|
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
|
||||||
<target name="-pre-compile">
|
unless="sdk.dir"
|
||||||
</target>
|
/>
|
||||||
|
|
||||||
[This is typically used for code obfuscation.
|
|
||||||
Compiled code location: ${out.classes.absolute.dir}
|
<!-- extension targets. Uncomment the ones where you want to do custom work
|
||||||
If this is not done in place, override ${out.dex.input.absolute.dir}]
|
in between standard targets -->
|
||||||
<target name="-post-compile">
|
<!--
|
||||||
</target>
|
<target name="-pre-build">
|
||||||
-->
|
</target>
|
||||||
|
<target name="-pre-compile">
|
||||||
<!-- Execute the Android Setup task that will setup some properties
|
</target>
|
||||||
specific to the target, and import the build rules files.
|
|
||||||
|
/* This is typically used for code obfuscation.
|
||||||
The rules file is imported from
|
Compiled code location: ${out.classes.absolute.dir}
|
||||||
<SDK>/tools/ant/
|
If this is not done in place, override ${out.dex.input.absolute.dir} */
|
||||||
Depending on the project type it can be either:
|
<target name="-post-compile">
|
||||||
- main_rules.xml
|
</target>
|
||||||
- lib_rules.xml
|
-->
|
||||||
- test_rules.xml
|
|
||||||
|
<!-- Import the actual build file.
|
||||||
To customize existing targets, there are two options:
|
|
||||||
- Customize only one target:
|
To customize existing targets, there are two options:
|
||||||
- copy/paste the target into this file, *before* the
|
- Customize only one target:
|
||||||
<setup> task.
|
- copy/paste the target into this file, *before* the
|
||||||
- customize it to your needs.
|
<import> task.
|
||||||
- Customize the whole script.
|
- customize it to your needs.
|
||||||
- copy/paste the content of the rules files (minus the top node)
|
- Customize the whole content of build.xml
|
||||||
into this file, *after* the <setup> task
|
- copy/paste the content of the rules files (minus the top node)
|
||||||
- disable the import of the rules by changing the setup task
|
into this file, replacing the <import> task.
|
||||||
below to <setup import="false" />.
|
- customize to your needs.
|
||||||
- customize to your needs.
|
|
||||||
-->
|
***********************
|
||||||
<setup />
|
****** IMPORTANT ******
|
||||||
|
***********************
|
||||||
</project>
|
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||||
|
in order to avoid having your file be overridden by tools such as "android update project"
|
||||||
|
-->
|
||||||
|
<!-- version-tag: 1 -->
|
||||||
|
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||||
|
|
||||||
|
</project>
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
# This file is automatically generated by Android Tools.
|
|
||||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
|
||||||
#
|
|
||||||
# This file must be checked in Version Control Systems.
|
|
||||||
#
|
|
||||||
# To customize properties used by the Ant build system use,
|
|
||||||
# "build.properties", and override values to adapt the script to your
|
|
||||||
# project structure.
|
|
||||||
|
|
||||||
# Project target.
|
|
||||||
target=android-4
|
|
||||||
# Indicates whether an apk should be generated for each density.
|
|
||||||
split.density=false
|
|
13
project.properties
Normal file
13
project.properties
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system use,
|
||||||
|
# "ant.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
|
||||||
|
# Indicates whether an apk should be generated for each density.
|
||||||
|
split.density=false
|
||||||
|
# Project target.
|
||||||
|
target=android-4
|
0
res/layout/main.xml → res/layout/log_view.xml
Executable file → Normal file
0
res/layout/main.xml → res/layout/log_view.xml
Executable file → Normal file
@ -45,6 +45,13 @@
|
|||||||
android:summaryOn="Incoming SMS will be stored in Messaging inbox"
|
android:summaryOn="Incoming SMS will be stored in Messaging inbox"
|
||||||
></CheckBoxPreference>
|
></CheckBoxPreference>
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="call_notifications"
|
||||||
|
android:title="Call notifications"
|
||||||
|
android:summaryOff="EnvayaSMS will not notify server when phone receives an incoming call"
|
||||||
|
android:summaryOn="EnvayaSMS will notify server when phone receives an incoming call"
|
||||||
|
></CheckBoxPreference>
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="wifi_sleep_policy"
|
android:key="wifi_sleep_policy"
|
||||||
android:title="Wi-Fi sleep policy"
|
android:title="Wi-Fi sleep policy"
|
||||||
@ -52,7 +59,7 @@
|
|||||||
android:entries="@array/wifi_sleep_policies"
|
android:entries="@array/wifi_sleep_policies"
|
||||||
android:entryValues="@array/wifi_sleep_policies_values"
|
android:entryValues="@array/wifi_sleep_policies_values"
|
||||||
>
|
>
|
||||||
</ListPreference>
|
</ListPreference>
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:key="network_failover"
|
android:key="network_failover"
|
||||||
|
@ -17,14 +17,20 @@ class EnvayaSMS
|
|||||||
const STATUS_QUEUED = 'queued';
|
const STATUS_QUEUED = 'queued';
|
||||||
const STATUS_FAILED = 'failed';
|
const STATUS_FAILED = 'failed';
|
||||||
const STATUS_SENT = 'sent';
|
const STATUS_SENT = 'sent';
|
||||||
|
const STATUS_CANCELLED = 'cancelled';
|
||||||
|
|
||||||
const DEVICE_STATUS_POWER_CONNECTED = "power_connected";
|
const DEVICE_STATUS_POWER_CONNECTED = "power_connected";
|
||||||
const DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
|
const DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
|
||||||
const DEVICE_STATUS_BATTERY_LOW = "battery_low";
|
const DEVICE_STATUS_BATTERY_LOW = "battery_low";
|
||||||
const DEVICE_STATUS_BATTERY_OKAY = "battery_okay";
|
const DEVICE_STATUS_BATTERY_OKAY = "battery_okay";
|
||||||
|
const DEVICE_STATUS_SEND_LIMIT_EXCEEDED = "send_limit_exceeded";
|
||||||
|
|
||||||
const MESSAGE_TYPE_SMS = 'sms';
|
const MESSAGE_TYPE_SMS = 'sms';
|
||||||
const MESSAGE_TYPE_MMS = 'mms';
|
const MESSAGE_TYPE_MMS = 'mms';
|
||||||
|
const MESSAGE_TYPE_CALL = 'call';
|
||||||
|
|
||||||
|
const NETWORK_MOBILE = "MOBILE";
|
||||||
|
const NETWORK_WIFI = "WIFI";
|
||||||
|
|
||||||
static function escape($val)
|
static function escape($val)
|
||||||
{
|
{
|
||||||
@ -46,7 +52,27 @@ class EnvayaSMS
|
|||||||
static::$request = new EnvayaSMS_Request();
|
static::$request = new EnvayaSMS_Request();
|
||||||
}
|
}
|
||||||
return static::$request;
|
return static::$request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function get_error_xml($message)
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
|
||||||
|
echo "<response>";
|
||||||
|
echo "<error>";
|
||||||
|
echo EnvayaSMS::escape($message);
|
||||||
|
echo "</error>";
|
||||||
|
echo "</response>";
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
static function get_success_xml()
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
|
||||||
|
echo "<response></response>";
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnvayaSMS_Request
|
class EnvayaSMS_Request
|
||||||
@ -56,12 +82,28 @@ class EnvayaSMS_Request
|
|||||||
public $version;
|
public $version;
|
||||||
public $phone_number;
|
public $phone_number;
|
||||||
public $log;
|
public $log;
|
||||||
|
|
||||||
|
public $version_name;
|
||||||
|
public $sdk_int;
|
||||||
|
public $manufacturer;
|
||||||
|
public $model;
|
||||||
|
public $network;
|
||||||
|
|
||||||
function __construct()
|
function __construct()
|
||||||
{
|
{
|
||||||
$this->version = $_POST['version'];
|
$this->version = $_POST['version'];
|
||||||
$this->phone_number = $_POST['phone_number'];
|
$this->phone_number = $_POST['phone_number'];
|
||||||
$this->log = @$_POST['log'];
|
$this->log = $_POST['log'];
|
||||||
|
$this->network = @$_POST['network'];
|
||||||
|
|
||||||
|
if (preg_match('#/(?P<version_name>[\w\.\-]+) \(Android; SDK (?P<sdk_int>\d+); (?P<manufacturer>[^;]*); (?P<model>[^\)]*)\)#',
|
||||||
|
@$_SERVER['HTTP_USER_AGENT'], $matches))
|
||||||
|
{
|
||||||
|
$this->version_name = $matches['version_name'];
|
||||||
|
$this->sdk_int = $matches['sdk_int'];
|
||||||
|
$this->manufacturer = $matches['manufacturer'];
|
||||||
|
$this->model = $matches['model'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_action()
|
function get_action()
|
||||||
@ -124,21 +166,24 @@ class EnvayaSMS_Request
|
|||||||
//error_log("Signed data: '$input'");
|
//error_log("Signed data: '$input'");
|
||||||
|
|
||||||
return base64_encode(sha1($input, true));
|
return base64_encode(sha1($input, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
static function get_messages_xml($messages)
|
static function get_messages_xml($messages)
|
||||||
{
|
{
|
||||||
ob_start();
|
ob_start();
|
||||||
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
|
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
|
||||||
|
echo "<response>";
|
||||||
echo "<messages>";
|
echo "<messages>";
|
||||||
foreach ($messages as $message)
|
foreach ($messages as $message)
|
||||||
{
|
{
|
||||||
|
$type = isset($message->type) ? $message->type : EnvayaSMS::MESSAGE_TYPE_SMS;
|
||||||
$id = isset($message->id) ? " id=\"".EnvayaSMS::escape($message->id)."\"" : "";
|
$id = isset($message->id) ? " id=\"".EnvayaSMS::escape($message->id)."\"" : "";
|
||||||
$to = isset($message->to) ? " to=\"".EnvayaSMS::escape($message->to)."\"" : "";
|
$to = isset($message->to) ? " to=\"".EnvayaSMS::escape($message->to)."\"" : "";
|
||||||
$priority = isset($message->priority) ? " priority=\"".$message->priority."\"" : "";
|
$priority = isset($message->priority) ? " priority=\"".$message->priority."\"" : "";
|
||||||
echo "<sms$id$to$priority>".EnvayaSMS::escape($message->message)."</sms>";
|
echo "<$type$id$to$priority>".EnvayaSMS::escape($message->message)."</$type>";
|
||||||
}
|
}
|
||||||
echo "</messages>";
|
echo "</messages>";
|
||||||
|
echo "</response>";
|
||||||
return ob_get_clean();
|
return ob_get_clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +194,7 @@ class EnvayaSMS_OutgoingMessage
|
|||||||
public $to; // destination phone number
|
public $to; // destination phone number
|
||||||
public $message; // content of SMS message
|
public $message; // content of SMS message
|
||||||
public $priority; // integer priority, higher numbers will be sent first
|
public $priority; // integer priority, higher numbers will be sent first
|
||||||
|
public $type; // EnvayaSMS::MESSAGE_TYPE_* value (default sms)
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnvayaSMS_Action
|
class EnvayaSMS_Action
|
||||||
@ -194,15 +240,17 @@ class EnvayaSMS_Action_Incoming extends EnvayaSMS_Action
|
|||||||
public $message_type; // EnvayaSMS::MESSAGE_TYPE_MMS or EnvayaSMS::MESSAGE_TYPE_SMS
|
public $message_type; // EnvayaSMS::MESSAGE_TYPE_MMS or EnvayaSMS::MESSAGE_TYPE_SMS
|
||||||
public $mms_parts; // array of EnvayaSMS_MMS_Part instances
|
public $mms_parts; // array of EnvayaSMS_MMS_Part instances
|
||||||
public $timestamp; // timestamp of incoming message (added in version 12)
|
public $timestamp; // timestamp of incoming message (added in version 12)
|
||||||
|
public $age; // delay in ms between time when message originally received and when forwarded to server (added in version 18)
|
||||||
|
|
||||||
function __construct($request)
|
function __construct($request)
|
||||||
{
|
{
|
||||||
parent::__construct($request);
|
parent::__construct($request);
|
||||||
$this->type = EnvayaSMS::ACTION_INCOMING;
|
$this->type = EnvayaSMS::ACTION_INCOMING;
|
||||||
$this->from = $_POST['from'];
|
$this->from = $_POST['from'];
|
||||||
$this->message = $_POST['message'];
|
$this->message = @$_POST['message'];
|
||||||
$this->message_type = $_POST['message_type'];
|
$this->message_type = $_POST['message_type'];
|
||||||
$this->timestamp = @$_POST['timestamp'];
|
$this->timestamp = @$_POST['timestamp'];
|
||||||
|
$this->age = @$_POST['age'];
|
||||||
|
|
||||||
if ($this->message_type == EnvayaSMS::MESSAGE_TYPE_MMS)
|
if ($this->message_type == EnvayaSMS::MESSAGE_TYPE_MMS)
|
||||||
{
|
{
|
||||||
@ -247,7 +295,7 @@ class EnvayaSMS_Action_SendStatus extends EnvayaSMS_Action
|
|||||||
{
|
{
|
||||||
public $status; // EnvayaSMS::STATUS_* values
|
public $status; // EnvayaSMS::STATUS_* values
|
||||||
public $id; // server ID previously used in EnvayaSMS_OutgoingMessage
|
public $id; // server ID previously used in EnvayaSMS_OutgoingMessage
|
||||||
public $error; // textual descrption of error (if applicable)
|
public $error; // textual description of error (if applicable)
|
||||||
|
|
||||||
function __construct($request)
|
function __construct($request)
|
||||||
{
|
{
|
||||||
|
2
server/php/example/mms_parts/.gitignore
vendored
Normal file
2
server/php/example/mms_parts/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
@ -14,11 +14,13 @@ $phone_number = $request->phone_number;
|
|||||||
|
|
||||||
$password = @$PASSWORDS[$phone_number];
|
$password = @$PASSWORDS[$phone_number];
|
||||||
|
|
||||||
|
header("Content-Type: text/xml");
|
||||||
|
|
||||||
if (!isset($password) || !$request->is_validated($password))
|
if (!isset($password) || !$request->is_validated($password))
|
||||||
{
|
{
|
||||||
header("HTTP/1.1 403 Forbidden");
|
header("HTTP/1.1 403 Forbidden");
|
||||||
error_log("Invalid request signature");
|
error_log("Invalid request signature");
|
||||||
echo "Invalid request signature";
|
echo EnvayaSMS::get_error_xml("Invalid request signature");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,14 +39,30 @@ $action = $request->get_action();
|
|||||||
switch ($action->type)
|
switch ($action->type)
|
||||||
{
|
{
|
||||||
case EnvayaSMS::ACTION_INCOMING:
|
case EnvayaSMS::ACTION_INCOMING:
|
||||||
error_log("Received SMS from {$action->from}");
|
$type = strtoupper($action->message_type);
|
||||||
|
|
||||||
|
error_log("Received $type from {$action->from}");
|
||||||
|
error_log(" message: {$action->message}");
|
||||||
|
|
||||||
|
if ($action->message_type == EnvayaSMS::MESSAGE_TYPE_MMS)
|
||||||
|
{
|
||||||
|
foreach ($action->mms_parts as $mms_part)
|
||||||
|
{
|
||||||
|
$ext_map = array('image/jpeg' => 'jpg', 'image/gif' => 'gif', 'text/plain' => 'txt', 'application/smil' => 'smil');
|
||||||
|
$ext = @$ext_map[$mms_part->type] ?: "unk";
|
||||||
|
|
||||||
|
$filename = "mms_parts/" . uniqid('mms') . ".$ext";
|
||||||
|
|
||||||
|
copy($mms_part->tmp_name, dirname(__DIR__)."/$filename");
|
||||||
|
error_log(" mms part type {$mms_part->type} saved to {$filename}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$reply = new EnvayaSMS_OutgoingMessage();
|
$reply = new EnvayaSMS_OutgoingMessage();
|
||||||
$reply->message = "You said: {$action->message}";
|
$reply->message = "You said: {$action->message}";
|
||||||
|
|
||||||
error_log("Sending reply: {$reply->message}");
|
error_log("Sending reply: {$reply->message}");
|
||||||
|
|
||||||
header("Content-Type: text/xml");
|
|
||||||
echo $action->get_response_xml(array($reply));
|
echo $action->get_response_xml(array($reply));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -70,7 +88,6 @@ switch ($action->type)
|
|||||||
}
|
}
|
||||||
closedir($dir);
|
closedir($dir);
|
||||||
|
|
||||||
header("Content-Type: text/xml");
|
|
||||||
echo $action->get_response_xml($messages);
|
echo $action->get_response_xml($messages);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -81,23 +98,23 @@ switch ($action->type)
|
|||||||
// delete file with matching id
|
// delete file with matching id
|
||||||
if (preg_match('#^\w+$#', $id) && unlink("$OUTGOING_DIR_NAME/$id.json"))
|
if (preg_match('#^\w+$#', $id) && unlink("$OUTGOING_DIR_NAME/$id.json"))
|
||||||
{
|
{
|
||||||
echo "OK";
|
echo EnvayaSMS::get_success_xml();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
header("HTTP/1.1 404 Not Found");
|
header("HTTP/1.1 404 Not Found");
|
||||||
echo "invalid id";
|
echo EnvayaSMS::get_error_xml("Invalid id");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case EnvayaSMS::ACTION_DEVICE_STATUS:
|
case EnvayaSMS::ACTION_DEVICE_STATUS:
|
||||||
error_log("device_status = {$action->status}");
|
error_log("device_status = {$action->status}");
|
||||||
echo "OK";
|
echo EnvayaSMS::get_success_xml();
|
||||||
return;
|
return;
|
||||||
case EnvayaSMS::ACTION_TEST:
|
case EnvayaSMS::ACTION_TEST:
|
||||||
echo "OK";
|
echo EnvayaSMS::get_success_xml();
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
header("HTTP/1.1 404 Not Found");
|
header("HTTP/1.1 404 Not Found");
|
||||||
echo "Invalid action";
|
echo EnvayaSMS::get_error_xml("Invalid action");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
@ -14,14 +14,15 @@ import android.net.ConnectivityManager;
|
|||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.telephony.PhoneStateListener;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -42,9 +43,10 @@ import org.apache.http.params.HttpConnectionParams;
|
|||||||
import org.apache.http.params.HttpParams;
|
import org.apache.http.params.HttpParams;
|
||||||
import org.apache.http.params.HttpProtocolParams;
|
import org.apache.http.params.HttpProtocolParams;
|
||||||
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
||||||
import org.envaya.sms.receiver.ReenableWifiReceiver;
|
import org.envaya.sms.task.CheckConnectivityTask;
|
||||||
import org.envaya.sms.task.HttpTask;
|
import org.envaya.sms.task.HttpTask;
|
||||||
import org.envaya.sms.task.PollerTask;
|
import org.envaya.sms.task.PollerTask;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
@ -59,14 +61,17 @@ public final class App extends Application {
|
|||||||
public static final String STATUS_QUEUED = "queued";
|
public static final String STATUS_QUEUED = "queued";
|
||||||
public static final String STATUS_FAILED = "failed";
|
public static final String STATUS_FAILED = "failed";
|
||||||
public static final String STATUS_SENT = "sent";
|
public static final String STATUS_SENT = "sent";
|
||||||
|
public static final String STATUS_CANCELLED = "cancelled";
|
||||||
|
|
||||||
public static final String DEVICE_STATUS_POWER_CONNECTED = "power_connected";
|
public static final String DEVICE_STATUS_POWER_CONNECTED = "power_connected";
|
||||||
public static final String DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
|
public static final String DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
|
||||||
public static final String DEVICE_STATUS_BATTERY_LOW = "battery_low";
|
public static final String DEVICE_STATUS_BATTERY_LOW = "battery_low";
|
||||||
public static final String DEVICE_STATUS_BATTERY_OKAY = "battery_okay";
|
public static final String DEVICE_STATUS_BATTERY_OKAY = "battery_okay";
|
||||||
|
public static final String DEVICE_STATUS_SEND_LIMIT_EXCEEDED = "send_limit_exceeded";
|
||||||
|
|
||||||
public static final String MESSAGE_TYPE_MMS = "mms";
|
public static final String MESSAGE_TYPE_MMS = "mms";
|
||||||
public static final String MESSAGE_TYPE_SMS = "sms";
|
public static final String MESSAGE_TYPE_SMS = "sms";
|
||||||
|
public static final String MESSAGE_TYPE_CALL = "call";
|
||||||
|
|
||||||
public static final String LOG_NAME = "EnvayaSMS";
|
public static final String LOG_NAME = "EnvayaSMS";
|
||||||
|
|
||||||
@ -76,9 +81,9 @@ public final class App extends Application {
|
|||||||
// signal to PendingMessages activity (if open) that inbox/outbox has changed
|
// signal to PendingMessages activity (if open) that inbox/outbox has changed
|
||||||
public static final String INBOX_CHANGED_INTENT = "org.envaya.sms.INBOX_CHANGED";
|
public static final String INBOX_CHANGED_INTENT = "org.envaya.sms.INBOX_CHANGED";
|
||||||
public static final String OUTBOX_CHANGED_INTENT = "org.envaya.sms.OUTBOX_CHANGED";
|
public static final String OUTBOX_CHANGED_INTENT = "org.envaya.sms.OUTBOX_CHANGED";
|
||||||
|
|
||||||
public static final String QUERY_EXPANSION_PACKS_INTENT = "org.envaya.sms.QUERY_EXPANSION_PACKS";
|
public static final String QUERY_EXPANSION_PACKS_INTENT = "org.envaya.sms.QUERY_EXPANSION_PACKS";
|
||||||
public static final String QUERY_EXPANSION_PACKS_EXTRA_PACKAGES = "packages";
|
public static final String QUERY_EXPANSION_PACKS_EXTRA_PACKAGES = "packages";
|
||||||
|
|
||||||
// Interface for sending outgoing messages to expansion packs
|
// Interface for sending outgoing messages to expansion packs
|
||||||
public static final String OUTGOING_SMS_INTENT_SUFFIX = ".OUTGOING_SMS";
|
public static final String OUTGOING_SMS_INTENT_SUFFIX = ".OUTGOING_SMS";
|
||||||
@ -96,11 +101,13 @@ public final class App extends Application {
|
|||||||
public static final String STATUS_EXTRA_NUM_PARTS = "num_parts";
|
public static final String STATUS_EXTRA_NUM_PARTS = "num_parts";
|
||||||
|
|
||||||
public static final int MAX_DISPLAYED_LOG = 8000;
|
public static final int MAX_DISPLAYED_LOG = 8000;
|
||||||
public static final int LOG_TIMESTAMP_INTERVAL = 60000; // ms
|
public static final int LOG_TIMESTAMP_INTERVAL = 30000; //60000; // ms
|
||||||
|
|
||||||
public static final int HTTP_CONNECTION_TIMEOUT = 10000; // ms
|
public static final int HTTP_CONNECTION_TIMEOUT = 10000; // ms
|
||||||
public static final int HTTP_SOCKET_TIMEOUT = 60000; // ms
|
public static final int HTTP_SOCKET_TIMEOUT = 60000; // ms
|
||||||
|
|
||||||
|
public static final int MESSAGE_SEND_TIMEOUT = 30000; // ms
|
||||||
|
|
||||||
// Each QueuedMessage is identified within our internal Map by its Uri.
|
// Each QueuedMessage is identified within our internal Map by its Uri.
|
||||||
// Currently QueuedMessage instances are only available within EnvayaSMS,
|
// Currently QueuedMessage instances are only available within EnvayaSMS,
|
||||||
// (but they could be made available to other applications later via a ContentProvider)
|
// (but they could be made available to other applications later via a ContentProvider)
|
||||||
@ -113,7 +120,7 @@ public final class App extends Application {
|
|||||||
public static final int DISABLE_WIFI_INTERVAL = 3600000;
|
public static final int DISABLE_WIFI_INTERVAL = 3600000;
|
||||||
|
|
||||||
// how often we can automatically failover between wifi/mobile connection
|
// how often we can automatically failover between wifi/mobile connection
|
||||||
public static final int CONNECTIVITY_FAILOVER_INTERVAL = 1800000;
|
public static final int CONNECTIVITY_FAILOVER_INTERVAL = 900000;
|
||||||
|
|
||||||
// max per-app outgoing SMS rate used by com.android.internal.telephony.SMSDispatcher
|
// max per-app outgoing SMS rate used by com.android.internal.telephony.SMSDispatcher
|
||||||
// with a slightly longer check period to account for variance in the time difference
|
// with a slightly longer check period to account for variance in the time difference
|
||||||
@ -145,7 +152,10 @@ public final class App extends Application {
|
|||||||
private int outgoingMessageCount = -1;
|
private int outgoingMessageCount = -1;
|
||||||
|
|
||||||
private MmsUtils mmsUtils;
|
private MmsUtils mmsUtils;
|
||||||
|
private CallListener callListener;
|
||||||
|
|
||||||
|
private boolean connectivityError = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate()
|
public void onCreate()
|
||||||
{
|
{
|
||||||
@ -154,6 +164,8 @@ public final class App extends Application {
|
|||||||
settings = PreferenceManager.getDefaultSharedPreferences(this);
|
settings = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
mmsUtils = new MmsUtils(this);
|
mmsUtils = new MmsUtils(this);
|
||||||
|
|
||||||
|
callListener = new CallListener(this);
|
||||||
|
|
||||||
outgoingMessagePackages.add(getPackageName());
|
outgoingMessagePackages.add(getPackageName());
|
||||||
|
|
||||||
mmsObserver = new MmsObserver(this);
|
mmsObserver = new MmsObserver(this);
|
||||||
@ -170,39 +182,87 @@ public final class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateExpansionPacks();
|
updateExpansionPacks();
|
||||||
|
configuredChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configuredChanged()
|
||||||
|
{
|
||||||
log(Html.fromHtml(
|
log(Html.fromHtml(
|
||||||
isEnabled() ? "<b>SMS gateway running.</b>" : "<b>SMS gateway disabled.</b>"));
|
isEnabled() ? "<b>SMS gateway running ("+getDisplayString(getPhoneNumber())+").</b>"
|
||||||
|
: "<b>SMS gateway disabled.</b>"));
|
||||||
|
|
||||||
|
log("Server URL: " + getDisplayString(getServerUrl()));
|
||||||
|
log("Keep new messages: " + (getKeepInInbox() ? "YES": "NO"));
|
||||||
|
log("Call notifications: " + (callNotificationsEnabled() ? "ON": "OFF"));
|
||||||
|
log("Network failover: " + (isNetworkFailoverEnabled() ? "ON": "OFF"));
|
||||||
|
|
||||||
|
boolean ignoreShortcodes = ignoreShortcodes();
|
||||||
|
boolean ignoreNonNumeric = ignoreNonNumeric();
|
||||||
|
List<String> ignoredNumbers = getIgnoredPhoneNumbers();
|
||||||
|
|
||||||
|
if (ignoredNumbers.size() > 0 || ignoreShortcodes || ignoreNonNumeric)
|
||||||
|
{
|
||||||
|
log("Ignored phone numbers:");
|
||||||
|
|
||||||
|
if (ignoreShortcodes || ignoreNonNumeric)
|
||||||
|
{
|
||||||
|
String ignoreDesc = " ";
|
||||||
|
if (ignoreShortcodes)
|
||||||
|
{
|
||||||
|
ignoreDesc += "all shortcodes";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreShortcodes && ignoreNonNumeric)
|
||||||
|
{
|
||||||
|
ignoreDesc += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreNonNumeric)
|
||||||
|
{
|
||||||
|
ignoreDesc += "all non-numeric";
|
||||||
|
}
|
||||||
|
log(ignoreDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String sender : ignoredNumbers)
|
||||||
|
{
|
||||||
|
log(" " + sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log("Server URL is: " + getDisplayString(getServerUrl()));
|
|
||||||
log("Your phone number is: " + getDisplayString(getPhoneNumber()));
|
|
||||||
|
|
||||||
if (isTestMode())
|
if (isTestMode())
|
||||||
{
|
{
|
||||||
log("Test mode is ON");
|
log("Test mode: ON");
|
||||||
log("Test phone numbers:");
|
log("Test phone numbers:");
|
||||||
|
|
||||||
for (String sender : getTestPhoneNumbers())
|
for (String sender : getTestPhoneNumbers())
|
||||||
{
|
{
|
||||||
log(" " + sender);
|
log(" " + sender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log(Html.fromHtml("<b>To change these settings, click Menu, then Settings.</b>"));
|
||||||
|
|
||||||
enabledChanged();
|
enabledChanged();
|
||||||
|
}
|
||||||
log(Html.fromHtml("<b>Press Menu to edit settings.</b>"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enabledChanged()
|
public void enabledChanged()
|
||||||
{
|
{
|
||||||
|
TelephonyManager telephony = (TelephonyManager)
|
||||||
|
getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
|
|
||||||
if (isEnabled())
|
if (isEnabled())
|
||||||
{
|
{
|
||||||
mmsObserver.register();
|
mmsObserver.register();
|
||||||
|
|
||||||
|
telephony.listen(callListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mmsObserver.unregister();
|
mmsObserver.unregister();
|
||||||
}
|
|
||||||
|
telephony.listen(callListener, PhoneStateListener.LISTEN_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
setOutgoingMessageAlarm();
|
setOutgoingMessageAlarm();
|
||||||
startService(new Intent(this, ForegroundService.class));
|
startService(new Intent(this, ForegroundService.class));
|
||||||
@ -259,6 +319,13 @@ public final class App extends Application {
|
|||||||
+ getOutgoingMessageLimit() + " in 1 hour reached");
|
+ getOutgoingMessageLimit() + " in 1 hour reached");
|
||||||
log("To increase this limit, install an expansion pack.");
|
log("To increase this limit, install an expansion pack.");
|
||||||
|
|
||||||
|
HttpTask task = new HttpTask(this,
|
||||||
|
new BasicNameValuePair("action", App.ACTION_DEVICE_STATUS),
|
||||||
|
new BasicNameValuePair("status", App.DEVICE_STATUS_SEND_LIMIT_EXCEEDED)
|
||||||
|
);
|
||||||
|
task.setRetryOnConnectivityError(true);
|
||||||
|
task.execute();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,13 +405,14 @@ public final class App extends Application {
|
|||||||
|
|
||||||
sendOrderedBroadcast(
|
sendOrderedBroadcast(
|
||||||
new Intent(App.QUERY_EXPANSION_PACKS_INTENT),
|
new Intent(App.QUERY_EXPANSION_PACKS_INTENT),
|
||||||
"android.permission.SEND_SMS",
|
null,
|
||||||
new BroadcastReceiver() {
|
new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent resultIntent) {
|
public void onReceive(Context context, Intent resultIntent) {
|
||||||
|
|
||||||
setExpansionPacks(this.getResultExtras(false)
|
Bundle extras = this.getResultExtras(false);
|
||||||
.getStringArrayList(App.QUERY_EXPANSION_PACKS_EXTRA_PACKAGES));
|
|
||||||
|
setExpansionPacks(extras.getStringArrayList(App.QUERY_EXPANSION_PACKS_EXTRA_PACKAGES));
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -375,7 +443,7 @@ public final class App extends Application {
|
|||||||
String serverUrl = getServerUrl();
|
String serverUrl = getServerUrl();
|
||||||
if (serverUrl.length() > 0) {
|
if (serverUrl.length() > 0) {
|
||||||
log("Checking for outgoing messages");
|
log("Checking for outgoing messages");
|
||||||
pollActive = true;
|
pollActive = true;
|
||||||
new PollerTask(this).execute();
|
new PollerTask(this).execute();
|
||||||
} else {
|
} else {
|
||||||
log("Can't check outgoing messages; server URL not set");
|
log("Can't check outgoing messages; server URL not set");
|
||||||
@ -402,7 +470,7 @@ public final class App extends Application {
|
|||||||
|
|
||||||
if (isEnabled())
|
if (isEnabled())
|
||||||
{
|
{
|
||||||
if (pollSeconds > 0) {
|
if (pollSeconds > 0) {
|
||||||
alarm.setRepeating(
|
alarm.setRepeating(
|
||||||
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
SystemClock.elapsedRealtime(),
|
SystemClock.elapsedRealtime(),
|
||||||
@ -411,7 +479,7 @@ public final class App extends Application {
|
|||||||
log("Checking for outgoing messages every " + pollSeconds + " sec");
|
log("Checking for outgoing messages every " + pollSeconds + " sec");
|
||||||
} else {
|
} else {
|
||||||
log("Not checking for outgoing messages.");
|
log("Not checking for outgoing messages.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,47 +490,64 @@ public final class App extends Application {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean callNotificationsEnabled()
|
||||||
|
{
|
||||||
|
return tryGetBooleanSetting("call_notifications", false);
|
||||||
|
}
|
||||||
|
|
||||||
public String getServerUrl() {
|
public String getServerUrl() {
|
||||||
return settings.getString("server_url", "");
|
return settings.getString("server_url", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPhoneNumber() {
|
public String getPhoneNumber() {
|
||||||
return settings.getString("phone_number", "");
|
return settings.getString("phone_number", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOutgoingPollSeconds() {
|
public int getOutgoingPollSeconds() {
|
||||||
return Integer.parseInt(settings.getString("outgoing_interval", "0"));
|
return Integer.parseInt(settings.getString("outgoing_interval", "0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled()
|
public boolean isEnabled()
|
||||||
{
|
{
|
||||||
return settings.getBoolean("enabled", false);
|
return tryGetBooleanSetting("enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryGetBooleanSetting(String name, boolean defaultValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return settings.getBoolean(name, defaultValue);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNetworkFailoverEnabled()
|
public boolean isNetworkFailoverEnabled()
|
||||||
{
|
{
|
||||||
return settings.getBoolean("network_failover", false);
|
return tryGetBooleanSetting("network_failover", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTestMode()
|
public boolean isTestMode()
|
||||||
{
|
{
|
||||||
return settings.getBoolean("test_mode", false);
|
return tryGetBooleanSetting("test_mode", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getKeepInInbox()
|
public boolean getKeepInInbox()
|
||||||
{
|
{
|
||||||
return settings.getBoolean("keep_in_inbox", false);
|
return tryGetBooleanSetting("keep_in_inbox", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean ignoreShortcodes()
|
public boolean ignoreShortcodes()
|
||||||
{
|
{
|
||||||
return settings.getBoolean("ignore_shortcodes", true);
|
return tryGetBooleanSetting("ignore_shortcodes", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean ignoreNonNumeric()
|
public boolean ignoreNonNumeric()
|
||||||
{
|
{
|
||||||
return settings.getBoolean("ignore_non_numeric", true);
|
return tryGetBooleanSetting("ignore_non_numeric", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
@ -644,6 +729,11 @@ public final class App extends Application {
|
|||||||
).commit();
|
).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void saveStringSetting(String key, String value)
|
||||||
|
{
|
||||||
|
settings.edit().putString(key, value).commit();
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void saveBooleanSetting(String key, boolean value)
|
public synchronized void saveBooleanSetting(String key, boolean value)
|
||||||
{
|
{
|
||||||
settings.edit().putBoolean(key, value).commit();
|
settings.edit().putBoolean(key, value).commit();
|
||||||
@ -761,21 +851,21 @@ public final class App extends Application {
|
|||||||
|
|
||||||
public synchronized boolean canCheck()
|
public synchronized boolean canCheck()
|
||||||
{
|
{
|
||||||
long time = SystemClock.elapsedRealtime();
|
long time = System.currentTimeMillis();
|
||||||
return (time - lastCheckTime >= App.CONNECTIVITY_FAILOVER_INTERVAL);
|
return (time - lastCheckTime >= App.CONNECTIVITY_FAILOVER_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChecked()
|
public void setChecked()
|
||||||
{
|
{
|
||||||
lastCheckTime = SystemClock.elapsedRealtime();
|
lastCheckTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Integer,ConnectivityCheckState> connectivityCheckStates
|
private Map<Integer,ConnectivityCheckState> connectivityCheckStates
|
||||||
= new HashMap<Integer, ConnectivityCheckState>();
|
= new HashMap<Integer, ConnectivityCheckState>();
|
||||||
|
|
||||||
private Thread connectivityThread;
|
private CheckConnectivityTask checkConnectivityTask;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Normally we rely on Android to automatically switch between
|
* Normally we rely on Android to automatically switch between
|
||||||
* mobile data and Wi-Fi, but if the phone is connected to a Wi-Fi router
|
* mobile data and Wi-Fi, but if the phone is connected to a Wi-Fi router
|
||||||
@ -820,7 +910,7 @@ public final class App extends Application {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int networkType = activeNetwork.getType();
|
final int networkType = activeNetwork.getType();
|
||||||
|
|
||||||
ConnectivityCheckState state =
|
ConnectivityCheckState state =
|
||||||
connectivityCheckStates.get(networkType);
|
connectivityCheckStates.get(networkType);
|
||||||
@ -832,79 +922,20 @@ public final class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!state.canCheck()
|
if (!state.canCheck()
|
||||||
|| (connectivityThread != null && connectivityThread.isAlive()))
|
|| (checkConnectivityTask != null && checkConnectivityTask.getStatus() != AsyncTask.Status.FINISHED))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.setChecked();
|
state.setChecked();
|
||||||
|
|
||||||
connectivityThread = new Thread() {
|
Uri serverUrl = Uri.parse(getServerUrl());
|
||||||
@Override
|
String hostName = serverUrl.getHost();
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
Uri serverUrl = Uri.parse(getServerUrl());
|
|
||||||
String hostName = serverUrl.getHost();
|
|
||||||
|
|
||||||
log("Checking connectivity to "+hostName+"...");
|
log("Checking connectivity to "+hostName+"...");
|
||||||
|
|
||||||
try
|
checkConnectivityTask = new CheckConnectivityTask(this, hostName, networkType);
|
||||||
{
|
checkConnectivityTask.execute();
|
||||||
InetAddress addr = InetAddress.getByName(hostName);
|
|
||||||
if (addr.isReachable(App.HTTP_CONNECTION_TIMEOUT))
|
|
||||||
{
|
|
||||||
log("OK");
|
|
||||||
onConnectivityRestored();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
// just what we suspected...
|
|
||||||
// server not reachable on this interface
|
|
||||||
}
|
|
||||||
|
|
||||||
log("Can't connect to "+hostName+".");
|
|
||||||
|
|
||||||
WifiManager wmgr = (WifiManager)getSystemService(Context.WIFI_SERVICE);
|
|
||||||
|
|
||||||
if (!isNetworkFailoverEnabled())
|
|
||||||
{
|
|
||||||
log("Network failover disabled.");
|
|
||||||
}
|
|
||||||
else if (networkType == ConnectivityManager.TYPE_WIFI)
|
|
||||||
{
|
|
||||||
log("Switching from WIFI to MOBILE");
|
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(App.this,
|
|
||||||
0,
|
|
||||||
new Intent(App.this, ReenableWifiReceiver.class),
|
|
||||||
0);
|
|
||||||
|
|
||||||
// set an alarm to try restoring Wi-Fi in a little while
|
|
||||||
AlarmManager alarm =
|
|
||||||
(AlarmManager)getSystemService(Context.ALARM_SERVICE);
|
|
||||||
|
|
||||||
alarm.set(
|
|
||||||
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
|
||||||
SystemClock.elapsedRealtime() + App.DISABLE_WIFI_INTERVAL,
|
|
||||||
pendingIntent);
|
|
||||||
|
|
||||||
wmgr.setWifiEnabled(false);
|
|
||||||
}
|
|
||||||
else if (networkType == ConnectivityManager.TYPE_MOBILE
|
|
||||||
&& !wmgr.isWifiEnabled())
|
|
||||||
{
|
|
||||||
log("Switching from MOBILE to WIFI");
|
|
||||||
wmgr.setWifiEnabled(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
log("Can't automatically fix connectivity.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
connectivityThread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int activeNetworkType = -1;
|
private int activeNetworkType = -1;
|
||||||
@ -933,8 +964,21 @@ public final class App extends Application {
|
|||||||
asyncCheckConnectivity();
|
asyncCheckConnectivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onConnectivityRestored()
|
public boolean hasConnectivityError()
|
||||||
{
|
{
|
||||||
|
return connectivityError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onConnectivityError()
|
||||||
|
{
|
||||||
|
connectivityError = true;
|
||||||
|
asyncCheckConnectivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onConnectivityRestored()
|
||||||
|
{
|
||||||
|
connectivityError = false;
|
||||||
|
|
||||||
inbox.retryAll();
|
inbox.retryAll();
|
||||||
|
|
||||||
if (getOutgoingPollSeconds() > 0)
|
if (getOutgoingPollSeconds() > 0)
|
||||||
@ -963,5 +1007,5 @@ public final class App extends Application {
|
|||||||
public synchronized void addQueuedTask(HttpTask task)
|
public synchronized void addQueuedTask(HttpTask task)
|
||||||
{
|
{
|
||||||
queuedTasks.add(task);
|
queuedTasks.add(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/org/envaya/sms/CallListener.java
Normal file
31
src/org/envaya/sms/CallListener.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package org.envaya.sms;
|
||||||
|
|
||||||
|
import android.telephony.PhoneStateListener;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
|
||||||
|
public class CallListener extends PhoneStateListener {
|
||||||
|
|
||||||
|
private App app;
|
||||||
|
|
||||||
|
public CallListener(App app)
|
||||||
|
{
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCallStateChanged(int state,String incomingNumber) {
|
||||||
|
if (state == TelephonyManager.CALL_STATE_RINGING)
|
||||||
|
{
|
||||||
|
IncomingCall call = new IncomingCall(app, incomingNumber, System.currentTimeMillis());
|
||||||
|
|
||||||
|
if (call.isForwardable())
|
||||||
|
{
|
||||||
|
app.inbox.forwardMessage(call);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.log("Ignoring incoming call from " + call.getFrom());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import org.envaya.sms.ui.Main;
|
import org.envaya.sms.ui.LogView;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Service running in foreground to make sure App instance stays
|
* Service running in foreground to make sure App instance stays
|
||||||
@ -159,7 +159,7 @@ public class ForegroundService extends Service {
|
|||||||
System.currentTimeMillis());
|
System.currentTimeMillis());
|
||||||
|
|
||||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||||
new Intent(this, Main.class), 0);
|
new Intent(this, LogView.class), 0);
|
||||||
|
|
||||||
notification.setLatestEventInfo(this,
|
notification.setLatestEventInfo(this,
|
||||||
"EnvayaSMS running",
|
"EnvayaSMS running",
|
||||||
|
49
src/org/envaya/sms/IncomingCall.java
Normal file
49
src/org/envaya/sms/IncomingCall.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* To change this template, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.envaya.sms;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import org.envaya.sms.task.ForwarderTask;
|
||||||
|
|
||||||
|
public class IncomingCall extends IncomingMessage {
|
||||||
|
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private static long nextId = 1;
|
||||||
|
|
||||||
|
public IncomingCall(App app, String from, long timestampMillis)
|
||||||
|
{
|
||||||
|
super(app, from, timestampMillis);
|
||||||
|
this.id = getNextId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized long getNextId()
|
||||||
|
{
|
||||||
|
long id = nextId;
|
||||||
|
nextId++;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayType()
|
||||||
|
{
|
||||||
|
return "call";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageType()
|
||||||
|
{
|
||||||
|
return App.MESSAGE_TYPE_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isForwardable()
|
||||||
|
{
|
||||||
|
return app.callNotificationsEnabled() && super.isForwardable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUri()
|
||||||
|
{
|
||||||
|
return Uri.withAppendedPath(App.INCOMING_URI, "call/" + id);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,19 @@
|
|||||||
package org.envaya.sms;
|
package org.envaya.sms;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.SystemClock;
|
||||||
import org.envaya.sms.receiver.IncomingMessageRetry;
|
import org.envaya.sms.receiver.IncomingMessageRetry;
|
||||||
|
import org.envaya.sms.task.ForwarderTask;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
public abstract class IncomingMessage extends QueuedMessage {
|
public abstract class IncomingMessage extends QueuedMessage {
|
||||||
|
|
||||||
protected String from;
|
protected String from;
|
||||||
|
protected String message = "";
|
||||||
protected long timestamp; // unix timestamp in milliseconds
|
protected long timestamp; // unix timestamp in milliseconds
|
||||||
|
|
||||||
|
protected long timeReceived; // SystemClock.elapsedRealtime
|
||||||
|
|
||||||
private ProcessingState state = ProcessingState.None;
|
private ProcessingState state = ProcessingState.None;
|
||||||
|
|
||||||
public enum ProcessingState
|
public enum ProcessingState
|
||||||
@ -24,6 +30,18 @@ public abstract class IncomingMessage extends QueuedMessage {
|
|||||||
super(app);
|
super(app);
|
||||||
this.from = from;
|
this.from = from;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
|
||||||
|
this.timeReceived = SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageBody()
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAge()
|
||||||
|
{
|
||||||
|
return SystemClock.elapsedRealtime() - timeReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp()
|
public long getTimestamp()
|
||||||
@ -77,5 +95,27 @@ public abstract class IncomingMessage extends QueuedMessage {
|
|||||||
return getDisplayType() + " from " + getFrom();
|
return getDisplayType() + " from " + getFrom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void tryForwardToServer();
|
public void tryForwardToServer()
|
||||||
|
{
|
||||||
|
if (numRetries > 0)
|
||||||
|
{
|
||||||
|
app.log("Retrying forwarding " + getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
getForwarderTask().execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getMessageType();
|
||||||
|
|
||||||
|
protected ForwarderTask getForwarderTask()
|
||||||
|
{
|
||||||
|
return new ForwarderTask(this,
|
||||||
|
new BasicNameValuePair("message_type", getMessageType()),
|
||||||
|
new BasicNameValuePair("message", getMessageBody()),
|
||||||
|
new BasicNameValuePair("action", App.ACTION_INCOMING),
|
||||||
|
new BasicNameValuePair("from", getFrom()),
|
||||||
|
new BasicNameValuePair("timestamp", "" + getTimestamp()),
|
||||||
|
new BasicNameValuePair("age", "" + getAge())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import java.util.List;
|
|||||||
import org.apache.http.entity.mime.FormBodyPart;
|
import org.apache.http.entity.mime.FormBodyPart;
|
||||||
import org.apache.http.entity.mime.content.ByteArrayBody;
|
import org.apache.http.entity.mime.content.ByteArrayBody;
|
||||||
import org.apache.http.entity.mime.content.ContentBody;
|
import org.apache.http.entity.mime.content.ContentBody;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.envaya.sms.task.ForwarderTask;
|
import org.envaya.sms.task.ForwarderTask;
|
||||||
|
|
||||||
public class IncomingMms extends IncomingMessage {
|
public class IncomingMms extends IncomingMessage {
|
||||||
@ -76,22 +75,13 @@ public class IncomingMms extends IncomingMessage {
|
|||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryForwardToServer()
|
@Override
|
||||||
|
protected ForwarderTask getForwarderTask()
|
||||||
{
|
{
|
||||||
if (numRetries > 0)
|
|
||||||
{
|
|
||||||
app.log("Retrying forwarding MMS from " + from);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
app.log("Forwarding MMS to server...");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FormBodyPart> formParts = new ArrayList<FormBodyPart>();
|
List<FormBodyPart> formParts = new ArrayList<FormBodyPart>();
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
String message = "";
|
|
||||||
JSONArray partsMetadata = new JSONArray();
|
JSONArray partsMetadata = new JSONArray();
|
||||||
|
|
||||||
for (MmsPart part : parts)
|
for (MmsPart part : parts)
|
||||||
@ -99,12 +89,7 @@ public class IncomingMms extends IncomingMessage {
|
|||||||
String formFieldName = "part" + i;
|
String formFieldName = "part" + i;
|
||||||
String text = part.getText();
|
String text = part.getText();
|
||||||
String contentType = part.getContentType();
|
String contentType = part.getContentType();
|
||||||
String partName = part.getName();
|
String partName = part.getName();
|
||||||
|
|
||||||
if ("text/plain".equals(contentType))
|
|
||||||
{
|
|
||||||
message = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentBody body;
|
ContentBody body;
|
||||||
|
|
||||||
@ -154,18 +139,33 @@ public class IncomingMms extends IncomingMessage {
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ForwarderTask task = new ForwarderTask(this,
|
ForwarderTask task = super.getForwarderTask();
|
||||||
new BasicNameValuePair("message", message),
|
task.addParam("mms_parts", partsMetadata.toString());
|
||||||
new BasicNameValuePair("message_type", App.MESSAGE_TYPE_MMS),
|
|
||||||
new BasicNameValuePair("mms_parts", partsMetadata.toString())
|
|
||||||
);
|
|
||||||
|
|
||||||
task.setFormParts(formParts);
|
task.setFormParts(formParts);
|
||||||
task.execute();
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessageBody()
|
||||||
|
{
|
||||||
|
for (MmsPart part : parts)
|
||||||
|
{
|
||||||
|
if ("text/plain".equals(part.getContentType()))
|
||||||
|
{
|
||||||
|
return part.getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
public Uri getUri()
|
public Uri getUri()
|
||||||
{
|
{
|
||||||
return Uri.withAppendedPath(App.INCOMING_URI, "mms/" + id);
|
return Uri.withAppendedPath(App.INCOMING_URI, "mms/" + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMessageType()
|
||||||
|
{
|
||||||
|
return App.MESSAGE_TYPE_MMS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,11 @@ import android.net.Uri;
|
|||||||
import android.telephony.SmsMessage;
|
import android.telephony.SmsMessage;
|
||||||
import java.security.InvalidParameterException;
|
import java.security.InvalidParameterException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.envaya.sms.task.ForwarderTask;
|
import org.envaya.sms.task.ForwarderTask;
|
||||||
|
|
||||||
|
|
||||||
public class IncomingSms extends IncomingMessage {
|
public class IncomingSms extends IncomingMessage {
|
||||||
|
|
||||||
protected String message;
|
|
||||||
|
|
||||||
// constructor for SMS retrieved from android.provider.Telephony.SMS_RECEIVED intent
|
// constructor for SMS retrieved from android.provider.Telephony.SMS_RECEIVED intent
|
||||||
public IncomingSms(App app, List<SmsMessage> smsParts) throws InvalidParameterException {
|
public IncomingSms(App app, List<SmsMessage> smsParts) throws InvalidParameterException {
|
||||||
super(app,
|
super(app,
|
||||||
@ -42,12 +39,7 @@ public class IncomingSms extends IncomingMessage {
|
|||||||
public IncomingSms(App app, String from, String message, long timestampMillis) {
|
public IncomingSms(App app, String from, String message, long timestampMillis) {
|
||||||
super(app, from, timestampMillis);
|
super(app, from, timestampMillis);
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMessageBody()
|
|
||||||
{
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayType()
|
public String getDisplayType()
|
||||||
{
|
{
|
||||||
@ -62,18 +54,9 @@ public class IncomingSms extends IncomingMessage {
|
|||||||
+ timestamp + "/" +
|
+ timestamp + "/" +
|
||||||
Uri.encode(message));
|
Uri.encode(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryForwardToServer() {
|
|
||||||
|
|
||||||
if (numRetries > 0)
|
|
||||||
{
|
|
||||||
app.log("Retrying forwarding SMS from " + from);
|
|
||||||
}
|
|
||||||
|
|
||||||
new ForwarderTask(this,
|
|
||||||
new BasicNameValuePair("message_type", App.MESSAGE_TYPE_SMS),
|
|
||||||
new BasicNameValuePair("message", getMessageBody())
|
|
||||||
).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public String getMessageType()
|
||||||
|
{
|
||||||
|
return App.MESSAGE_TYPE_SMS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,7 @@ package org.envaya.sms;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import java.io.File;
|
import java.util.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Utilities for parsing IncomingMms from the MMS content provider tables,
|
* Utilities for parsing IncomingMms from the MMS content provider tables,
|
||||||
@ -56,9 +52,25 @@ public class MmsUtils
|
|||||||
{
|
{
|
||||||
long partId = cur.getLong(0);
|
long partId = cur.getLong(0);
|
||||||
|
|
||||||
MmsPart part = new MmsPart(app, partId);
|
String contentType = cur.getString(1);
|
||||||
part.setContentType(cur.getString(1));
|
|
||||||
part.setName(cur.getString(2));
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MmsPart part = new MmsPart(app, partId);
|
||||||
|
|
||||||
|
part.setContentType(contentType);
|
||||||
|
|
||||||
|
String name = cur.getString(2);
|
||||||
|
|
||||||
|
if (name == null || name.length() == 0)
|
||||||
|
{
|
||||||
|
name = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
part.setName(name);
|
||||||
|
|
||||||
part.setDataFile(cur.getString(5));
|
part.setDataFile(cur.getString(5));
|
||||||
|
|
||||||
@ -116,9 +128,17 @@ public class MmsUtils
|
|||||||
{
|
{
|
||||||
long id = c.getLong(0);
|
long id = c.getLong(0);
|
||||||
long date = c.getLong(2);
|
long date = c.getLong(2);
|
||||||
|
|
||||||
|
String from = getSenderNumber(id);
|
||||||
|
|
||||||
|
if (from == null)
|
||||||
|
{
|
||||||
|
app.log("Ignoring MMS "+id+" for now because sender number is null");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
IncomingMms mms = new IncomingMms(app,
|
IncomingMms mms = new IncomingMms(app,
|
||||||
getSenderNumber(id),
|
from,
|
||||||
date * 1000, // MMS timestamp is in seconds for some reason,
|
date * 1000, // MMS timestamp is in seconds for some reason,
|
||||||
// while everything else is in ms
|
// while everything else is in ms
|
||||||
id);
|
id);
|
||||||
|
@ -6,8 +6,7 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.telephony.SmsManager;
|
import android.os.SystemClock;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -19,6 +18,7 @@ import java.util.Queue;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.envaya.sms.receiver.DequeueOutgoingMessageReceiver;
|
import org.envaya.sms.receiver.DequeueOutgoingMessageReceiver;
|
||||||
|
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
||||||
import org.envaya.sms.task.HttpTask;
|
import org.envaya.sms.task.HttpTask;
|
||||||
|
|
||||||
public class Outbox {
|
public class Outbox {
|
||||||
@ -39,7 +39,7 @@ public class Outbox {
|
|||||||
// cache of next time we can send the first message in queue without
|
// cache of next time we can send the first message in queue without
|
||||||
// exceeding android sending limit
|
// exceeding android sending limit
|
||||||
private long nextValidOutgoingTime;
|
private long nextValidOutgoingTime;
|
||||||
|
|
||||||
// enqueue outgoing messages in descending order by priority, ascending by local id
|
// enqueue outgoing messages in descending order by priority, ascending by local id
|
||||||
// (order in which message was received)
|
// (order in which message was received)
|
||||||
private PriorityQueue<OutgoingMessage> outgoingQueue = new PriorityQueue<OutgoingMessage>(10,
|
private PriorityQueue<OutgoingMessage> outgoingQueue = new PriorityQueue<OutgoingMessage>(10,
|
||||||
@ -75,7 +75,10 @@ public class Outbox {
|
|||||||
logMessage = "sent successfully";
|
logMessage = "sent successfully";
|
||||||
} else if (status.equals(App.STATUS_FAILED)) {
|
} else if (status.equals(App.STATUS_FAILED)) {
|
||||||
logMessage = "could not be sent (" + errorMessage + ")";
|
logMessage = "could not be sent (" + errorMessage + ")";
|
||||||
} else {
|
} else if (status.equals(App.STATUS_CANCELLED)) {
|
||||||
|
logMessage = "cancelled";
|
||||||
|
}
|
||||||
|
else {
|
||||||
logMessage = "queued";
|
logMessage = "queued";
|
||||||
}
|
}
|
||||||
String smsDesc = sms.getLogName();
|
String smsDesc = sms.getLogName();
|
||||||
@ -116,6 +119,8 @@ public class Outbox {
|
|||||||
{
|
{
|
||||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Sent);
|
sms.setProcessingState(OutgoingMessage.ProcessingState.Sent);
|
||||||
|
|
||||||
|
sms.clearSendTimeout();
|
||||||
|
|
||||||
notifyMessageStatus(sms, App.STATUS_SENT, "");
|
notifyMessageStatus(sms, App.STATUS_SENT, "");
|
||||||
|
|
||||||
Uri uri = sms.getUri();
|
Uri uri = sms.getUri();
|
||||||
@ -146,9 +151,11 @@ public class Outbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void messageFailed(OutgoingMessage sms, String error)
|
public synchronized void messageFailed(OutgoingMessage sms, String error)
|
||||||
{
|
{
|
||||||
|
sms.clearSendTimeout();
|
||||||
|
|
||||||
if (sms.scheduleRetry())
|
if (sms.scheduleRetry())
|
||||||
{
|
{
|
||||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Scheduled);
|
sms.setProcessingState(OutgoingMessage.ProcessingState.Scheduled);
|
||||||
@ -164,49 +171,33 @@ public class Outbox {
|
|||||||
maybeDequeueMessage();
|
maybeDequeueMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void sendMessage(OutgoingMessage sms) {
|
public synchronized void sendMessage(OutgoingMessage message) {
|
||||||
|
|
||||||
String to = sms.getTo();
|
try
|
||||||
if (to == null || to.length() == 0)
|
|
||||||
{
|
{
|
||||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
message.validate();
|
||||||
"Destination address is empty");
|
}
|
||||||
return;
|
catch (ValidationException ex)
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.isForwardablePhoneNumber(to))
|
|
||||||
{
|
{
|
||||||
// this is mostly to prevent accidentally sending real messages to
|
notifyMessageStatus(message, App.STATUS_FAILED, ex.getMessage());
|
||||||
// random people while testing...
|
return;
|
||||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
|
||||||
"Destination address is not allowed");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String messageBody = sms.getMessageBody();
|
Uri uri = message.getUri();
|
||||||
|
|
||||||
if (messageBody == null || messageBody.length() == 0)
|
|
||||||
{
|
|
||||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
|
||||||
"Message body is empty");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri uri = sms.getUri();
|
|
||||||
if (outgoingMessages.containsKey(uri)) {
|
if (outgoingMessages.containsKey(uri)) {
|
||||||
app.debug("Duplicate outgoing " + sms.getLogName() + ", skipping");
|
app.debug("Duplicate outgoing " + message.getLogName() + ", skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recentSentMessageUris.contains(uri))
|
if (recentSentMessageUris.contains(uri))
|
||||||
{
|
{
|
||||||
app.debug("Outgoing " + sms.getLogName() + " already sent, re-notifying server");
|
app.debug("Outgoing " + message.getLogName() + " already sent, re-notifying server");
|
||||||
notifyMessageStatus(sms, App.STATUS_SENT, "");
|
notifyMessageStatus(message, App.STATUS_SENT, "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingMessages.put(uri, sms);
|
outgoingMessages.put(uri, message);
|
||||||
enqueueMessage(sms);
|
enqueueMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void deleteMessage(OutgoingMessage message)
|
public synchronized void deleteMessage(OutgoingMessage message)
|
||||||
@ -222,7 +213,7 @@ public class Outbox {
|
|||||||
numSendingOutgoingMessages--;
|
numSendingOutgoingMessages--;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyMessageStatus(message, App.STATUS_FAILED,
|
notifyMessageStatus(message, App.STATUS_CANCELLED,
|
||||||
"deleted by user");
|
"deleted by user");
|
||||||
app.log(message.getDescription() + " deleted");
|
app.log(message.getDescription() + " deleted");
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
@ -233,46 +224,32 @@ public class Outbox {
|
|||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (nextValidOutgoingTime <= now && numSendingOutgoingMessages < 2)
|
if (nextValidOutgoingTime <= now && numSendingOutgoingMessages < 2)
|
||||||
{
|
{
|
||||||
OutgoingMessage sms = outgoingQueue.peek();
|
OutgoingMessage message = outgoingQueue.peek();
|
||||||
|
|
||||||
if (sms == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SmsManager smgr = SmsManager.getDefault();
|
OutgoingMessage.ScheduleInfo schedule = message.scheduleSend();
|
||||||
ArrayList<String> bodyParts = smgr.divideMessage(sms.getMessageBody());
|
|
||||||
|
|
||||||
int numParts = bodyParts.size();
|
if (!schedule.now)
|
||||||
|
|
||||||
if (numParts > App.OUTGOING_SMS_MAX_COUNT)
|
|
||||||
{
|
{
|
||||||
outgoingQueue.poll();
|
nextValidOutgoingTime = schedule.time;
|
||||||
outgoingMessages.remove(sms.getUri());
|
|
||||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
|
||||||
"Message has too many parts ("+(numParts)+")");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String packageName = app.chooseOutgoingSmsPackage(numParts);
|
|
||||||
|
|
||||||
if (packageName == null)
|
|
||||||
{
|
|
||||||
nextValidOutgoingTime = app.getNextValidOutgoingTime(numParts);
|
|
||||||
|
|
||||||
if (nextValidOutgoingTime <= now) // should never happen
|
if (nextValidOutgoingTime <= now) // should never happen
|
||||||
{
|
{
|
||||||
nextValidOutgoingTime = now + 2000;
|
nextValidOutgoingTime = now + 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
long diff = nextValidOutgoingTime - now;
|
long diff = nextValidOutgoingTime - now;
|
||||||
|
|
||||||
app.log("Waiting for " + (diff/1000) + " seconds");
|
app.log("Waiting for " + (diff/1000) + " seconds");
|
||||||
|
|
||||||
AlarmManager alarm = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarm = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
Intent intent = new Intent(app, DequeueOutgoingMessageReceiver.class);
|
Intent intent = new Intent(app, DequeueOutgoingMessageReceiver.class);
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
|
||||||
0,
|
0,
|
||||||
intent,
|
intent,
|
||||||
@ -282,16 +259,18 @@ public class Outbox {
|
|||||||
AlarmManager.RTC_WAKEUP,
|
AlarmManager.RTC_WAKEUP,
|
||||||
nextValidOutgoingTime,
|
nextValidOutgoingTime,
|
||||||
pendingIntent);
|
pendingIntent);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingQueue.poll();
|
outgoingQueue.poll();
|
||||||
numSendingOutgoingMessages++;
|
numSendingOutgoingMessages++;
|
||||||
|
|
||||||
|
message.setProcessingState(OutgoingMessage.ProcessingState.Sending);
|
||||||
|
message.send(schedule);
|
||||||
|
|
||||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Sending);
|
message.setSendTimeout();
|
||||||
|
|
||||||
sms.trySend(bodyParts, packageName);
|
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
|
||||||
package org.envaya.sms;
|
package org.envaya.sms;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
import org.envaya.sms.receiver.OutgoingMessageRetry;
|
import org.envaya.sms.receiver.OutgoingMessageRetry;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import java.util.ArrayList;
|
import android.os.SystemClock;
|
||||||
|
import org.envaya.sms.receiver.OutgoingMessageTimeout;
|
||||||
|
|
||||||
public class OutgoingMessage extends QueuedMessage {
|
public abstract class OutgoingMessage extends QueuedMessage {
|
||||||
|
|
||||||
private String serverId;
|
private String serverId;
|
||||||
private String message;
|
private String message;
|
||||||
@ -18,6 +22,12 @@ public class OutgoingMessage extends QueuedMessage {
|
|||||||
|
|
||||||
private ProcessingState state = ProcessingState.None;
|
private ProcessingState state = ProcessingState.None;
|
||||||
|
|
||||||
|
public class ScheduleInfo
|
||||||
|
{
|
||||||
|
public boolean now = false;
|
||||||
|
public long time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
public enum ProcessingState
|
public enum ProcessingState
|
||||||
{
|
{
|
||||||
None, // not doing anything with this sms now... just sitting around
|
None, // not doing anything with this sms now... just sitting around
|
||||||
@ -43,6 +53,13 @@ public class OutgoingMessage extends QueuedMessage {
|
|||||||
this.state = status;
|
this.state = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCancelable()
|
||||||
|
{
|
||||||
|
return this.state == ProcessingState.None
|
||||||
|
|| this.state == ProcessingState.Queued
|
||||||
|
|| this.state == ProcessingState.Scheduled;
|
||||||
|
}
|
||||||
|
|
||||||
static synchronized int getNextLocalId()
|
static synchronized int getNextLocalId()
|
||||||
{
|
{
|
||||||
return nextLocalId++;
|
return nextLocalId++;
|
||||||
@ -59,10 +76,10 @@ public class OutgoingMessage extends QueuedMessage {
|
|||||||
("_o" + localId) : serverId));
|
("_o" + localId) : serverId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLogName()
|
public static Uri getUriForServerId(String serverId)
|
||||||
{
|
{
|
||||||
return (serverId == null) ? "SMS reply" : ("SMS id=" + serverId);
|
return Uri.withAppendedPath(App.OUTGOING_URI, serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getServerId()
|
public String getServerId()
|
||||||
{
|
{
|
||||||
@ -112,32 +129,7 @@ public class OutgoingMessage extends QueuedMessage {
|
|||||||
public int getPriority()
|
public int getPriority()
|
||||||
{
|
{
|
||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void trySend(ArrayList<String> bodyParts, String packageName)
|
|
||||||
{
|
|
||||||
if (numRetries == 0)
|
|
||||||
{
|
|
||||||
app.log("Sending " + getLogName() + " to " + getTo());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
app.log("Retrying sending " + getLogName() + " to " + getTo());
|
|
||||||
}
|
|
||||||
|
|
||||||
int numParts = bodyParts.size();
|
|
||||||
if (numParts > 1)
|
|
||||||
{
|
|
||||||
app.log("(Multipart message with "+numParts+" parts)");
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = new Intent(packageName + App.OUTGOING_SMS_INTENT_SUFFIX, this.getUri());
|
|
||||||
intent.putExtra(App.OUTGOING_SMS_EXTRA_DELIVERY_REPORT, false);
|
|
||||||
intent.putExtra(App.OUTGOING_SMS_EXTRA_TO, getTo());
|
|
||||||
intent.putExtra(App.OUTGOING_SMS_EXTRA_BODY, bodyParts);
|
|
||||||
|
|
||||||
app.sendBroadcast(intent, "android.permission.SEND_SMS");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Intent getRetryIntent() {
|
protected Intent getRetryIntent() {
|
||||||
Intent intent = new Intent(app, OutgoingMessageRetry.class);
|
Intent intent = new Intent(app, OutgoingMessageRetry.class);
|
||||||
@ -165,8 +157,42 @@ public class OutgoingMessage extends QueuedMessage {
|
|||||||
return getDisplayType() + " to " + getTo();
|
return getDisplayType() + " to " + getTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayType()
|
abstract String getLogName();
|
||||||
|
|
||||||
|
public void validate() throws ValidationException
|
||||||
{
|
{
|
||||||
return "SMS";
|
}
|
||||||
|
|
||||||
|
abstract ScheduleInfo scheduleSend();
|
||||||
|
abstract void send(ScheduleInfo schedule);
|
||||||
|
|
||||||
|
protected PendingIntent getTimeoutPendingIntent()
|
||||||
|
{
|
||||||
|
Intent timeout = new Intent(app, OutgoingMessageTimeout.class);
|
||||||
|
timeout.setData(getUri());
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(app,
|
||||||
|
0,
|
||||||
|
timeout,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendTimeout()
|
||||||
|
{
|
||||||
|
AlarmManager alarm = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
|
PendingIntent timeoutIntent = getTimeoutPendingIntent();
|
||||||
|
|
||||||
|
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
SystemClock.elapsedRealtime() + App.MESSAGE_SEND_TIMEOUT, timeoutIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSendTimeout()
|
||||||
|
{
|
||||||
|
AlarmManager alarm = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
|
PendingIntent timeoutIntent = getTimeoutPendingIntent();
|
||||||
|
|
||||||
|
alarm.cancel(timeoutIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
src/org/envaya/sms/OutgoingSms.java
Normal file
127
src/org/envaya/sms/OutgoingSms.java
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package org.envaya.sms;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.telephony.SmsManager;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class OutgoingSms extends OutgoingMessage {
|
||||||
|
|
||||||
|
public OutgoingSms(App app)
|
||||||
|
{
|
||||||
|
super(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLogName()
|
||||||
|
{
|
||||||
|
return "SMS";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<String> _bodyParts;
|
||||||
|
|
||||||
|
public ArrayList<String> getBodyParts()
|
||||||
|
{
|
||||||
|
if (_bodyParts == null)
|
||||||
|
{
|
||||||
|
SmsManager smgr = SmsManager.getDefault();
|
||||||
|
_bodyParts = smgr.divideMessage(getMessageBody());
|
||||||
|
}
|
||||||
|
return _bodyParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumParts()
|
||||||
|
{
|
||||||
|
return getBodyParts().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScheduleInfo extends OutgoingMessage.ScheduleInfo
|
||||||
|
{
|
||||||
|
public String packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutgoingMessage.ScheduleInfo scheduleSend()
|
||||||
|
{
|
||||||
|
ScheduleInfo schedule = new ScheduleInfo();
|
||||||
|
|
||||||
|
int numParts = getNumParts();
|
||||||
|
String packageName = app.chooseOutgoingSmsPackage(numParts);
|
||||||
|
|
||||||
|
if (packageName == null)
|
||||||
|
{
|
||||||
|
schedule.time = app.getNextValidOutgoingTime(numParts);
|
||||||
|
schedule.now = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
schedule.now = true;
|
||||||
|
schedule.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(OutgoingMessage.ScheduleInfo _schedule)
|
||||||
|
{
|
||||||
|
ScheduleInfo schedule = (ScheduleInfo)_schedule;
|
||||||
|
|
||||||
|
if (numRetries == 0)
|
||||||
|
{
|
||||||
|
app.log("Sending " + getDescription());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.log("Retrying sending " + getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> bodyParts = getBodyParts();
|
||||||
|
int numParts = bodyParts.size();
|
||||||
|
if (numParts > 1)
|
||||||
|
{
|
||||||
|
app.log("(Multipart message with "+numParts+" parts)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(schedule.packageName + App.OUTGOING_SMS_INTENT_SUFFIX, this.getUri());
|
||||||
|
intent.putExtra(App.OUTGOING_SMS_EXTRA_DELIVERY_REPORT, false);
|
||||||
|
intent.putExtra(App.OUTGOING_SMS_EXTRA_TO, getTo());
|
||||||
|
intent.putExtra(App.OUTGOING_SMS_EXTRA_BODY, bodyParts);
|
||||||
|
|
||||||
|
app.sendBroadcast(intent, "android.permission.SEND_SMS");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayType()
|
||||||
|
{
|
||||||
|
return "SMS";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate() throws ValidationException
|
||||||
|
{
|
||||||
|
super.validate();
|
||||||
|
|
||||||
|
String to = getTo();
|
||||||
|
if (to == null || to.length() == 0)
|
||||||
|
{
|
||||||
|
throw new ValidationException("Destination address is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.isForwardablePhoneNumber(to))
|
||||||
|
{
|
||||||
|
// this is mostly to prevent accidentally sending real messages to
|
||||||
|
// random people while testing...
|
||||||
|
throw new ValidationException("Destination address is not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
String messageBody = getMessageBody();
|
||||||
|
|
||||||
|
if (messageBody == null || messageBody.length() == 0)
|
||||||
|
{
|
||||||
|
throw new ValidationException("Message body is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
int numParts = getNumParts();
|
||||||
|
|
||||||
|
if (numParts > App.OUTGOING_SMS_MAX_COUNT)
|
||||||
|
{
|
||||||
|
throw new ValidationException("Message has too many parts ("+numParts+")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/org/envaya/sms/ValidationException.java
Normal file
9
src/org/envaya/sms/ValidationException.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package org.envaya.sms;
|
||||||
|
|
||||||
|
public class ValidationException extends Exception {
|
||||||
|
|
||||||
|
public ValidationException(String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
41
src/org/envaya/sms/XmlUtils.java
Normal file
41
src/org/envaya/sms/XmlUtils.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package org.envaya.sms;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
public class XmlUtils {
|
||||||
|
|
||||||
|
public static Document parseResponse(HttpResponse response)
|
||||||
|
throws IOException, ParserConfigurationException, SAXException {
|
||||||
|
InputStream responseStream = response.getEntity().getContent();
|
||||||
|
DocumentBuilder xmlBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||||
|
return xmlBuilder.parse(responseStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getElementText(Element element) {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
NodeList childNodes = element.getChildNodes();
|
||||||
|
int numChildren = childNodes.getLength();
|
||||||
|
for (int j = 0; j < numChildren; j++) {
|
||||||
|
text.append(childNodes.item(j).getNodeValue());
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getErrorText(Document xml) {
|
||||||
|
NodeList errorNodes = xml.getElementsByTagName("error");
|
||||||
|
if (errorNodes.getLength() > 0) {
|
||||||
|
Element errorElement = (Element) errorNodes.item(0);
|
||||||
|
return getElementText(errorElement);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,16 @@ public class ExpansionPackInstallReceiver extends BroadcastReceiver
|
|||||||
{
|
{
|
||||||
App app = (App) context.getApplicationContext();
|
App app = (App) context.getApplicationContext();
|
||||||
|
|
||||||
|
String action = intent.getAction();
|
||||||
|
|
||||||
String packageName = intent.getData().getSchemeSpecificPart();
|
String packageName = intent.getData().getSchemeSpecificPart();
|
||||||
|
|
||||||
if (packageName != null && packageName.startsWith(context.getPackageName() + ".pack"))
|
if (packageName != null)
|
||||||
{
|
{
|
||||||
app.updateExpansionPacks();
|
if (packageName.startsWith(context.getPackageName() + ".pack"))
|
||||||
|
{
|
||||||
|
app.updateExpansionPacks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -35,8 +35,8 @@ public class MessageStatusNotifier extends BroadcastReceiver {
|
|||||||
{
|
{
|
||||||
// TODO: process message status for parts other than the first one
|
// TODO: process message status for parts other than the first one
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int resultCode = getResultCode();
|
int resultCode = getResultCode();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package org.envaya.sms.receiver;
|
package org.envaya.sms.receiver;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
|
|
||||||
public class OutgoingMessagePoller extends BroadcastReceiver {
|
public class OutgoingMessagePoller extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
App app = (App) context.getApplicationContext();
|
App app = (App) context.getApplicationContext();
|
||||||
app.checkOutgoingMessages();
|
app.checkOutgoingMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
src/org/envaya/sms/receiver/OutgoingMessageTimeout.java
Normal file
29
src/org/envaya/sms/receiver/OutgoingMessageTimeout.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
package org.envaya.sms.receiver;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import org.envaya.sms.App;
|
||||||
|
import org.envaya.sms.OutgoingMessage;
|
||||||
|
|
||||||
|
public class OutgoingMessageTimeout extends BroadcastReceiver
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
App app = (App) context.getApplicationContext();
|
||||||
|
if (!app.isEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutgoingMessage message = app.outbox.getMessage(intent.getData());
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.outbox.messageFailed(message, "Timeout while attempting to send message");
|
||||||
|
}
|
||||||
|
}
|
200
src/org/envaya/sms/task/BaseHttpTask.java
Normal file
200
src/org/envaya/sms/task/BaseHttpTask.java
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package org.envaya.sms.task;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import org.envaya.sms.App;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.mime.FormBodyPart;
|
||||||
|
import org.apache.http.entity.mime.MultipartEntity;
|
||||||
|
import org.apache.http.entity.mime.content.StringBody;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
|
public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||||
|
|
||||||
|
protected App app;
|
||||||
|
protected String url;
|
||||||
|
protected List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
|
||||||
|
|
||||||
|
private List<FormBodyPart> formParts;
|
||||||
|
protected boolean useMultipartPost = false;
|
||||||
|
protected HttpPost post;
|
||||||
|
protected Throwable requestException;
|
||||||
|
|
||||||
|
public BaseHttpTask(App app, String url, BasicNameValuePair... paramsArr)
|
||||||
|
{
|
||||||
|
this.url = url;
|
||||||
|
this.app = app;
|
||||||
|
params = new ArrayList<BasicNameValuePair>(Arrays.asList(paramsArr));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addParam(String name, String value)
|
||||||
|
{
|
||||||
|
params.add(new BasicNameValuePair(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFormParts(List<FormBodyPart> formParts)
|
||||||
|
{
|
||||||
|
useMultipartPost = true;
|
||||||
|
this.formParts = formParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpPost makeHttpPost() throws Exception
|
||||||
|
{
|
||||||
|
HttpPost httpPost = new HttpPost(url);
|
||||||
|
|
||||||
|
httpPost.setHeader("User-Agent", "EnvayaSMS/" + app.getPackageInfo().versionName + " (Android; SDK "+Build.VERSION.SDK_INT + "; " + Build.MANUFACTURER + "; " + Build.MODEL+")");
|
||||||
|
|
||||||
|
if (useMultipartPost)
|
||||||
|
{
|
||||||
|
MultipartEntity entity = new MultipartEntity();//HttpMultipartMode.BROWSER_COMPATIBLE);
|
||||||
|
|
||||||
|
Charset charset = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
for (BasicNameValuePair param : params)
|
||||||
|
{
|
||||||
|
entity.addPart(param.getName(), new StringBody(param.getValue(), charset));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FormBodyPart formPart : formParts)
|
||||||
|
{
|
||||||
|
entity.addPart(formPart);
|
||||||
|
}
|
||||||
|
httpPost.setEntity(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpResponse doInBackground(String... ignored)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
post = makeHttpPost();
|
||||||
|
HttpClient client = app.getHttpClient();
|
||||||
|
return client.execute(post);
|
||||||
|
}
|
||||||
|
catch (Throwable ex)
|
||||||
|
{
|
||||||
|
requestException = ex;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String message = ex.getMessage();
|
||||||
|
// workaround for https://issues.apache.org/jira/browse/HTTPCLIENT-881
|
||||||
|
if ((ex instanceof IOException)
|
||||||
|
&& message != null && message.equals("Connection already shutdown"))
|
||||||
|
{
|
||||||
|
//app.log("Retrying request");
|
||||||
|
post = makeHttpPost();
|
||||||
|
HttpClient client = app.getHttpClient();
|
||||||
|
return client.execute(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable ex2)
|
||||||
|
{
|
||||||
|
requestException = ex2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidContentType(String contentType)
|
||||||
|
{
|
||||||
|
return true; // contentType.startsWith("text/xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(HttpResponse response) {
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
|
Header contentTypeHeader = response.getFirstHeader("Content-Type");
|
||||||
|
String contentType = (contentTypeHeader != null) ? contentTypeHeader.getValue() : "";
|
||||||
|
|
||||||
|
boolean validContentType = isValidContentType(contentType);
|
||||||
|
|
||||||
|
if (statusCode == 200)
|
||||||
|
{
|
||||||
|
if (validContentType)
|
||||||
|
{
|
||||||
|
handleResponse(response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Invalid response type " + contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (statusCode >= 400 && statusCode <= 499 && validContentType)
|
||||||
|
{
|
||||||
|
handleErrorResponse(response);
|
||||||
|
handleFailure();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("HTTP " + statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable ex)
|
||||||
|
{
|
||||||
|
post.abort();
|
||||||
|
handleResponseException(ex);
|
||||||
|
handleFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.getEntity().consumeContent();
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handleRequestException(requestException);
|
||||||
|
handleFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleResponse(HttpResponse response) throws Exception
|
||||||
|
{
|
||||||
|
// if we get a valid server response after a connectivity error, then forward any pending messages
|
||||||
|
if (app.hasConnectivityError())
|
||||||
|
{
|
||||||
|
app.onConnectivityRestored();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleErrorResponse(HttpResponse response) throws Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleFailure()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleRequestException(Throwable ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleResponseException(Throwable ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
101
src/org/envaya/sms/task/CheckConnectivityTask.java
Normal file
101
src/org/envaya/sms/task/CheckConnectivityTask.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package org.envaya.sms.task;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import org.envaya.sms.App;
|
||||||
|
import org.envaya.sms.receiver.ReenableWifiReceiver;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
|
||||||
|
public class CheckConnectivityTask extends AsyncTask<String, Void, Boolean> {
|
||||||
|
|
||||||
|
protected App app;
|
||||||
|
protected String hostName;
|
||||||
|
protected int networkType;
|
||||||
|
|
||||||
|
public CheckConnectivityTask(App app, String hostName, int networkType)
|
||||||
|
{
|
||||||
|
this.app = app;
|
||||||
|
this.hostName = hostName;
|
||||||
|
this.networkType = networkType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean doInBackground(String... ignored)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InetAddress addr = InetAddress.getByName(hostName);
|
||||||
|
if (addr.isReachable(App.HTTP_CONNECTION_TIMEOUT))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
// just what we suspected...
|
||||||
|
// server not reachable on this interface
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean reachable)
|
||||||
|
{
|
||||||
|
if (reachable.booleanValue())
|
||||||
|
{
|
||||||
|
app.log("OK");
|
||||||
|
app.onConnectivityRestored();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.log("Can't connect to "+hostName+".");
|
||||||
|
|
||||||
|
WifiManager wmgr = (WifiManager)app.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
|
||||||
|
if (!app.isNetworkFailoverEnabled())
|
||||||
|
{
|
||||||
|
app.debug("Network failover disabled.");
|
||||||
|
}
|
||||||
|
else if (networkType == ConnectivityManager.TYPE_WIFI)
|
||||||
|
{
|
||||||
|
app.log("Switching from WIFI to MOBILE");
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
|
||||||
|
0,
|
||||||
|
new Intent(app, ReenableWifiReceiver.class),
|
||||||
|
0);
|
||||||
|
|
||||||
|
// set an alarm to try restoring Wi-Fi in a little while
|
||||||
|
AlarmManager alarm =
|
||||||
|
(AlarmManager)app.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
|
alarm.set(
|
||||||
|
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
SystemClock.elapsedRealtime() + App.DISABLE_WIFI_INTERVAL,
|
||||||
|
pendingIntent);
|
||||||
|
|
||||||
|
wmgr.setWifiEnabled(false);
|
||||||
|
}
|
||||||
|
else if (networkType == ConnectivityManager.TYPE_MOBILE
|
||||||
|
&& !wmgr.isWifiEnabled())
|
||||||
|
{
|
||||||
|
app.log("Switching from MOBILE to WIFI");
|
||||||
|
wmgr.setWifiEnabled(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.log("Can't automatically fix connectivity.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,6 @@ package org.envaya.sms.task;
|
|||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.envaya.sms.App;
|
|
||||||
import org.envaya.sms.IncomingMessage;
|
import org.envaya.sms.IncomingMessage;
|
||||||
import org.envaya.sms.OutgoingMessage;
|
import org.envaya.sms.OutgoingMessage;
|
||||||
|
|
||||||
@ -12,11 +11,13 @@ public class ForwarderTask extends HttpTask {
|
|||||||
|
|
||||||
public ForwarderTask(IncomingMessage message, BasicNameValuePair... paramsArr) {
|
public ForwarderTask(IncomingMessage message, BasicNameValuePair... paramsArr) {
|
||||||
super(message.app, paramsArr);
|
super(message.app, paramsArr);
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
}
|
||||||
params.add(new BasicNameValuePair("action", App.ACTION_INCOMING));
|
|
||||||
params.add(new BasicNameValuePair("from", message.getFrom()));
|
@Override
|
||||||
params.add(new BasicNameValuePair("timestamp", "" + message.getTimestamp()));
|
public boolean isValidContentType(String contentType)
|
||||||
|
{
|
||||||
|
return contentType.startsWith("text/xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -31,10 +32,12 @@ public class ForwarderTask extends HttpTask {
|
|||||||
app.outbox.sendMessage(reply);
|
app.outbox.sendMessage(reply);
|
||||||
}
|
}
|
||||||
app.inbox.messageForwarded(message);
|
app.inbox.messageForwarded(message);
|
||||||
|
|
||||||
|
super.handleResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleFailure() {
|
protected void handleFailure() {
|
||||||
app.inbox.messageFailed(message);
|
app.inbox.messageFailed(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,79 +4,57 @@
|
|||||||
*/
|
*/
|
||||||
package org.envaya.sms.task;
|
package org.envaya.sms.task;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import org.envaya.sms.XmlUtils;
|
||||||
|
import org.envaya.sms.OutgoingSms;
|
||||||
|
import org.envaya.sms.OutgoingMessage;
|
||||||
|
import org.envaya.sms.App;
|
||||||
|
import org.envaya.sms.Base64Coder;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.entity.mime.FormBodyPart;
|
|
||||||
import org.apache.http.entity.mime.MultipartEntity;
|
|
||||||
import org.apache.http.entity.mime.content.StringBody;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.envaya.sms.App;
|
|
||||||
import org.envaya.sms.Base64Coder;
|
|
||||||
import org.envaya.sms.OutgoingMessage;
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
|
public class HttpTask extends BaseHttpTask {
|
||||||
|
|
||||||
protected App app;
|
|
||||||
|
|
||||||
protected String url;
|
|
||||||
protected List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
|
|
||||||
|
|
||||||
protected BasicNameValuePair[] paramsArr;
|
|
||||||
|
|
||||||
private List<FormBodyPart> formParts;
|
|
||||||
private boolean useMultipartPost = false;
|
|
||||||
|
|
||||||
private HttpPost post;
|
|
||||||
private String logEntries;
|
private String logEntries;
|
||||||
|
|
||||||
private boolean retryOnConnectivityError;
|
private boolean retryOnConnectivityError;
|
||||||
|
|
||||||
|
private BasicNameValuePair[] ctorParams;
|
||||||
|
|
||||||
public HttpTask(App app, BasicNameValuePair... paramsArr)
|
public HttpTask(App app, BasicNameValuePair... paramsArr)
|
||||||
{
|
{
|
||||||
super();
|
super(app, app.getServerUrl(), paramsArr);
|
||||||
this.app = app;
|
this.ctorParams = paramsArr;
|
||||||
this.paramsArr = paramsArr;
|
|
||||||
params = new ArrayList<BasicNameValuePair>(Arrays.asList(paramsArr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRetryOnConnectivityError(boolean retry)
|
public void setRetryOnConnectivityError(boolean retry)
|
||||||
{
|
{
|
||||||
this.retryOnConnectivityError = retry;
|
this.retryOnConnectivityError = retry; // doesn't work with addParam!
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpTask getCopy()
|
protected HttpTask getCopy()
|
||||||
{
|
{
|
||||||
return new HttpTask(app, paramsArr);
|
return new HttpTask(app, ctorParams); // doesn't work with addParam!
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFormParts(List<FormBodyPart> formParts)
|
|
||||||
{
|
|
||||||
useMultipartPost = true;
|
|
||||||
this.formParts = formParts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSignature()
|
private String getSignature()
|
||||||
throws NoSuchAlgorithmException, UnsupportedEncodingException
|
throws NoSuchAlgorithmException, UnsupportedEncodingException
|
||||||
@ -110,6 +88,7 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
|
|||||||
return new String(Base64Coder.encode(digest));
|
return new String(Base64Coder.encode(digest));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected HttpResponse doInBackground(String... ignored) {
|
protected HttpResponse doInBackground(String... ignored) {
|
||||||
url = app.getServerUrl();
|
url = app.getServerUrl();
|
||||||
|
|
||||||
@ -122,181 +101,136 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
|
|||||||
|
|
||||||
params.add(new BasicNameValuePair("version", "" + app.getPackageInfo().versionCode));
|
params.add(new BasicNameValuePair("version", "" + app.getPackageInfo().versionCode));
|
||||||
params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber()));
|
params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber()));
|
||||||
params.add(new BasicNameValuePair("log", logEntries));
|
params.add(new BasicNameValuePair("send_limit", "" + app.getOutgoingMessageLimit()));
|
||||||
|
|
||||||
post = new HttpPost(url);
|
|
||||||
|
|
||||||
try
|
ConnectivityManager cm =
|
||||||
{
|
(ConnectivityManager)app.getSystemService(App.CONNECTIVITY_SERVICE);
|
||||||
if (useMultipartPost)
|
|
||||||
{
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
||||||
MultipartEntity entity = new MultipartEntity();//HttpMultipartMode.BROWSER_COMPATIBLE);
|
if (activeNetwork != null)
|
||||||
|
|
||||||
Charset charset = Charset.forName("UTF-8");
|
|
||||||
|
|
||||||
for (BasicNameValuePair param : params)
|
|
||||||
{
|
|
||||||
entity.addPart(param.getName(), new StringBody(param.getValue(), charset));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (FormBodyPart formPart : formParts)
|
|
||||||
{
|
|
||||||
entity.addPart(formPart);
|
|
||||||
}
|
|
||||||
post.setEntity(entity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpClient client = app.getHttpClient();
|
|
||||||
|
|
||||||
String signature = getSignature();
|
|
||||||
|
|
||||||
post.setHeader("X-Request-Signature", signature);
|
|
||||||
post.setHeader("User-Agent", "EnvayaSMS/" + app.getPackageInfo().versionName);
|
|
||||||
|
|
||||||
HttpResponse response = client.execute(post);
|
|
||||||
|
|
||||||
int statusCode = response.getStatusLine().getStatusCode();
|
|
||||||
if (statusCode == 200)
|
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
else if (statusCode == 403)
|
|
||||||
{
|
|
||||||
response.getEntity().consumeContent();
|
|
||||||
app.ungetNewLogEntries(logEntries);
|
|
||||||
app.log("Failed to authenticate to server");
|
|
||||||
app.log("(Phone number or password may be incorrect)");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response.getEntity().consumeContent();
|
|
||||||
app.ungetNewLogEntries(logEntries);
|
|
||||||
app.log("Received HTTP " + statusCode + " from server");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
{
|
||||||
post.abort();
|
params.add(new BasicNameValuePair("network", "" + activeNetwork.getTypeName()));
|
||||||
app.ungetNewLogEntries(logEntries);
|
|
||||||
app.logError("Error while contacting server", ex);
|
|
||||||
|
|
||||||
if (ex instanceof UnknownHostException || ex instanceof SocketTimeoutException)
|
|
||||||
{
|
|
||||||
if (retryOnConnectivityError)
|
|
||||||
{
|
|
||||||
app.addQueuedTask(getCopy());
|
|
||||||
}
|
|
||||||
|
|
||||||
app.asyncCheckConnectivity();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
catch (Throwable ex)
|
|
||||||
{
|
params.add(new BasicNameValuePair("log", logEntries));
|
||||||
post.abort();
|
|
||||||
app.ungetNewLogEntries(logEntries);
|
return super.doInBackground();
|
||||||
app.logError("Unexpected error while contacting server", ex, true);
|
}
|
||||||
return null;
|
|
||||||
}
|
@Override
|
||||||
|
protected HttpPost makeHttpPost()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
HttpPost httpPost = super.makeHttpPost();
|
||||||
|
|
||||||
|
String signature = getSignature();
|
||||||
|
|
||||||
|
httpPost.setHeader("X-Request-Signature", signature);
|
||||||
|
|
||||||
|
return httpPost;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getDefaultToAddress()
|
protected String getDefaultToAddress()
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<OutgoingMessage> parseResponseXML(HttpResponse response)
|
protected List<OutgoingMessage> parseResponseXML(HttpResponse response)
|
||||||
throws IOException, ParserConfigurationException, SAXException
|
throws IOException, ParserConfigurationException, SAXException
|
||||||
{
|
{
|
||||||
List<OutgoingMessage> messages = new ArrayList<OutgoingMessage>();
|
List<OutgoingMessage> messages = new ArrayList<OutgoingMessage>();
|
||||||
InputStream responseStream = response.getEntity().getContent();
|
Document xml = XmlUtils.parseResponse(response);
|
||||||
DocumentBuilder xmlBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
|
||||||
Document xml = xmlBuilder.parse(responseStream);
|
|
||||||
|
|
||||||
NodeList smsNodes = xml.getElementsByTagName("sms");
|
Element messagesElement = (Element) xml.getElementsByTagName("messages").item(0);
|
||||||
for (int i = 0; i < smsNodes.getLength(); i++) {
|
if (messagesElement != null)
|
||||||
Element smsElement = (Element) smsNodes.item(i);
|
{
|
||||||
|
NodeList messageNodes = messagesElement.getChildNodes();
|
||||||
OutgoingMessage sms = new OutgoingMessage(app);
|
int numNodes = messageNodes.getLength();
|
||||||
|
for (int i = 0; i < numNodes; i++)
|
||||||
sms.setFrom(app.getPhoneNumber());
|
|
||||||
|
|
||||||
String to = smsElement.getAttribute("to");
|
|
||||||
|
|
||||||
sms.setTo(to.equals("") ? getDefaultToAddress() : to);
|
|
||||||
|
|
||||||
String serverId = smsElement.getAttribute("id");
|
|
||||||
|
|
||||||
sms.setServerId(serverId.equals("") ? null : serverId);
|
|
||||||
|
|
||||||
String priorityStr = smsElement.getAttribute("priority");
|
|
||||||
|
|
||||||
if (!priorityStr.equals(""))
|
|
||||||
{
|
{
|
||||||
try
|
Element messageElement = (Element) messageNodes.item(i);
|
||||||
{
|
|
||||||
sms.setPriority(Integer.parseInt(priorityStr));
|
|
||||||
}
|
|
||||||
catch (NumberFormatException ex)
|
|
||||||
{
|
|
||||||
app.log("Invalid message priority: " + priorityStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder messageBody = new StringBuilder();
|
OutgoingMessage message = new OutgoingSms(app);
|
||||||
NodeList childNodes = smsElement.getChildNodes();
|
|
||||||
int numChildren = childNodes.getLength();
|
message.setFrom(app.getPhoneNumber());
|
||||||
for (int j = 0; j < numChildren; j++)
|
|
||||||
{
|
String to = messageElement.getAttribute("to");
|
||||||
messageBody.append(childNodes.item(j).getNodeValue());
|
|
||||||
|
message.setTo(to.equals("") ? getDefaultToAddress() : to);
|
||||||
|
|
||||||
|
String serverId = messageElement.getAttribute("id");
|
||||||
|
|
||||||
|
message.setServerId(serverId.equals("") ? null : serverId);
|
||||||
|
|
||||||
|
String priorityStr = messageElement.getAttribute("priority");
|
||||||
|
|
||||||
|
if (!priorityStr.equals(""))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message.setPriority(Integer.parseInt(priorityStr));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex)
|
||||||
|
{
|
||||||
|
app.log("Invalid message priority: " + priorityStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message.setMessageBody(XmlUtils.getElementText(messageElement));
|
||||||
|
|
||||||
|
messages.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
sms.setMessageBody(messageBody.toString());
|
|
||||||
|
|
||||||
messages.add(sms);
|
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(HttpResponse response) {
|
protected void handleFailure()
|
||||||
if (response != null)
|
{
|
||||||
|
app.ungetNewLogEntries(logEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleResponseException(Throwable ex)
|
||||||
|
{
|
||||||
|
app.logError("Error in server response", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleRequestException(Throwable ex)
|
||||||
|
{
|
||||||
|
if (ex instanceof IOException)
|
||||||
{
|
{
|
||||||
try
|
app.logError("Error while contacting server", ex);
|
||||||
{
|
|
||||||
handleResponse(response);
|
if (ex instanceof UnknownHostException || ex instanceof SocketTimeoutException)
|
||||||
}
|
{
|
||||||
catch (Throwable ex)
|
if (retryOnConnectivityError)
|
||||||
{
|
{
|
||||||
post.abort();
|
app.addQueuedTask(getCopy());
|
||||||
app.logError("Error processing server response", ex);
|
}
|
||||||
handleFailure();
|
|
||||||
}
|
app.onConnectivityError();
|
||||||
try
|
|
||||||
{
|
|
||||||
response.getEntity().consumeContent();
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
handleFailure();
|
app.logError("Unexpected error while contacting server", ex, true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleResponse(HttpResponse response) throws Exception
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleFailure()
|
@Override
|
||||||
|
public void handleErrorResponse(HttpResponse response) throws Exception
|
||||||
{
|
{
|
||||||
}
|
Document xml = XmlUtils.parseResponse(response);
|
||||||
|
String error = XmlUtils.getErrorText(xml);
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
app.log(error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.log("HTTP " +response.getStatusLine().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,12 @@ public class PollerTask extends HttpTask {
|
|||||||
super(app, new BasicNameValuePair("action", App.ACTION_OUTGOING));
|
super(app, new BasicNameValuePair("action", App.ACTION_OUTGOING));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidContentType(String contentType)
|
||||||
|
{
|
||||||
|
return contentType.startsWith("text/xml");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(HttpResponse response) {
|
protected void onPostExecute(HttpResponse response) {
|
||||||
super.onPostExecute(response);
|
super.onPostExecute(response);
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
package org.envaya.sms.ui;
|
package org.envaya.sms.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
import org.envaya.sms.R;
|
import org.envaya.sms.R;
|
||||||
|
|
||||||
public class Help extends Activity {
|
public class Help extends Activity {
|
||||||
|
|
||||||
|
private App app;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
@ -17,19 +25,44 @@ public class Help extends Activity {
|
|||||||
|
|
||||||
TextView help = (TextView) this.findViewById(R.id.help);
|
TextView help = (TextView) this.findViewById(R.id.help);
|
||||||
|
|
||||||
App app = (App)getApplication();
|
app = (App)getApplication();
|
||||||
|
|
||||||
String html = "<b>EnvayaSMS " + app.getPackageInfo().versionName + "</b><br /><br />"
|
String html = "<b>EnvayaSMS " + app.getPackageInfo().versionName + "</b><br /><br />"
|
||||||
+ "EnvayaSMS is a SMS gateway.<br /><br /> "
|
|
||||||
+ "It forwards all incoming SMS messages received by this phone to a server on the internet, "
|
|
||||||
+ "and also sends outgoing SMS messages from that server to other phones.<br /><br />"
|
|
||||||
+ "See sms.envaya.org for more information.<br /><br />"
|
|
||||||
+ "The Settings screen allows you configure EnvayaSMS to work with a particular server, "
|
|
||||||
+ "by entering the server URL, your phone number, "
|
|
||||||
+ "and the password assigned to your phone on the server.<br /><br />"
|
|
||||||
+ "Menu icons cc/by www.androidicons.com<br /><br />";
|
+ "Menu icons cc/by www.androidicons.com<br /><br />";
|
||||||
|
|
||||||
help.setText(Html.fromHtml(html));
|
help.setText(Html.fromHtml(html));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resetClicked(View v)
|
||||||
|
{
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Are you sure?")
|
||||||
|
.setPositiveButton("Yes",
|
||||||
|
new OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which)
|
||||||
|
{
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(app)
|
||||||
|
.edit()
|
||||||
|
.clear()
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
app.enabledChanged();
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setNegativeButton("Cancel",
|
||||||
|
new OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which)
|
||||||
|
{
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
311
src/org/envaya/sms/ui/Main.java → src/org/envaya/sms/ui/LogView.java
Executable file → Normal file
311
src/org/envaya/sms/ui/Main.java → src/org/envaya/sms/ui/LogView.java
Executable file → Normal file
@ -1,157 +1,156 @@
|
|||||||
package org.envaya.sms.ui;
|
package org.envaya.sms.ui;
|
||||||
|
|
||||||
import org.envaya.sms.task.HttpTask;
|
import org.envaya.sms.task.HttpTask;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.*;
|
||||||
import android.content.Context;
|
import android.net.Uri;
|
||||||
import android.content.Intent;
|
import android.os.Bundle;
|
||||||
import android.content.IntentFilter;
|
import android.preference.PreferenceManager;
|
||||||
import android.os.Bundle;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.preference.PreferenceManager;
|
import android.view.Menu;
|
||||||
import android.text.method.ScrollingMovementMethod;
|
import android.view.MenuInflater;
|
||||||
import android.view.Menu;
|
import android.view.MenuItem;
|
||||||
import android.view.MenuInflater;
|
import android.view.View;
|
||||||
import android.view.MenuItem;
|
import android.widget.ScrollView;
|
||||||
import android.view.View;
|
import android.widget.TextView;
|
||||||
import android.widget.ScrollView;
|
import org.apache.http.HttpResponse;
|
||||||
import android.widget.TextView;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.HttpResponse;
|
import org.envaya.sms.App;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.envaya.sms.R;
|
||||||
import org.envaya.sms.App;
|
|
||||||
import org.envaya.sms.R;
|
public class LogView extends Activity {
|
||||||
|
|
||||||
public class Main extends Activity {
|
private App app;
|
||||||
|
|
||||||
private App app;
|
private BroadcastReceiver logReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
private BroadcastReceiver logReceiver = new BroadcastReceiver() {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@Override
|
updateLogView();
|
||||||
public void onReceive(Context context, Intent intent) {
|
}
|
||||||
updateLogView();
|
};
|
||||||
}
|
|
||||||
};
|
private ScrollView scrollView;
|
||||||
|
private TextView info;
|
||||||
private ScrollView scrollView;
|
|
||||||
private TextView info;
|
private class TestTask extends HttpTask
|
||||||
|
{
|
||||||
private class TestTask extends HttpTask
|
public TestTask() {
|
||||||
{
|
super(LogView.this.app, new BasicNameValuePair("action", App.ACTION_TEST));
|
||||||
public TestTask() {
|
}
|
||||||
super(Main.this.app, new BasicNameValuePair("action", App.ACTION_TEST));
|
|
||||||
}
|
@Override
|
||||||
|
protected void handleResponse(HttpResponse response) throws Exception
|
||||||
@Override
|
{
|
||||||
protected void handleResponse(HttpResponse response) throws Exception
|
app.log("Server connection OK!");
|
||||||
{
|
}
|
||||||
parseResponseXML(response);
|
}
|
||||||
app.log("Server connection OK!");
|
|
||||||
}
|
private int lastLogEpoch = -1;
|
||||||
}
|
|
||||||
|
public void updateLogView()
|
||||||
private int lastLogEpoch = -1;
|
{
|
||||||
|
int logEpoch = app.getLogEpoch();
|
||||||
public void updateLogView()
|
CharSequence displayedLog = app.getDisplayedLog();
|
||||||
{
|
|
||||||
int logEpoch = app.getLogEpoch();
|
if (lastLogEpoch == logEpoch)
|
||||||
CharSequence displayedLog = app.getDisplayedLog();
|
{
|
||||||
|
int beforeLen = info.getText().length();
|
||||||
if (lastLogEpoch == logEpoch)
|
int afterLen = displayedLog.length();
|
||||||
{
|
|
||||||
int beforeLen = info.getText().length();
|
if (beforeLen == afterLen)
|
||||||
int afterLen = displayedLog.length();
|
{
|
||||||
|
return;
|
||||||
if (beforeLen == afterLen)
|
}
|
||||||
{
|
|
||||||
return;
|
info.append(displayedLog, beforeLen, afterLen);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
info.append(displayedLog, beforeLen, afterLen);
|
{
|
||||||
}
|
info.setText(displayedLog);
|
||||||
else
|
lastLogEpoch = logEpoch;
|
||||||
{
|
}
|
||||||
info.setText(displayedLog);
|
|
||||||
lastLogEpoch = logEpoch;
|
scrollView.post(new Runnable() { public void run() {
|
||||||
}
|
scrollView.fullScroll(View.FOCUS_DOWN);
|
||||||
|
} });
|
||||||
scrollView.post(new Runnable() { public void run() {
|
}
|
||||||
scrollView.fullScroll(View.FOCUS_DOWN);
|
|
||||||
} });
|
/** Called when the activity is first created. */
|
||||||
}
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
/** Called when the activity is first created. */
|
super.onCreate(savedInstanceState);
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
app = (App) getApplication();
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
setContentView(R.layout.log_view);
|
||||||
app = (App) getApplication();
|
PreferenceManager.setDefaultValues(this, R.xml.prefs, false);
|
||||||
|
|
||||||
setContentView(R.layout.main);
|
scrollView = (ScrollView) this.findViewById(R.id.info_scroll);
|
||||||
PreferenceManager.setDefaultValues(this, R.xml.prefs, false);
|
info = (TextView) this.findViewById(R.id.info);
|
||||||
|
|
||||||
scrollView = (ScrollView) this.findViewById(R.id.info_scroll);
|
info.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
info = (TextView) this.findViewById(R.id.info);
|
|
||||||
|
//info.setMovementMethod(new ScrollingMovementMethod());
|
||||||
info.setMovementMethod(new ScrollingMovementMethod());
|
|
||||||
|
updateLogView();
|
||||||
updateLogView();
|
|
||||||
|
IntentFilter logReceiverFilter = new IntentFilter();
|
||||||
IntentFilter logReceiverFilter = new IntentFilter();
|
logReceiverFilter.addAction(App.LOG_CHANGED_INTENT);
|
||||||
logReceiverFilter.addAction(App.LOG_CHANGED_INTENT);
|
registerReceiver(logReceiver, logReceiverFilter);
|
||||||
registerReceiver(logReceiver, logReceiverFilter);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public void onDestroy()
|
||||||
public void onDestroy()
|
{
|
||||||
{
|
this.unregisterReceiver(logReceiver);
|
||||||
this.unregisterReceiver(logReceiver);
|
super.onDestroy();
|
||||||
super.onDestroy();
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
// Handle item selection
|
||||||
// Handle item selection
|
switch (item.getItemId()) {
|
||||||
switch (item.getItemId()) {
|
case R.id.settings:
|
||||||
case R.id.settings:
|
startActivity(new Intent(this, Prefs.class));
|
||||||
startActivity(new Intent(this, Prefs.class));
|
return true;
|
||||||
return true;
|
case R.id.check_now:
|
||||||
case R.id.check_now:
|
app.checkOutgoingMessages();
|
||||||
app.checkOutgoingMessages();
|
return true;
|
||||||
return true;
|
case R.id.retry_now:
|
||||||
case R.id.retry_now:
|
app.retryStuckMessages();
|
||||||
app.retryStuckMessages();
|
return true;
|
||||||
return true;
|
case R.id.forward_inbox:
|
||||||
case R.id.forward_inbox:
|
startActivity(new Intent(this, MessagingInbox.class));
|
||||||
startActivity(new Intent(this, MessagingInbox.class));
|
return true;
|
||||||
return true;
|
case R.id.pending:
|
||||||
case R.id.pending:
|
startActivity(new Intent(this, PendingMessages.class));
|
||||||
startActivity(new Intent(this, PendingMessages.class));
|
return true;
|
||||||
return true;
|
case R.id.test:
|
||||||
case R.id.test:
|
app.log("Testing server connection...");
|
||||||
app.log("Testing server connection...");
|
new TestTask().execute();
|
||||||
new TestTask().execute();
|
return true;
|
||||||
return true;
|
default:
|
||||||
default:
|
return super.onOptionsItemSelected(item);
|
||||||
return super.onOptionsItemSelected(item);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// first time the Menu key is pressed
|
||||||
// first time the Menu key is pressed
|
@Override
|
||||||
@Override
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
MenuInflater inflater = getMenuInflater();
|
||||||
MenuInflater inflater = getMenuInflater();
|
inflater.inflate(R.menu.mainmenu, menu);
|
||||||
inflater.inflate(R.menu.mainmenu, menu);
|
|
||||||
|
return(true);
|
||||||
return(true);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
@Override
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
MenuItem retryItem = menu.findItem(R.id.retry_now);
|
||||||
MenuItem retryItem = menu.findItem(R.id.retry_now);
|
int pendingTasks = app.getPendingTaskCount();
|
||||||
int pendingTasks = app.getPendingTaskCount();
|
retryItem.setEnabled(pendingTasks > 0);
|
||||||
retryItem.setEnabled(pendingTasks > 0);
|
retryItem.setTitle("Retry All (" + pendingTasks + ")");
|
||||||
retryItem.setTitle("Retry All (" + pendingTasks + ")");
|
|
||||||
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -89,6 +89,10 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
|||||||
|
|
||||||
app.log("Server URL changed to: " + app.getDisplayString(app.getServerUrl()));
|
app.log("Server URL changed to: " + app.getDisplayString(app.getServerUrl()));
|
||||||
}
|
}
|
||||||
|
else if (key.equals("call_notifications"))
|
||||||
|
{
|
||||||
|
app.log("Call notifications changed to: " + (app.callNotificationsEnabled() ? "ON": "OFF"));
|
||||||
|
}
|
||||||
else if (key.equals("phone_number"))
|
else if (key.equals("phone_number"))
|
||||||
{
|
{
|
||||||
app.log("Phone number changed to: " + app.getDisplayString(app.getPhoneNumber()));
|
app.log("Phone number changed to: " + app.getDisplayString(app.getPhoneNumber()));
|
||||||
|
Loading…
Reference in New Issue
Block a user