mirror of
https://github.com/cwinfo/envayasms.git
synced 2024-12-04 12:35:32 +00:00
version 3.0 - real-time AMQP connections; change server API format from XML to JSON, update PHP server library; persistent storage of pending messages
This commit is contained in:
parent
f53ccc3cc9
commit
239ee1fd52
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
local.properties
|
||||
bin
|
||||
gen
|
||||
nbandroid
|
||||
nbandroid
|
||||
private
|
||||
local.properties
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "server/php/example/httpserver"]
|
||||
path = server/php/example/httpserver
|
||||
url = git://github.com/youngj/httpserver.git
|
||||
[submodule "server/php/example/php-amqplib"]
|
||||
path = server/php/example/php-amqplib
|
||||
url = git://github.com/youngj/php-amqplib.git
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.envaya.sms"
|
||||
android:versionCode="18"
|
||||
android:versionName="2.0.5">
|
||||
android:versionCode="29"
|
||||
android:versionName="3.0.0">
|
||||
|
||||
<uses-sdk android:minSdkVersion="4" />
|
||||
|
||||
@ -23,51 +23,69 @@
|
||||
|
||||
<application android:name="org.envaya.sms.App"
|
||||
android:icon="@drawable/icon" android:label="@string/app_name">
|
||||
|
||||
<activity android:name=".ui.LogView" android:label="@string/app_name">
|
||||
<activity android:name="org.envaya.sms.ui.Main" android:label="@string/app_name" android:noHistory="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.Help" android:label="EnvayaSMS : Help">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.TestPhoneNumbers" android:label="EnvayaSMS : Test Phone Numbers">
|
||||
<activity android:name="org.envaya.sms.ui.LogView" android:label="@string/log_view_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.envaya.sms.ui.Help" android:label="@string/help_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.envaya.sms.ui.TestPhoneNumbers" android:label="@string/test_phone_numbers_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.IgnoredPhoneNumbers" android:label="EnvayaSMS : Ignored Phone Numbers">
|
||||
<activity android:name="org.envaya.sms.ui.IgnoredPhoneNumbers" android:label="@string/ignored_phone_numbers_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.MessagingInbox" android:label="EnvayaSMS : Forward Inbox">
|
||||
<activity android:name="org.envaya.sms.ui.MessagingSmsInbox" android:label="@string/forward_saved_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.envaya.sms.ui.MessagingMmsInbox" android:label="@string/forward_saved_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.PendingMessages" android:label="EnvayaSMS : Pending Messages">
|
||||
<activity android:name="org.envaya.sms.ui.MessagingSentSms" android:label="@string/forward_saved_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.envaya.sms.ui.PendingMessages" android:label="@string/pending_messages_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.Prefs" android:label="EnvayaSMS : Settings">
|
||||
<activity android:name="org.envaya.sms.ui.Prefs" android:label="@string/settings_title">
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.envaya.sms.ui.ExpansionPacks" android:label="...">
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.SmsReceiver">
|
||||
<receiver android:name="org.envaya.sms.receiver.SmsReceiver">
|
||||
<intent-filter android:priority="101">
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.OutgoingSmsReceiver">
|
||||
<receiver android:name="org.envaya.sms.receiver.OutgoingSmsReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.envaya.sms.OUTGOING_SMS" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.MessageStatusNotifier" android:exported="true">
|
||||
<receiver android:name="org.envaya.sms.receiver.MessageStatusNotifier" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="org.envaya.sms.MESSAGE_STATUS" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.envaya.sms.receiver.NudgeReceiver" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="org.envaya.sms.NUDGE" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!--
|
||||
we don't really use message delivery notifications yet...
|
||||
@ -80,31 +98,28 @@
|
||||
</receiver>
|
||||
-->
|
||||
|
||||
<receiver android:name=".receiver.DequeueOutgoingMessageReceiver">
|
||||
<receiver android:name="org.envaya.sms.receiver.DequeueOutgoingMessageReceiver">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.OutgoingMessageTimeout">
|
||||
<receiver android:name="org.envaya.sms.receiver.OutgoingMessageTimeout">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.OutgoingMessagePoller">
|
||||
<receiver android:name="org.envaya.sms.receiver.OutgoingMessagePoller">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.OutgoingMessageRetry">
|
||||
<receiver android:name="org.envaya.sms.receiver.OutgoingMessageRetry">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.IncomingMessageRetry">
|
||||
<receiver android:name="org.envaya.sms.receiver.IncomingMessageRetry">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.ReenableWifiReceiver">
|
||||
<receiver android:name="org.envaya.sms.receiver.ReenableWifiReceiver">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.BootReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
<receiver android:name="org.envaya.sms.receiver.StartAmqpConsumer">
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.ExpansionPackInstallReceiver">
|
||||
<receiver android:name="org.envaya.sms.receiver.ExpansionPackInstallReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_ADDED" />
|
||||
<action android:name="android.intent.action.PACKAGE_REMOVED" />
|
||||
@ -113,13 +128,13 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.ConnectivityChangeReceiver" >
|
||||
<receiver android:name="org.envaya.sms.receiver.ConnectivityChangeReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receiver.DeviceStatusReceiver">
|
||||
<receiver android:name="org.envaya.sms.receiver.DeviceStatusReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
|
||||
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
|
||||
@ -128,11 +143,19 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".CheckMmsInboxService">
|
||||
<service android:name="org.envaya.sms.service.CheckMessagingService">
|
||||
</service>
|
||||
|
||||
<service android:name=".ForegroundService">
|
||||
<service android:name="org.envaya.sms.service.EnabledChangedService">
|
||||
</service>
|
||||
|
||||
<service android:name="org.envaya.sms.service.ForegroundService">
|
||||
</service>
|
||||
|
||||
<service android:name="org.envaya.sms.service.AmqpConsumerService">
|
||||
</service>
|
||||
|
||||
<service android:name="org.envaya.sms.service.AmqpHeartbeatService">
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
@ -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.
|
||||
|
31
build.xml
31
build.xml
@ -43,23 +43,22 @@
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
/>
|
||||
|
||||
|
||||
<!-- extension targets. Uncomment the ones where you want to do custom work
|
||||
in between standard targets -->
|
||||
<!--
|
||||
<target name="-pre-build">
|
||||
</target>
|
||||
<target name="-pre-compile">
|
||||
</target>
|
||||
|
||||
/* This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir} */
|
||||
<target name="-post-compile">
|
||||
</target>
|
||||
-->
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
# 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
|
15
export.properties
Normal file
15
export.properties
Normal file
@ -0,0 +1,15 @@
|
||||
# Export properties
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
|
||||
# The main content for this file is:
|
||||
# - package name for the application being export
|
||||
# - list of the projects being export
|
||||
# - version code for the application
|
||||
|
||||
# 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 alias to use.
|
||||
# The password will be asked during the build when you use the 'release' target.
|
||||
|
BIN
libs/commons-cli-1.1.jar
Normal file
BIN
libs/commons-cli-1.1.jar
Normal file
Binary file not shown.
BIN
libs/commons-io-1.2.jar
Normal file
BIN
libs/commons-io-1.2.jar
Normal file
Binary file not shown.
BIN
libs/rabbitmq-client.jar
Normal file
BIN
libs/rabbitmq-client.jar
Normal file
Binary file not shown.
20
proguard-project.txt
Normal file
20
proguard-project.txt
Normal file
@ -0,0 +1,20 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
@ -3,18 +3,22 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" android:background="#333333">
|
||||
<ScrollView android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" android:layout_weight="1">
|
||||
<TextView
|
||||
android:linksClickable="true"
|
||||
android:drawablePadding="5px"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/help"
|
||||
android:autoLink="web"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_margin="5px">
|
||||
</TextView>
|
||||
</ScrollView>
|
||||
</TextView>
|
||||
<Button
|
||||
android:id="@+id/reset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:onClick="resetClicked"
|
||||
android:text="Reset All Settings" />
|
||||
</LinearLayout>
|
||||
|
@ -5,13 +5,17 @@
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
<Spinner android:id="@+android:id/inbox_selector"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
</Spinner>
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:text="The inbox is empty."
|
||||
android:text="No messages found."
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
|
@ -3,14 +3,46 @@
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" android:background="#333333">
|
||||
<ScrollView android:id="@+id/info_scroll" android:layout_width="fill_parent"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:onClick="infoClicked"
|
||||
android:padding="10px"
|
||||
android:background="#666666">
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/heading"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:gravity="center"
|
||||
></TextView>
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:gravity="center"
|
||||
android:id="@+id/info"
|
||||
></TextView>
|
||||
</LinearLayout>
|
||||
<ScrollView android:id="@+id/log_scroll" android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" android:layout_weight="1">
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/info"
|
||||
android:id="@+id/log"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textColorLink="#FFFFFF"
|
||||
android:layout_margin="5px"></TextView>
|
||||
</ScrollView>
|
||||
<Button
|
||||
android:id="@+id/upgrade_button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20sp"
|
||||
android:onClick="upgradeClicked"
|
||||
android:text="" />
|
||||
</LinearLayout>
|
||||
|
@ -11,6 +11,13 @@
|
||||
android:text="@string/test_phone_numbers">
|
||||
</TextView>
|
||||
|
||||
<CheckBox android:id="@+id/auto_add_outgoing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="autoAddOutgoingClicked"
|
||||
android:text="Automatically add recipients of outgoing messages"
|
||||
/>
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
|
@ -9,9 +9,9 @@
|
||||
<item android:id="@+id/check_now"
|
||||
android:icon="@drawable/ic_menu_tick"
|
||||
android:title="@string/check_now" />
|
||||
<item android:id="@+id/forward_inbox"
|
||||
<item android:id="@+id/forward_saved"
|
||||
android:icon="@drawable/ic_menu_dialog"
|
||||
android:title="@string/forward_inbox" />
|
||||
android:title="@string/forward_saved" />
|
||||
<item android:id="@+id/retry_now"
|
||||
android:icon="@drawable/ic_menu_magnet"
|
||||
android:title="@string/retry_now" />
|
||||
|
@ -1,18 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EnvayaSMS</string>
|
||||
<string name="running">EnvayaSMS running</string>
|
||||
<string name="disabled">EnvayaSMS disabled</string>
|
||||
<string name="started">EnvayaSMS started.</string>
|
||||
<string name="stopped">EnvayaSMS stopped.</string>
|
||||
<string name="log_view_title">EnvayaSMS : Log View</string>
|
||||
<string name="add_phone_title">EnvayaSMS : Add Phone</string>
|
||||
<string name="help_title">EnvayaSMS : Help</string>
|
||||
<string name='test_phone_numbers_title'>EnvayaSMS : Test Phone Numbers</string>
|
||||
<string name='ignored_phone_numbers_title'>EnvayaSMS : Ignored Phone Numbers</string>
|
||||
<string name='forward_saved_title'>EnvayaSMS : Forward Saved Messages</string>
|
||||
<string name='pending_messages_title'>EnvayaSMS : Pending Messages</string>
|
||||
<string name='settings_title'>EnvayaSMS : Settings</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="test">Test Connection</string>
|
||||
<string name="check_now">Check Messages</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="pending">Pending Msgs...</string>
|
||||
<string name="retry_now">Retry</string>
|
||||
<string name="forward_inbox">Fwd Inbox...</string>
|
||||
<string name='service_started'>New SMS will be forwarded to server</string>
|
||||
<string name='test_phone_numbers'>When running EnvayaSMS in Test Mode,
|
||||
<string name="forward_saved">Fwd Saved...</string>
|
||||
<string name='service_started'>New messages will be forwarded to server</string>
|
||||
<string name='test_phone_numbers'>When running Telerivet in Test Mode,
|
||||
EnvayaSMS will only forward SMS messages from the phone numbers
|
||||
listed below. (Incoming SMS messages from other phone numbers will be saved
|
||||
in the normal Messaging inbox, and outgoing messages will be ignored.)</string>
|
||||
in the normal Messaging inbox.)</string>
|
||||
<string name='ignored_phone_numbers'>
|
||||
EnvayaSMS will ignore SMS messages from the phone numbers listed below.
|
||||
Incoming messages from these senders will be saved in the normal Messaging inbox.</string>
|
||||
|
23
res/xml/expansion_packs.xml
Normal file
23
res/xml/expansion_packs.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="pack01"
|
||||
android:title="SMS Expansion Pack 1"
|
||||
android:summary="...">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="market://details?id=org.envaya.sms.pack01"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
<PreferenceScreen
|
||||
android:key="pack02"
|
||||
android:title="SMS Expansion Pack 2"
|
||||
android:summary="...">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="market://details?id=org.envaya.sms.pack02"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
</PreferenceScreen>
|
@ -6,9 +6,11 @@
|
||||
android:key="enabled"
|
||||
android:title="Enable EnvayaSMS"
|
||||
android:defaultValue='false'
|
||||
android:summaryOn="All new SMS will be forwarded between phone and server"
|
||||
android:summaryOff="New SMS will not be forwarded between phone and server"
|
||||
></CheckBoxPreference>
|
||||
android:summaryOn="New messages will be forwarded between phone and server"
|
||||
android:summaryOff="New messages will not be forwarded between phone and server"
|
||||
></CheckBoxPreference>
|
||||
|
||||
<PreferenceCategory android:title="Server Settings">
|
||||
|
||||
<EditTextPreference
|
||||
android:key="server_url"
|
||||
@ -37,36 +39,42 @@
|
||||
android:entries="@array/check_intervals"
|
||||
android:entryValues="@array/check_intervals_values"
|
||||
></ListPreference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Messaging Settings">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="keep_in_inbox"
|
||||
android:title="Keep new messages"
|
||||
android:summaryOff="Incoming SMS will not be stored in Messaging inbox"
|
||||
android:summaryOn="Incoming SMS will be stored in Messaging inbox"
|
||||
android:summaryOff="Incoming messages will not be stored in Messaging inbox"
|
||||
android:summaryOn="Incoming messages will be stored in Messaging inbox"
|
||||
></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
|
||||
android:key="wifi_sleep_policy"
|
||||
android:title="Wi-Fi sleep policy"
|
||||
android:defaultValue="never"
|
||||
android:entries="@array/wifi_sleep_policies"
|
||||
android:entryValues="@array/wifi_sleep_policies_values"
|
||||
>
|
||||
</ListPreference>
|
||||
|
||||
android:summaryOff="Telerivet will not notify server when phone receives an incoming call"
|
||||
android:summaryOn="Telerivet will notify server when phone receives an incoming call"
|
||||
></CheckBoxPreference>
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="send_limit"
|
||||
android:title="SMS rate limit"
|
||||
android:summary="..."
|
||||
>
|
||||
<intent
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetPackage="org.envaya.sms"
|
||||
android:targetClass="org.envaya.sms.ui.ExpansionPacks" />
|
||||
</PreferenceScreen>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="network_failover"
|
||||
android:title="Network failover"
|
||||
android:summaryOff="Do nothing if phone can't connect to server via Wi-Fi"
|
||||
android:summaryOn="Automatically switch to mobile data if phone can't connect to server via Wi-Fi"
|
||||
></CheckBoxPreference>
|
||||
android:key="forward_sent"
|
||||
android:title="Forward sent messages"
|
||||
android:summaryOff="SMS sent from Messaging app will not be forwarded to server"
|
||||
android:summaryOn="SMS sent from Messaging app will be forwarded to server"
|
||||
></CheckBoxPreference>
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="ignored_numbers"
|
||||
@ -77,7 +85,7 @@
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetPackage="org.envaya.sms"
|
||||
android:targetClass="org.envaya.sms.ui.IgnoredPhoneNumbers" />
|
||||
</PreferenceScreen>
|
||||
</PreferenceScreen>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="test_mode"
|
||||
@ -96,16 +104,114 @@
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetPackage="org.envaya.sms"
|
||||
android:targetClass="org.envaya.sms.ui.TestPhoneNumbers" />
|
||||
</PreferenceScreen>
|
||||
</PreferenceScreen>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="help"
|
||||
android:title="About EnvayaSMS"
|
||||
>
|
||||
<PreferenceCategory android:title="Networking Settings">
|
||||
|
||||
<ListPreference
|
||||
android:key="wifi_sleep_policy"
|
||||
android:title="Wi-Fi sleep policy"
|
||||
android:defaultValue="never"
|
||||
android:entries="@array/wifi_sleep_policies"
|
||||
android:entryValues="@array/wifi_sleep_policies_values"
|
||||
>
|
||||
</ListPreference>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="network_failover"
|
||||
android:title="Network failover"
|
||||
android:summaryOff="Do nothing if phone can't connect to server via Wi-Fi"
|
||||
android:summaryOn="Automatically switch to mobile data if phone can't connect to server via Wi-Fi"
|
||||
></CheckBoxPreference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="AMQP Settings (Real-Time Connection)">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="amqp_enabled"
|
||||
android:title="Enable AMQP"
|
||||
android:summaryOff="AMQP is disabled"
|
||||
android:summaryOn="AMQP is enabled"
|
||||
></CheckBoxPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_host"
|
||||
android:title="AMQP Host"
|
||||
android:inputType="textUri"
|
||||
android:defaultValue=""
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_port"
|
||||
android:title="AMQP Port"
|
||||
android:inputType="number"
|
||||
android:defaultValue="5672"
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_vhost"
|
||||
android:title="AMQP Virtual Host"
|
||||
android:inputType="text"
|
||||
android:defaultValue="/"
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="amqp_ssl"
|
||||
android:title="AMQP SSL"
|
||||
android:summaryOff="Off (Plain text)"
|
||||
android:summaryOn="On (Encrypted)"
|
||||
android:dependency="amqp_enabled"
|
||||
></CheckBoxPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_user"
|
||||
android:title="AMQP User"
|
||||
android:defaultValue=""
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_password"
|
||||
android:title="AMQP Password"
|
||||
android:defaultValue=""
|
||||
android:password="true"
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_queue"
|
||||
android:title="AMQP Queue Name"
|
||||
android:defaultValue=""
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
<EditTextPreference
|
||||
android:key="amqp_heartbeat"
|
||||
android:title="AMQP Heartbeat (sec)"
|
||||
android:inputType="number"
|
||||
android:defaultValue="60"
|
||||
android:dependency="amqp_enabled"
|
||||
></EditTextPreference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Help">
|
||||
<PreferenceScreen
|
||||
android:key="help"
|
||||
android:title="About EnvayaSMS"
|
||||
>
|
||||
<intent
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetPackage="org.envaya.sms"
|
||||
android:targetClass="org.envaya.sms.ui.Help" />
|
||||
</PreferenceScreen>
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
@ -1,24 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* PHP server library for EnvayaSMS
|
||||
* PHP server library for EnvayaSMS 3.0
|
||||
*
|
||||
* For example usage see example/www/index.php
|
||||
* For example usage see example/www/gateway.php
|
||||
*/
|
||||
|
||||
class EnvayaSMS
|
||||
class EnvayaSMS
|
||||
{
|
||||
const ACTION_INCOMING = 'incoming';
|
||||
const ACTION_OUTGOING = 'outgoing';
|
||||
const ACTION_FORWARD_SENT = 'forward_sent';
|
||||
const ACTION_SEND_STATUS = 'send_status';
|
||||
const ACTION_DEVICE_STATUS = 'device_status';
|
||||
const ACTION_TEST = 'test';
|
||||
const ACTION_OUTGOING = 'outgoing';
|
||||
const ACTION_AMQP_STARTED = 'amqp_started';
|
||||
// ACTION_OUTGOING should probably be const ACTION_POLL = 'poll',
|
||||
// but 'outgoing' maintains backwards compatibility between new phone versions with old servers
|
||||
|
||||
const STATUS_QUEUED = 'queued';
|
||||
const STATUS_FAILED = 'failed';
|
||||
const STATUS_SENT = 'sent';
|
||||
const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
const EVENT_SEND = 'send';
|
||||
const EVENT_CANCEL = 'cancel';
|
||||
const EVENT_CANCEL_ALL = 'cancel_all';
|
||||
const EVENT_LOG = 'log';
|
||||
const EVENT_SETTINGS = 'settings';
|
||||
|
||||
const DEVICE_STATUS_POWER_CONNECTED = "power_connected";
|
||||
const DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
|
||||
const DEVICE_STATUS_BATTERY_LOW = "battery_low";
|
||||
@ -29,8 +38,10 @@ class EnvayaSMS
|
||||
const MESSAGE_TYPE_MMS = 'mms';
|
||||
const MESSAGE_TYPE_CALL = 'call';
|
||||
|
||||
const NETWORK_MOBILE = "MOBILE";
|
||||
const NETWORK_WIFI = "WIFI";
|
||||
// power source constants same as from Android's BatteryManager.EXTRA_PLUGGED
|
||||
const POWER_SOURCE_BATTERY = 0;
|
||||
const POWER_SOURCE_AC = 1;
|
||||
const POWER_SOURCE_USB = 2;
|
||||
|
||||
static function escape($val)
|
||||
{
|
||||
@ -41,60 +52,36 @@ class EnvayaSMS
|
||||
|
||||
static function get_request()
|
||||
{
|
||||
if (!isset(static::$request))
|
||||
if (!isset(self::$request))
|
||||
{
|
||||
$version = @$_POST['version'];
|
||||
|
||||
// If API version changes, could return
|
||||
// different EnvayaSMS_Request instance
|
||||
// to support multiple phone versions
|
||||
|
||||
static::$request = new EnvayaSMS_Request();
|
||||
if (isset($_POST['action']))
|
||||
{
|
||||
self::$request = new EnvayaSMS_ActionRequest();
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$request = new EnvayaSMS_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();
|
||||
}
|
||||
return self::$request;
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Request
|
||||
{
|
||||
private $request_action;
|
||||
|
||||
{
|
||||
public $version;
|
||||
public $phone_number;
|
||||
public $log;
|
||||
|
||||
public $version_name;
|
||||
public $sdk_int;
|
||||
public $manufacturer;
|
||||
public $model;
|
||||
public $network;
|
||||
|
||||
public $model;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->version = $_POST['version'];
|
||||
$this->phone_number = $_POST['phone_number'];
|
||||
$this->log = $_POST['log'];
|
||||
$this->network = @$_POST['network'];
|
||||
$this->version = (int)@$_POST['version'];
|
||||
|
||||
if (preg_match('#/(?P<version_name>[\w\.\-]+) \(Android; SDK (?P<sdk_int>\d+); (?P<manufacturer>[^;]*); (?P<model>[^\)]*)\)#',
|
||||
@$_SERVER['HTTP_USER_AGENT'], $matches))
|
||||
@ -103,9 +90,115 @@ class EnvayaSMS_Request
|
||||
$this->sdk_int = $matches['sdk_int'];
|
||||
$this->manufacturer = $matches['manufacturer'];
|
||||
$this->model = $matches['model'];
|
||||
}
|
||||
}
|
||||
|
||||
function supports_json()
|
||||
{
|
||||
return $this->version >= 28;
|
||||
}
|
||||
|
||||
function supports_update_settings()
|
||||
{
|
||||
return $this->version >= 29;
|
||||
}
|
||||
|
||||
function get_response_type()
|
||||
{
|
||||
if ($this->supports_json())
|
||||
{
|
||||
return 'application/json';
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'text/xml';
|
||||
}
|
||||
}
|
||||
|
||||
function render_response($events = null /* optional array of EnvayaSMS_Event objects */)
|
||||
{
|
||||
if ($this->supports_json())
|
||||
{
|
||||
return json_encode(array('events' => $events));
|
||||
}
|
||||
else
|
||||
{
|
||||
ob_start();
|
||||
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
|
||||
echo "<response>";
|
||||
|
||||
if ($events)
|
||||
{
|
||||
foreach ($events as $event)
|
||||
{
|
||||
echo "<messages>";
|
||||
if ($event instanceof EnvayaSMS_Event_Send)
|
||||
{
|
||||
if ($event->messages)
|
||||
{
|
||||
foreach ($event->messages as $message)
|
||||
{
|
||||
$type = isset($message->type) ? $message->type : EnvayaSMS::MESSAGE_TYPE_SMS;
|
||||
$id = isset($message->id) ? " id=\"".EnvayaSMS::escape($message->id)."\"" : "";
|
||||
$to = isset($message->to) ? " to=\"".EnvayaSMS::escape($message->to)."\"" : "";
|
||||
$priority = isset($message->priority) ? " priority=\"".$message->priority."\"" : "";
|
||||
echo "<$type$id$to$priority>".EnvayaSMS::escape($message->message)."</$type>";
|
||||
}
|
||||
}
|
||||
}
|
||||
echo "</messages>";
|
||||
}
|
||||
}
|
||||
echo "</response>";
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
function render_error_response($message)
|
||||
{
|
||||
if ($this->supports_json())
|
||||
{
|
||||
return json_encode(array('error' => array('message' => $message)));
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_ActionRequest extends EnvayaSMS_Request
|
||||
{
|
||||
private $request_action;
|
||||
|
||||
public $settings_version; // integer version of current settings (as provided by server)
|
||||
public $phone_number; // phone number of Android phone
|
||||
public $log; // app log messages since last successful request
|
||||
public $now; // current time (ms since Unix epoch) according to Android clock
|
||||
public $network; // name of network, like WIFI or MOBILE (may vary depending on phone)
|
||||
public $battery; // battery level as percentage
|
||||
public $power; // power source integer, see EnvayaSMS::POWER_SOURCE_*
|
||||
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->phone_number = $_POST['phone_number'];
|
||||
$this->log = $_POST['log'];
|
||||
$this->network = @$_POST['network'];
|
||||
$this->now = @$_POST['now'];
|
||||
$this->settings_version = @$_POST['settings_version'];
|
||||
$this->battery = @$_POST['battery'];
|
||||
$this->power = @$_POST['power'];
|
||||
}
|
||||
|
||||
function get_action()
|
||||
{
|
||||
if (!$this->request_action)
|
||||
@ -121,7 +214,9 @@ class EnvayaSMS_Request
|
||||
{
|
||||
case EnvayaSMS::ACTION_INCOMING:
|
||||
return new EnvayaSMS_Action_Incoming($this);
|
||||
case EnvayaSMS::ACTION_OUTGOING:
|
||||
case EnvayaSMS::ACTION_FORWARD_SENT:
|
||||
return new EnvayaSMS_Action_ForwardSent($this);
|
||||
case EnvayaSMS::ACTION_OUTGOING:
|
||||
return new EnvayaSMS_Action_Outgoing($this);
|
||||
case EnvayaSMS::ACTION_SEND_STATUS:
|
||||
return new EnvayaSMS_Action_SendStatus($this);
|
||||
@ -129,6 +224,8 @@ class EnvayaSMS_Request
|
||||
return new EnvayaSMS_Action_Test($this);
|
||||
case EnvayaSMS::ACTION_DEVICE_STATUS:
|
||||
return new EnvayaSMS_Action_DeviceStatus($this);
|
||||
case EnvayaSMS::ACTION_AMQP_STARTED:
|
||||
return new EnvayaSMS_Action_AmqpStarted($this);
|
||||
default:
|
||||
return new EnvayaSMS_Action($this);
|
||||
}
|
||||
@ -163,29 +260,8 @@ class EnvayaSMS_Request
|
||||
|
||||
$input .= ",$password";
|
||||
|
||||
//error_log("Signed data: '$input'");
|
||||
|
||||
return base64_encode(sha1($input, true));
|
||||
}
|
||||
|
||||
static function get_messages_xml($messages)
|
||||
{
|
||||
ob_start();
|
||||
echo "<?xml version='1.0' encoding='UTF-8'?>\n";
|
||||
echo "<response>";
|
||||
echo "<messages>";
|
||||
foreach ($messages as $message)
|
||||
{
|
||||
$type = isset($message->type) ? $message->type : EnvayaSMS::MESSAGE_TYPE_SMS;
|
||||
$id = isset($message->id) ? " id=\"".EnvayaSMS::escape($message->id)."\"" : "";
|
||||
$to = isset($message->to) ? " to=\"".EnvayaSMS::escape($message->to)."\"" : "";
|
||||
$priority = isset($message->priority) ? " priority=\"".$message->priority."\"" : "";
|
||||
echo "<$type$id$to$priority>".EnvayaSMS::escape($message->message)."</$type>";
|
||||
}
|
||||
echo "</messages>";
|
||||
echo "</response>";
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_OutgoingMessage
|
||||
@ -197,6 +273,10 @@ class EnvayaSMS_OutgoingMessage
|
||||
public $type; // EnvayaSMS::MESSAGE_TYPE_* value (default sms)
|
||||
}
|
||||
|
||||
/*
|
||||
* An 'action' is the term for a HTTP request that app sends to the server.
|
||||
*/
|
||||
|
||||
class EnvayaSMS_Action
|
||||
{
|
||||
public $type;
|
||||
@ -233,24 +313,19 @@ class EnvayaSMS_MMS_Part
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Action_Incoming extends EnvayaSMS_Action
|
||||
abstract class EnvayaSMS_Action_Forward extends EnvayaSMS_Action
|
||||
{
|
||||
public $from; // Sender phone number
|
||||
public $message; // The message body of the SMS, or the content of the text/plain part of the MMS.
|
||||
public $message_type; // EnvayaSMS::MESSAGE_TYPE_MMS or EnvayaSMS::MESSAGE_TYPE_SMS
|
||||
public $mms_parts; // array of EnvayaSMS_MMS_Part instances
|
||||
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)
|
||||
{
|
||||
parent::__construct($request);
|
||||
$this->type = EnvayaSMS::ACTION_INCOMING;
|
||||
$this->from = $_POST['from'];
|
||||
$this->message = @$_POST['message'];
|
||||
$this->message_type = $_POST['message_type'];
|
||||
$this->timestamp = @$_POST['timestamp'];
|
||||
$this->age = @$_POST['age'];
|
||||
|
||||
if ($this->message_type == EnvayaSMS::MESSAGE_TYPE_MMS)
|
||||
{
|
||||
@ -260,26 +335,52 @@ class EnvayaSMS_Action_Incoming extends EnvayaSMS_Action
|
||||
$this->mms_parts[] = new EnvayaSMS_MMS_Part($mms_part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_response_xml($messages)
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Action_Incoming extends EnvayaSMS_Action_Forward
|
||||
{
|
||||
public $from; // Sender phone number
|
||||
|
||||
function __construct($request)
|
||||
{
|
||||
return $this->request->get_messages_xml($messages);
|
||||
parent::__construct($request);
|
||||
$this->type = EnvayaSMS::ACTION_INCOMING;
|
||||
$this->from = $_POST['from'];
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Action_ForwardSent extends EnvayaSMS_Action_Forward
|
||||
{
|
||||
public $to; // Recipient phone number
|
||||
|
||||
function __construct($request)
|
||||
{
|
||||
parent::__construct($request);
|
||||
$this->type = EnvayaSMS::ACTION_FORWARD_SENT;
|
||||
$this->to = $_POST['to'];
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Action_AmqpStarted extends EnvayaSMS_Action
|
||||
{
|
||||
public $consumer_tag;
|
||||
|
||||
function __construct($request)
|
||||
{
|
||||
parent::__construct($request);
|
||||
$this->type = EnvayaSMS::ACTION_AMQP_STARTED;
|
||||
$this->consumer_tag = $_POST['consumer_tag'];
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Action_Outgoing extends EnvayaSMS_Action
|
||||
{
|
||||
function __construct($request)
|
||||
{
|
||||
parent::__construct($request);
|
||||
$this->type = EnvayaSMS::ACTION_OUTGOING;
|
||||
}
|
||||
|
||||
function get_response_xml($messages)
|
||||
{
|
||||
return $this->request->get_messages_xml($messages);
|
||||
}
|
||||
$this->type = EnvayaSMS::ACTION_OUTGOING;
|
||||
}
|
||||
}
|
||||
|
||||
class EnvayaSMS_Action_Test extends EnvayaSMS_Action
|
||||
@ -318,3 +419,89 @@ class EnvayaSMS_Action_DeviceStatus extends EnvayaSMS_Action
|
||||
$this->status = $_POST['status'];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An 'event' is the term for something the server sends to the app,
|
||||
* either via a response to an 'action', or directly via AMQP.
|
||||
*/
|
||||
|
||||
class EnvayaSMS_Event
|
||||
{
|
||||
public $event;
|
||||
|
||||
/*
|
||||
* Formats this event as the body of an AMQP message.
|
||||
*/
|
||||
function render()
|
||||
{
|
||||
return json_encode($this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Instruct the phone to send one or more outgoing messages (SMS or USSD)
|
||||
*/
|
||||
class EnvayaSMS_Event_Send extends EnvayaSMS_Event
|
||||
{
|
||||
public $messages;
|
||||
|
||||
function __construct($messages /* array of EnvayaSMS_OutgoingMessage objects */)
|
||||
{
|
||||
$this->event = EnvayaSMS::EVENT_SEND;
|
||||
$this->messages = $messages;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update some of the app's settings.
|
||||
*/
|
||||
class EnvayaSMS_Event_Settings extends EnvayaSMS_Event
|
||||
{
|
||||
public $settings;
|
||||
|
||||
function __construct($settings /* associative array of key => value pairs (values can be int, bool, or string) */)
|
||||
{
|
||||
$this->event = EnvayaSMS::EVENT_SETTINGS;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cancel sending a message that was previously queued in the app via a 'send' event.
|
||||
* Has no effect if the message has already been sent.
|
||||
*/
|
||||
class EnvayaSMS_Event_Cancel extends EnvayaSMS_Event
|
||||
{
|
||||
public $id;
|
||||
|
||||
function __construct($id /* id of previously created EnvayaSMS_OutgoingMessage object (string) */)
|
||||
{
|
||||
$this->event = EnvayaSMS::EVENT_CANCEL;
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Cancels all outgoing messages that are currently queued in the app. Incoming mesages are not affected.
|
||||
*/
|
||||
class EnvayaSMS_Event_CancelAll extends EnvayaSMS_Event
|
||||
{
|
||||
function __construct()
|
||||
{
|
||||
$this->event = EnvayaSMS::EVENT_CANCEL_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Appends a message to the app log.
|
||||
*/
|
||||
class EnvayaSMS_Event_Log extends EnvayaSMS_Event
|
||||
{
|
||||
public $message;
|
||||
|
||||
function __construct($message)
|
||||
{
|
||||
$this->event = EnvayaSMS::EVENT_LOG;
|
||||
$this->message = $message;
|
||||
}
|
||||
}
|
||||
|
@ -10,19 +10,21 @@ PHP web server, by running the following commands:
|
||||
git submodule init
|
||||
php server.php
|
||||
|
||||
example/config.php contains the list of phone numbers and passwords for phones running EnvayaSMS.
|
||||
example/config.php contains the password for a phone running EnvayaSMS. The password
|
||||
This password must match the password in the EnvayaSMS app settings,
|
||||
otherwise example/gateway.php will return an "Invalid password" error.
|
||||
|
||||
On a phone running EnvayaSMS, go to Menu -> Settings and enter:
|
||||
* Server URL: The URL to example/www/index.php.
|
||||
If you're using server.php, this will be http://<your_ip_address>:8002/
|
||||
* Your phone number: One of the phone numbers listed in example/config.php
|
||||
* Password: The corresponding password in example/config.php
|
||||
* Server URL: The URL to example/www/gateway.php.
|
||||
If you're using server.php, this will be http://<your_ip_address>:8002/gateway.php
|
||||
* Your phone number: The phone number of your Android phone
|
||||
* Password: The password in example/config.php
|
||||
|
||||
To send an outgoing SMS, use
|
||||
php example/send_sms.php
|
||||
|
||||
php example/send_sms.php
|
||||
|
||||
example/www/test.html allows you to simulate the HTTP requests made by EnvayaSMS
|
||||
in your browser without actually using the EnvayaSMS app.
|
||||
If you're using server.php, just go to http://<your_ip_address>:8002/test.html
|
||||
|
||||
See EnvayaSMS.php and example/www/index.php
|
||||
See EnvayaSMS.php and example/www/gateway.php
|
||||
|
@ -1,9 +1,33 @@
|
||||
<?php
|
||||
|
||||
$PASSWORDS = array(
|
||||
'16505551212' => 'rosebud',
|
||||
'16505551213' => 's3krit',
|
||||
);
|
||||
$PHONE_NUMBERS = array_keys($PASSWORDS);
|
||||
ini_set('display_errors','0');
|
||||
|
||||
$OUTGOING_DIR_NAME = __DIR__."/outgoing_sms";
|
||||
/*
|
||||
* This password must match the password in the EnvayaSMS app settings,
|
||||
* otherwise example/www/gateway.php will return an "Invalid request signature" error.
|
||||
*/
|
||||
|
||||
$PASSWORD = 'rosebud';
|
||||
|
||||
/*
|
||||
* example/send_sms.php uses the local file system to queue outgoing messages
|
||||
* in this directory.
|
||||
*/
|
||||
|
||||
$OUTGOING_DIR_NAME = __DIR__."/outgoing_sms";
|
||||
|
||||
/*
|
||||
* AMQP allows you to send outgoing messages in real-time (i.e. 'push' instead of polling).
|
||||
* In order to use AMQP, you would need to install an AMQP server such as RabbitMQ, and
|
||||
* also enter the AMQP connection settings in the app. (The settings in the EnvayaSMS app
|
||||
* should use the same vhost and queue name, but may use a different host/port/user/password.)
|
||||
*/
|
||||
|
||||
$AMQP_SETTINGS = array(
|
||||
'host' => 'localhost',
|
||||
'port' => 5672,
|
||||
'user' => 'guest',
|
||||
'password' => 'guest',
|
||||
'vhost' => '/',
|
||||
'queue_name' => "envayasms"
|
||||
);
|
||||
|
1
server/php/example/php-amqplib
Submodule
1
server/php/example/php-amqplib
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 056e94d28d14466c19a4eccb15f2d5e8e7ba017d
|
@ -1,46 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Command line script to simulate sending an outgoing SMS from the server.
|
||||
* Command line script to send an outgoing SMS from the server.
|
||||
*
|
||||
* The message will be queued on the server until the next time
|
||||
* EnvayaSMS checks for outgoing messages.
|
||||
* This example script queues outgoing messages using the local filesystem.
|
||||
* The messages are sent the next time EnvayaSMS sends an ACTION_OUTGOING request to www/gateway.php.
|
||||
*/
|
||||
|
||||
require_once __DIR__."/config.php";
|
||||
require_once dirname(__DIR__)."/EnvayaSMS.php";
|
||||
|
||||
$arg_len = sizeof($argv);
|
||||
|
||||
if ($arg_len == 4)
|
||||
{
|
||||
$from = $argv[1];
|
||||
$to = $argv[2];
|
||||
$message = $argv[3];
|
||||
}
|
||||
else if ($arg_len == 3)
|
||||
if (sizeof($argv) == 3)
|
||||
{
|
||||
$from = $PHONE_NUMBERS[0];
|
||||
$to = $argv[1];
|
||||
$message = $argv[2];
|
||||
$body = $argv[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log("Usage: php send_sms.php [<from>] <to> \"<message>\"");
|
||||
error_log("Examples: ");
|
||||
error_log(" php send_sms.php 16505551212 16504449876 \"hello world\"");
|
||||
error_log("Usage: php send_sms.php <to> \"<message>\"");
|
||||
error_log("Example: ");
|
||||
error_log(" php send_sms.php 16504449876 \"hello world\"");
|
||||
die;
|
||||
}
|
||||
|
||||
$id = uniqid("");
|
||||
$message = new EnvayaSMS_OutgoingMessage();
|
||||
$message->id = uniqid("");
|
||||
$message->to = $to;
|
||||
$message->message = $body;
|
||||
|
||||
$filename = "$OUTGOING_DIR_NAME/$id.json";
|
||||
|
||||
file_put_contents($filename, json_encode(array(
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'message' => $message,
|
||||
'id' => $id
|
||||
)));
|
||||
|
||||
echo "Message $id added to outgoing queue\n";
|
||||
file_put_contents("$OUTGOING_DIR_NAME/{$message->id}.json", json_encode($message));
|
||||
|
||||
echo "Message {$message->id} added to filesystem queue\n";
|
||||
|
45
server/php/example/send_sms_amqp.php
Normal file
45
server/php/example/send_sms_amqp.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Command line script to send an outgoing SMS from the server.
|
||||
*
|
||||
* Requires an AMQP server to be configured in config.php, and
|
||||
* pushes SMS to the phone immediately using the real-time connection.
|
||||
*/
|
||||
|
||||
require_once __DIR__."/config.php";
|
||||
require_once dirname(__DIR__)."/EnvayaSMS.php";
|
||||
require_once __DIR__."/php-amqplib/amqp.inc";
|
||||
|
||||
if (sizeof($argv) == 3)
|
||||
{
|
||||
$to = $argv[1];
|
||||
$body = $argv[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log("Usage: php send_sms_amqp.php <to> \"<message>\"");
|
||||
die;
|
||||
}
|
||||
|
||||
$message = new EnvayaSMS_OutgoingMessage();
|
||||
$message->id = uniqid("");
|
||||
$message->to = $to;
|
||||
$message->message = $body;
|
||||
|
||||
$conn = new AMQPConnection($AMQP_SETTINGS['host'], $AMQP_SETTINGS['port'],
|
||||
$AMQP_SETTINGS['user'], $AMQP_SETTINGS['password'], $AMQP_SETTINGS['vhost']);
|
||||
|
||||
$ch = $conn->channel();
|
||||
$ch->queue_declare($AMQP_SETTINGS['queue_name'], false, true, false, false);
|
||||
|
||||
$event = new EnvayaSMS_Event_Send(array($message));
|
||||
|
||||
$msg = new AMQPMessage($event->render(), array('content_type' => 'application/json', 'delivery-mode' => 2));
|
||||
|
||||
$ch->basic_publish($msg, '', $AMQP_SETTINGS['queue_name']);
|
||||
|
||||
$ch->close();
|
||||
$conn->close();
|
||||
|
||||
echo "Message {$message->id} added to AMQP queue\n";
|
87
server/php/example/www/index.php → server/php/example/www/gateway.php
Executable file → Normal file
87
server/php/example/www/index.php → server/php/example/www/gateway.php
Executable file → Normal file
@ -1,44 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This example script implements the EnvayaSMS API.
|
||||
*
|
||||
* It sends an auto-reply to each incoming message, and sends outgoing SMS
|
||||
* that were previously queued by example/send_sms.php .
|
||||
*
|
||||
* To use this file, set the URL to this file as as the the Server URL in the EnvayaSMS app.
|
||||
* The password in the EnvayaSMS app settings must be the same as $PASSWORD in config.php.
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__)."/config.php";
|
||||
require_once dirname(dirname(__DIR__))."/EnvayaSMS.php";
|
||||
|
||||
ini_set('display_errors','0');
|
||||
|
||||
// this example implementation uses the filesystem to store outgoing SMS messages,
|
||||
// but presumably a production implementation would use another storage method
|
||||
|
||||
$request = EnvayaSMS::get_request();
|
||||
|
||||
$phone_number = $request->phone_number;
|
||||
header("Content-Type: {$request->get_response_type()}");
|
||||
|
||||
$password = @$PASSWORDS[$phone_number];
|
||||
|
||||
header("Content-Type: text/xml");
|
||||
|
||||
if (!isset($password) || !$request->is_validated($password))
|
||||
if (!$request->is_validated($PASSWORD))
|
||||
{
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
error_log("Invalid request signature");
|
||||
echo EnvayaSMS::get_error_xml("Invalid request signature");
|
||||
error_log("Invalid password");
|
||||
echo $request->render_error_response("Invalid password");
|
||||
return;
|
||||
}
|
||||
|
||||
// append to EnvayaSMS app log
|
||||
$app_log = $request->log;
|
||||
if ($app_log)
|
||||
{
|
||||
$log_file = dirname(__DIR__)."/log/sms_".preg_replace('#[^\w]#', '', $request->phone_number).".log";
|
||||
$f = fopen($log_file, "a");
|
||||
fwrite($f, $app_log);
|
||||
fclose($f);
|
||||
}
|
||||
|
||||
$action = $request->get_action();
|
||||
|
||||
switch ($action->type)
|
||||
{
|
||||
case EnvayaSMS::ACTION_INCOMING:
|
||||
|
||||
// Send an auto-reply for each incoming message.
|
||||
|
||||
$type = strtoupper($action->message_type);
|
||||
|
||||
error_log("Received $type from {$action->from}");
|
||||
@ -62,25 +56,29 @@ switch ($action->type)
|
||||
$reply->message = "You said: {$action->message}";
|
||||
|
||||
error_log("Sending reply: {$reply->message}");
|
||||
|
||||
echo $action->get_response_xml(array($reply));
|
||||
|
||||
echo $request->render_response(array(
|
||||
new EnvayaSMS_Event_Send(array($reply))
|
||||
));
|
||||
return;
|
||||
|
||||
case EnvayaSMS::ACTION_OUTGOING:
|
||||
$messages = array();
|
||||
|
||||
// In this example implementation, outgoing SMS messages are queued
|
||||
// on the local file system by send_sms.php.
|
||||
|
||||
$dir = opendir($OUTGOING_DIR_NAME);
|
||||
while ($file = readdir($dir))
|
||||
{
|
||||
if (preg_match('#\.json$#', $file))
|
||||
{
|
||||
$data = json_decode(file_get_contents("$OUTGOING_DIR_NAME/$file"), true);
|
||||
if ($data && @$data['from'] == $phone_number)
|
||||
if ($data)
|
||||
{
|
||||
$sms = new EnvayaSMS_OutgoingMessage();
|
||||
$sms->id = $data['id'];
|
||||
$sms->to = $data['to'];
|
||||
$sms->from = $data['from'];
|
||||
$sms->message = $data['message'];
|
||||
$messages[] = $sms;
|
||||
}
|
||||
@ -88,33 +86,40 @@ switch ($action->type)
|
||||
}
|
||||
closedir($dir);
|
||||
|
||||
echo $action->get_response_xml($messages);
|
||||
$events = array();
|
||||
|
||||
if ($messages)
|
||||
{
|
||||
$events[] = new EnvayaSMS_Event_Send($messages);
|
||||
}
|
||||
|
||||
echo $request->render_response($events);
|
||||
|
||||
return;
|
||||
|
||||
case EnvayaSMS::ACTION_SEND_STATUS:
|
||||
|
||||
$id = $action->id;
|
||||
|
||||
error_log("message $id status: {$action->status}");
|
||||
|
||||
// delete file with matching id
|
||||
if (preg_match('#^\w+$#', $id) && unlink("$OUTGOING_DIR_NAME/$id.json"))
|
||||
if (preg_match('#^\w+$#', $id))
|
||||
{
|
||||
echo EnvayaSMS::get_success_xml();
|
||||
}
|
||||
else
|
||||
{
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
echo EnvayaSMS::get_error_xml("Invalid id");
|
||||
}
|
||||
unlink("$OUTGOING_DIR_NAME/$id.json");
|
||||
}
|
||||
echo $request->render_response();
|
||||
|
||||
return;
|
||||
case EnvayaSMS::ACTION_DEVICE_STATUS:
|
||||
error_log("device_status = {$action->status}");
|
||||
echo EnvayaSMS::get_success_xml();
|
||||
return;
|
||||
echo $request->render_response();
|
||||
return;
|
||||
case EnvayaSMS::ACTION_TEST:
|
||||
echo EnvayaSMS::get_success_xml();
|
||||
return;
|
||||
echo $request->render_response();
|
||||
return;
|
||||
default:
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
echo EnvayaSMS::get_error_xml("Invalid action");
|
||||
echo $request->render_error_response("The server does not support the requested action.");
|
||||
return;
|
||||
}
|
87
server/php/example/www/gateway_amqp.php
Normal file
87
server/php/example/www/gateway_amqp.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* An example implementation of the EnvayaSMS server API that uses AMQP
|
||||
* to send outgoing messages in real-time, intended to be used together with
|
||||
* example/send_sms_amqp.php.
|
||||
*
|
||||
* To use this file, set the URL to this file as as the the Server URL in the EnvayaSMS app.
|
||||
* The password in the EnvayaSMS app settings must be the same as $PASSWORD in config.php.
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__)."/config.php";
|
||||
require_once dirname(dirname(__DIR__))."/EnvayaSMS.php";
|
||||
|
||||
$request = EnvayaSMS::get_request();
|
||||
|
||||
header("Content-Type: {$request->get_response_type()}");
|
||||
|
||||
if (!$request->is_validated($PASSWORD))
|
||||
{
|
||||
header("HTTP/1.1 403 Forbidden");
|
||||
error_log("Invalid password");
|
||||
echo $request->render_error_response("Invalid password");
|
||||
return;
|
||||
}
|
||||
|
||||
$action = $request->get_action();
|
||||
|
||||
switch ($action->type)
|
||||
{
|
||||
case EnvayaSMS::ACTION_INCOMING:
|
||||
|
||||
// Doesn't do anything with incoming messages
|
||||
|
||||
error_log("Received {$action->message_type} from {$action->from}: {$action->message}");
|
||||
echo $request->render_response();
|
||||
return;
|
||||
|
||||
case EnvayaSMS::ACTION_OUTGOING:
|
||||
|
||||
// Doesn't need to do anything when polling for outgoing messages
|
||||
// since they should be sent via the AMQP connection.
|
||||
|
||||
// Optionally, you could use AMQP basic_get to retrieve any messages
|
||||
// from the AMQP queue so that it works in both polling and push modes.
|
||||
|
||||
error_log("No messages here, use AMQP instead");
|
||||
echo $request->render_response(array(
|
||||
new EnvayaSMS_Event_Log("No messages via polling, use AMQP instead")
|
||||
));
|
||||
return;
|
||||
|
||||
case EnvayaSMS::ACTION_AMQP_STARTED:
|
||||
|
||||
// The main point of this action is to allow the server to kick off old
|
||||
// AMQP connections (that weren't closed properly) before their heartbeat timeout
|
||||
// expires. This makes it possible to use long heartbeat timeouts to maximize
|
||||
// the phone's battery life.
|
||||
|
||||
// With RabbitMQ, this can be done using the management API:
|
||||
|
||||
// GET /queues/VHOST/QUEUE_NAME
|
||||
// to get the connection name for each consumer other than the current one
|
||||
|
||||
// DELETE /connections/CONNECTION_NAME
|
||||
// to close the connection for each consumer other than the current one
|
||||
|
||||
error_log("AMQP connection started with consumer tag {$action->consumer_tag}");
|
||||
echo $request->render_response();
|
||||
return;
|
||||
|
||||
case EnvayaSMS::ACTION_SEND_STATUS:
|
||||
error_log("message {$action->id} status: {$action->status}");
|
||||
echo $request->render_response();
|
||||
return;
|
||||
case EnvayaSMS::ACTION_DEVICE_STATUS:
|
||||
error_log("device_status = {$action->status}");
|
||||
echo $request->render_response();
|
||||
return;
|
||||
case EnvayaSMS::ACTION_TEST:
|
||||
echo $request->render_response();
|
||||
return;
|
||||
default:
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
echo $request->render_error_response("The server does not support the requested action.");
|
||||
return;
|
||||
}
|
@ -31,13 +31,26 @@ body
|
||||
<tr><th>Phone Number</th><td><input id='phone_number' type='text' /></td></tr>
|
||||
<tr><th>Password</th><td><input id='password' type='password' /></td></tr>
|
||||
<tr><th>Log Messages</th><td><textarea id='log' style='width:250px'></textarea></td></tr>
|
||||
<tr><th>Send Limit</th><td><input id='send_limit' value='100' type='text' /></td></tr>
|
||||
<tr><th>Settings Version</th><td><input id='settings_version' value='1' type='text' /></td></tr>
|
||||
<tr><th>Battery</th><td><input id='battery' value='100' type='text' /></td></tr>
|
||||
<tr><th>Power Source</th><td><select id='power'>
|
||||
<option value='0'>0 (battery)</option>
|
||||
<option value='1'>1 (USB)</option>
|
||||
<option value='2'>2 (AC)</option>
|
||||
</select></td></tr>
|
||||
<tr><th>Network Type</th><td><input id='network' value='WIFI' type='text' /></td></tr>
|
||||
<tr><th>Current Timestamp</th><td><input id='now' type='text' /></td></tr>
|
||||
|
||||
<tr><th>Action</th><td><select id='action' onchange='actionChanged()' onkeypress='actionChanged()'>
|
||||
<option value='incoming'>incoming</option>
|
||||
<option value='outgoing'>outgoing</option>
|
||||
<option value='send_status'>send_status</option>
|
||||
<option value='device_status'>device_status</option>
|
||||
<option value='test'>test</option>
|
||||
<option value='amqp_started'>amqp_started</option>
|
||||
</select></td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
<div id='action_incoming'>
|
||||
@ -47,6 +60,7 @@ body
|
||||
<tr><th>Message Type</th><td><select id='message_type'>
|
||||
<option value='sms'>sms</option>
|
||||
<option value='mms'>mms</option>
|
||||
<option value='call'>call</option>
|
||||
</select></td></tr>
|
||||
<tr><th>Message</th><td><textarea id='message' style='width:250px'></textarea></td></tr>
|
||||
<tr><th>Timestamp</th><td><input id='timestamp' type='text' /></td></tr>
|
||||
@ -64,6 +78,7 @@ body
|
||||
<option value='sent'>sent</option>
|
||||
<option value='failed'>failed</option>
|
||||
<option value='queued'>queued</option>
|
||||
<option value='cancelled'>cancelled</option>
|
||||
</select></td></tr>
|
||||
<tr><th>Error Message</th><td><input id='error' type='text' size='50' /></td></tr>
|
||||
</table>
|
||||
@ -80,10 +95,16 @@ body
|
||||
<option value='power_disconnected'>power_disconnected</option>
|
||||
<option value='battery_low'>battery_low</option>
|
||||
<option value='battery_okay'>battery_okay</option>
|
||||
<option value='send_limit_exceeded'>send_limit_exceeded</option>
|
||||
</select></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id='action_amqp_started' style='display:none'>
|
||||
<h4>Parameters for action=amqp_started:</h4>
|
||||
<table class='smsTable'>
|
||||
<tr><th>Consumer Tag</th><td><input id='consumer_tag' type='text' /></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script type='text/javascript'>
|
||||
|
||||
@ -110,11 +131,17 @@ function performAction() {
|
||||
var action = $('action').value;
|
||||
|
||||
var params = {
|
||||
version: '13',
|
||||
version: '29',
|
||||
phone_number: $('phone_number').value,
|
||||
action: action,
|
||||
log: $('log').value
|
||||
};
|
||||
log: $('log').value,
|
||||
send_limit: $('send_limit').value,
|
||||
settings_version: $('settings_version').value,
|
||||
battery: $('battery').value,
|
||||
power: $('power').value,
|
||||
network: $('network').value,
|
||||
now: $('now').value
|
||||
};
|
||||
|
||||
if (action == 'incoming')
|
||||
{
|
||||
@ -133,6 +160,10 @@ function performAction() {
|
||||
{
|
||||
params.status = $('device_status').value;
|
||||
}
|
||||
else if (action == 'amqp_started')
|
||||
{
|
||||
params.status = $('consumer_tag').value;
|
||||
}
|
||||
|
||||
var xhr = (window.ActiveXObject && !window.XMLHttpRequest) ? new ActiveXObject("Msxml2.XMLHTTP") : new XMLHttpRequest();
|
||||
|
||||
@ -189,8 +220,9 @@ function performAction() {
|
||||
xhr.send(paramStr);
|
||||
}
|
||||
|
||||
$('server_url').value = location.href.replace("test.html","");
|
||||
$('server_url').value = location.href.replace("test.html","gateway.php");
|
||||
$('timestamp').value = new Date().getTime();
|
||||
$('now').value = new Date().getTime();
|
||||
|
||||
</script>
|
||||
|
||||
|
464
src/org/envaya/sms/AmqpConsumer.java
Normal file
464
src/org/envaya/sms/AmqpConsumer.java
Normal file
@ -0,0 +1,464 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import org.envaya.sms.service.AmqpConsumerService;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.SystemClock;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.QueueingConsumer;
|
||||
import com.rabbitmq.client.AlreadyClosedException;
|
||||
import com.rabbitmq.client.ShutdownSignalException;
|
||||
import org.envaya.sms.receiver.StartAmqpConsumer;
|
||||
import org.envaya.sms.service.AmqpHeartbeatService;
|
||||
import org.envaya.sms.task.HttpTask;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class AmqpConsumer {
|
||||
|
||||
public static final long RETRY_DELAY = 5000; // ms
|
||||
public static final long RETRY_ERROR_DELAY = 60000; // ms
|
||||
public static final long MIN_EXPECTED_ERROR_DELAY = 10000; // ms
|
||||
|
||||
public static final int WIFI_MODE_FULL_HIGH_PERF = 3;
|
||||
// constant not added until Android 3.1 but seems to work on older versions
|
||||
// (at least 2.3)
|
||||
|
||||
protected Channel channel = null;
|
||||
protected Connection connection;
|
||||
|
||||
protected App app;
|
||||
|
||||
protected ConsumeThread consumeThread;
|
||||
|
||||
protected WifiManager.WifiLock wifiLock;
|
||||
|
||||
protected WifiManager wifiManager;
|
||||
protected AlarmManager alarmManager;
|
||||
//protected PowerManager powerManager;
|
||||
|
||||
public AmqpConsumer(App app)
|
||||
{
|
||||
this.app = app;
|
||||
wifiManager = (WifiManager) app.getSystemService(Context.WIFI_SERVICE);
|
||||
alarmManager = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||
//powerManager = (PowerManager) app.getSystemService(Context.POWER_SERVICE);
|
||||
}
|
||||
|
||||
// 'async' and 'delayed' methods must not be synchronized or app will deadlock ---
|
||||
// StartAmqpConsumerService thread owns lock on AmqpConsumer and needs lock on App to call app.log,
|
||||
// while main thread in onConnectivityChanged owns lock on App and must not take any locks on AmqpConsumer
|
||||
public void startAsync()
|
||||
{
|
||||
startStopAsync(true);
|
||||
}
|
||||
|
||||
public void stopAsync()
|
||||
{
|
||||
cancelStartDelayed();
|
||||
startStopAsync(false);
|
||||
}
|
||||
|
||||
public void startStopAsync(boolean start)
|
||||
{
|
||||
Intent intent = new Intent(app, AmqpConsumerService.class);
|
||||
intent.putExtra("start", start);
|
||||
app.startService(intent);
|
||||
}
|
||||
|
||||
public void cancelStartDelayed()
|
||||
{
|
||||
alarmManager.cancel(getStartPendingIntent());
|
||||
}
|
||||
|
||||
public PendingIntent getStartPendingIntent()
|
||||
{
|
||||
return PendingIntent.getBroadcast(app,
|
||||
0,
|
||||
new Intent(app, StartAmqpConsumer.class),
|
||||
0);
|
||||
}
|
||||
|
||||
public void startDelayed(long delay)
|
||||
{
|
||||
alarmManager.set(
|
||||
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
SystemClock.elapsedRealtime() + delay,
|
||||
getStartPendingIntent()
|
||||
);
|
||||
}
|
||||
|
||||
public synchronized void startBlocking()
|
||||
{
|
||||
stopBlocking();
|
||||
|
||||
boolean enabled = app.tryGetBooleanSetting("amqp_enabled", false);
|
||||
if (!enabled)
|
||||
{
|
||||
app.log("Real-time connection disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
String host = app.tryGetStringSetting("amqp_host", null);
|
||||
int port = app.tryGetIntegerSetting("amqp_port", 0);
|
||||
boolean ssl = app.tryGetBooleanSetting("amqp_ssl", false);
|
||||
String vhost = app.tryGetStringSetting("amqp_vhost", null);
|
||||
String username = app.tryGetStringSetting("amqp_user", null);
|
||||
String password = app.tryGetStringSetting("amqp_password", null);
|
||||
String queue = app.tryGetStringSetting("amqp_queue", null);
|
||||
|
||||
if (host == null || port == 0 || username == null || password == null || queue == null || vhost == null)
|
||||
{
|
||||
app.log("Real-time connection not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean started = tryStart(host, port, ssl, vhost, username, password, queue);
|
||||
if (!started)
|
||||
{
|
||||
startDelayed(RETRY_ERROR_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable heartbeatRunnable;
|
||||
|
||||
public synchronized void setHeartbeatRunnable(Runnable heartbeatRunnable)
|
||||
{
|
||||
this.heartbeatRunnable = heartbeatRunnable;
|
||||
}
|
||||
|
||||
public synchronized void sendHeartbeatBlocking()
|
||||
{
|
||||
if (heartbeatRunnable != null)
|
||||
{
|
||||
heartbeatRunnable.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("no heartbeat runnable");
|
||||
}
|
||||
}
|
||||
|
||||
public PendingIntent getHeartbeatPendingIntent()
|
||||
{
|
||||
return PendingIntent.getService(app,
|
||||
0,
|
||||
new Intent(app, AmqpHeartbeatService.class),
|
||||
0);
|
||||
}
|
||||
|
||||
public class HeartbeatExecutor extends ScheduledThreadPoolExecutor
|
||||
{
|
||||
public HeartbeatExecutor()
|
||||
{
|
||||
super(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
|
||||
{
|
||||
long delayMs = TimeUnit.MILLISECONDS.convert(initialDelay, unit);
|
||||
long periodMs = TimeUnit.MILLISECONDS.convert(period, unit);
|
||||
|
||||
setHeartbeatRunnable(command);
|
||||
|
||||
alarmManager.setRepeating(
|
||||
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
SystemClock.elapsedRealtime() + delayMs,
|
||||
periodMs,
|
||||
getHeartbeatPendingIntent());
|
||||
|
||||
//app.log("scheduleAtFixedRate " + delayMs + ", " + periodMs);
|
||||
|
||||
return new ScheduledFuture<Integer>()
|
||||
{
|
||||
public long getDelay(TimeUnit u2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
public int compareTo(Delayed d2)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
public Integer get()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public Integer get(long timeout, TimeUnit unit)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public boolean cancel(boolean interrupt)
|
||||
{
|
||||
// doesn't do anything -- alarm cancelled by stopBlocking
|
||||
cancelled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return cancelled;
|
||||
}
|
||||
public boolean isDone()
|
||||
{
|
||||
return cancelled;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean tryStart(String host, int port, boolean ssl, String vhost, String username, String password, String queue)
|
||||
{
|
||||
app.log("Establishing real-time connection...");
|
||||
|
||||
if (wifiManager.isWifiEnabled())
|
||||
{
|
||||
if (wifiLock == null)
|
||||
{
|
||||
wifiLock = wifiManager.createWifiLock(WIFI_MODE_FULL_HIGH_PERF, "telerivet-amqp");
|
||||
wifiLock.setReferenceCounted(false);
|
||||
}
|
||||
|
||||
wifiLock.acquire();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ConnectionFactory connectionFactory = new ConnectionFactory();
|
||||
|
||||
connectionFactory.setHost(host);
|
||||
connectionFactory.setPort(port);
|
||||
connectionFactory.setVirtualHost(vhost);
|
||||
connectionFactory.setUsername(username);
|
||||
connectionFactory.setPassword(password);
|
||||
|
||||
/*
|
||||
connectionFactory.setLogger(new IILogger() {
|
||||
public void log(String str)
|
||||
{
|
||||
app.log(str);
|
||||
}
|
||||
});
|
||||
*/
|
||||
connectionFactory.setHeartbeatExecutor(new HeartbeatExecutor());
|
||||
|
||||
TrustManager[] trustManagers = null; // use built-in SSL certificate verification
|
||||
|
||||
if (ssl)
|
||||
{
|
||||
/*
|
||||
// could customize SSL certificate verification
|
||||
trustManagers = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType)
|
||||
throws CertificateException
|
||||
{
|
||||
}
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
SSLContext c = SSLContext.getInstance("TLS");
|
||||
c.init(null, trustManagers, new SecureRandom());
|
||||
|
||||
connectionFactory.useSslProtocol(c);
|
||||
}
|
||||
|
||||
// Need to periodically check if connection is still working
|
||||
// to allow the client and server to detect broken connections
|
||||
// after 2*heartbeat seconds
|
||||
|
||||
// AMQP heartbeat interval has a big effect on battery usage on idle phones.
|
||||
// The CPU will wake up every heartbeat for 5 seconds.
|
||||
// For the rest of the time, the phone stays in deep sleep (CPU off) and is automatically
|
||||
// woken up whenever the server sends a packet.
|
||||
connectionFactory.setRequestedHeartbeat(app.tryGetIntegerSetting("amqp_heartbeat", 300));
|
||||
|
||||
connection = connectionFactory.newConnection();
|
||||
channel = connection.createChannel();
|
||||
|
||||
channel.queueDeclare(queue, true, false, false, null);
|
||||
channel.basicQos(1);
|
||||
|
||||
QueueingConsumer consumer = new QueueingConsumer(channel);
|
||||
|
||||
channel.basicConsume(queue, false, consumer);
|
||||
|
||||
consumeThread = new ConsumeThread(consumer);
|
||||
consumeThread.start();
|
||||
|
||||
HttpTask task = new HttpTask(app,
|
||||
new BasicNameValuePair("action", App.ACTION_AMQP_STARTED),
|
||||
new BasicNameValuePair("consumer_tag", consumer.getConsumerTag())
|
||||
);
|
||||
task.execute();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
app.logError("Error establishing real-time connection", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopBlocking() {
|
||||
|
||||
try
|
||||
{
|
||||
if (wifiLock != null && wifiLock.isHeld())
|
||||
{
|
||||
wifiLock.release();
|
||||
}
|
||||
alarmManager.cancel(getHeartbeatPendingIntent());
|
||||
heartbeatRunnable = null;
|
||||
|
||||
if (consumeThread != null)
|
||||
{
|
||||
consumeThread.terminate();
|
||||
consumeThread = null;
|
||||
}
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
channel = null;
|
||||
}
|
||||
|
||||
if (connection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.close(10000);
|
||||
}
|
||||
catch (AlreadyClosedException ex) {}
|
||||
catch (ShutdownSignalException ex) {}
|
||||
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
app.logError("Error stopping real-time connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsumeThread extends Thread
|
||||
{
|
||||
private QueueingConsumer consumer;
|
||||
private boolean terminated = false;
|
||||
|
||||
public ConsumeThread(QueueingConsumer consumer)
|
||||
{
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
public synchronized void terminate()
|
||||
{
|
||||
this.terminated = true;
|
||||
}
|
||||
|
||||
public synchronized boolean isTerminated()
|
||||
{
|
||||
return terminated;
|
||||
}
|
||||
|
||||
public void processMessage(QueueingConsumer.Delivery delivery)
|
||||
{
|
||||
String jsonStr = new String(delivery.getBody());
|
||||
|
||||
try
|
||||
{
|
||||
JSONObject json = new JSONObject(jsonStr);
|
||||
JsonUtils.processEvent(json, app, null);
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
app.logError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long startTime = SystemClock.elapsedRealtime();
|
||||
|
||||
try
|
||||
{
|
||||
app.log("Real-time connection established.");
|
||||
|
||||
Channel ch = consumer.getChannel();
|
||||
|
||||
while(true)
|
||||
{
|
||||
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
|
||||
|
||||
if (isTerminated())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ch.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
|
||||
|
||||
processMessage(delivery);
|
||||
|
||||
if (isTerminated())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
app.log("Real-time connection stopped.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!isTerminated())
|
||||
{
|
||||
if (ex instanceof ShutdownSignalException)
|
||||
{
|
||||
//app.logError("Real-time connection interrupted", ex);
|
||||
app.log("Real-time connection interrupted");
|
||||
}
|
||||
else
|
||||
{
|
||||
app.logError("Real-time connection interrupted", ex);
|
||||
}
|
||||
|
||||
long age = SystemClock.elapsedRealtime() - startTime;
|
||||
|
||||
stopAsync();
|
||||
if (age < MIN_EXPECTED_ERROR_DELAY)
|
||||
{
|
||||
startDelayed(RETRY_ERROR_DELAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
startDelayed(RETRY_DELAY);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Real-time connection stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import org.envaya.sms.service.EnabledChangedService;
|
||||
import android.app.Activity;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Application;
|
||||
@ -18,8 +19,6 @@ import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.Log;
|
||||
@ -54,9 +53,17 @@ public final class App extends Application {
|
||||
|
||||
public static final String ACTION_OUTGOING = "outgoing";
|
||||
public static final String ACTION_INCOMING = "incoming";
|
||||
public static final String ACTION_FORWARD_SENT = "forward_sent";
|
||||
public static final String ACTION_SEND_STATUS = "send_status";
|
||||
public static final String ACTION_DEVICE_STATUS = "device_status";
|
||||
public static final String ACTION_TEST = "test";
|
||||
public static final String ACTION_AMQP_STARTED = "amqp_started";
|
||||
|
||||
public static final String EVENT_SEND = "send";
|
||||
public static final String EVENT_CANCEL = "cancel";
|
||||
public static final String EVENT_CANCEL_ALL = "cancel_all";
|
||||
public static final String EVENT_LOG = "log";
|
||||
public static final String EVENT_SETTINGS = "settings";
|
||||
|
||||
public static final String STATUS_QUEUED = "queued";
|
||||
public static final String STATUS_FAILED = "failed";
|
||||
@ -77,7 +84,10 @@ public final class App extends Application {
|
||||
|
||||
// intent to signal to Main activity (if open) that log has changed
|
||||
public static final String LOG_CHANGED_INTENT = "org.envaya.sms.LOG_CHANGED";
|
||||
|
||||
public static final String SETTINGS_CHANGED_INTENT = "org.envaya.sms.SETTINGS_CHANGED";
|
||||
|
||||
public static final String EXPANSION_PACKS_CHANGED_INTENT = "org.envaya.sms.EXPANSION_PACKS_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 OUTBOX_CHANGED_INTENT = "org.envaya.sms.OUTBOX_CHANGED";
|
||||
@ -134,7 +144,8 @@ public final class App extends Application {
|
||||
public final Queue<HttpTask> queuedTasks = new LinkedList<HttpTask>();
|
||||
|
||||
private SharedPreferences settings;
|
||||
private MmsObserver mmsObserver;
|
||||
private MessagingObserver messagingObserver;
|
||||
|
||||
private SpannableStringBuilder displayedLog = new SpannableStringBuilder();
|
||||
private long lastLogTime;
|
||||
|
||||
@ -151,24 +162,39 @@ public final class App extends Application {
|
||||
// count to provide round-robin selection of expansion packs
|
||||
private int outgoingMessageCount = -1;
|
||||
|
||||
private MmsUtils mmsUtils;
|
||||
private MessagingUtils messagingUtils;
|
||||
private CallListener callListener;
|
||||
private DatabaseHelper dbHelper;
|
||||
private AmqpConsumer amqpConsumer;
|
||||
|
||||
private boolean connectivityError = false;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
|
||||
// workaround for http://code.google.com/p/android/issues/detail?id=20915
|
||||
try
|
||||
{
|
||||
Class.forName("android.os.AsyncTask");
|
||||
}
|
||||
catch (ClassNotFoundException ex)
|
||||
{
|
||||
}
|
||||
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mmsUtils = new MmsUtils(this);
|
||||
messagingUtils = new MessagingUtils(this);
|
||||
|
||||
callListener = new CallListener(this);
|
||||
|
||||
outgoingMessagePackages.add(getPackageName());
|
||||
|
||||
mmsObserver = new MmsObserver(this);
|
||||
messagingObserver = new MessagingObserver(this);
|
||||
|
||||
dbHelper = new DatabaseHelper(this);
|
||||
|
||||
amqpConsumer = new AmqpConsumer(this);
|
||||
|
||||
try
|
||||
{
|
||||
@ -187,92 +213,30 @@ public final class App extends Application {
|
||||
|
||||
public void configuredChanged()
|
||||
{
|
||||
log(Html.fromHtml(
|
||||
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)
|
||||
if (isConfigured())
|
||||
{
|
||||
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);
|
||||
}
|
||||
sendBroadcast(new Intent(App.SETTINGS_CHANGED_INTENT));
|
||||
enabledChanged();
|
||||
}
|
||||
|
||||
if (isTestMode())
|
||||
{
|
||||
log("Test mode: ON");
|
||||
log("Test phone numbers:");
|
||||
|
||||
for (String sender : getTestPhoneNumbers())
|
||||
{
|
||||
log(" " + sender);
|
||||
}
|
||||
}
|
||||
|
||||
log(Html.fromHtml("<b>To change these settings, click Menu, then Settings.</b>"));
|
||||
|
||||
enabledChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void enabledChanged()
|
||||
{
|
||||
TelephonyManager telephony = (TelephonyManager)
|
||||
getSystemService(Context.TELEPHONY_SERVICE);
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
mmsObserver.register();
|
||||
|
||||
telephony.listen(callListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mmsObserver.unregister();
|
||||
|
||||
telephony.listen(callListener, PhoneStateListener.LISTEN_NONE);
|
||||
}
|
||||
|
||||
setOutgoingMessageAlarm();
|
||||
startService(new Intent(this, ForegroundService.class));
|
||||
}
|
||||
{
|
||||
// startup/shutdown tasks may be slow, so offload them to a worker thread...
|
||||
// IntentService takes care of only running one request at a time
|
||||
startService(new Intent(this, EnabledChangedService.class));
|
||||
}
|
||||
|
||||
public PackageInfo getPackageInfo()
|
||||
{
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
public boolean isSmsExpansionPackInstalled(String packageName)
|
||||
{
|
||||
return outgoingMessagePackages.contains(packageName);
|
||||
}
|
||||
|
||||
public synchronized String chooseOutgoingSmsPackage(int numParts)
|
||||
{
|
||||
outgoingMessageCount++;
|
||||
@ -388,8 +352,9 @@ public final class App extends Application {
|
||||
|
||||
if (prevLimit != newLimit)
|
||||
{
|
||||
log("Outgoing SMS limit: " + newLimit + " messages/hour");
|
||||
log("Outgoing SMS rate limit: " + newLimit + " messages/hour");
|
||||
}
|
||||
sendBroadcast(new Intent(App.EXPANSION_PACKS_CHANGED_INTENT));
|
||||
}
|
||||
|
||||
public int getOutgoingMessageLimit()
|
||||
@ -442,11 +407,11 @@ public final class App extends Application {
|
||||
{
|
||||
String serverUrl = getServerUrl();
|
||||
if (serverUrl.length() > 0) {
|
||||
log("Checking for outgoing messages");
|
||||
log("Checking for messages");
|
||||
pollActive = true;
|
||||
new PollerTask(this).execute();
|
||||
} else {
|
||||
log("Can't check outgoing messages; server URL not set");
|
||||
log("Can't check messages; server URL not set");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -490,22 +455,46 @@ public final class App extends Application {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConfigured()
|
||||
{
|
||||
return getServerUrl().length() > 0;
|
||||
}
|
||||
|
||||
public boolean callNotificationsEnabled()
|
||||
{
|
||||
return tryGetBooleanSetting("call_notifications", false);
|
||||
}
|
||||
|
||||
public String getConfigureServer() {
|
||||
return settings.getString("configure_server", "");
|
||||
}
|
||||
|
||||
public String getServerUrl() {
|
||||
return settings.getString("server_url", "");
|
||||
}
|
||||
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return settings.getString("phone_number", "");
|
||||
}
|
||||
|
||||
public int getOutgoingPollSeconds() {
|
||||
return Integer.parseInt(settings.getString("outgoing_interval", "0"));
|
||||
public boolean isAmqpEnabled()
|
||||
{
|
||||
return tryGetBooleanSetting("amqp_enabled", false);
|
||||
}
|
||||
|
||||
public String getPhoneID() {
|
||||
return settings.getString("phone_id", "");
|
||||
}
|
||||
|
||||
public String getPhoneToken()
|
||||
{
|
||||
return settings.getString("phone_token", "");
|
||||
}
|
||||
|
||||
public int getOutgoingPollSeconds()
|
||||
{
|
||||
return tryGetIntegerSetting("outgoing_interval", 0);
|
||||
}
|
||||
|
||||
public boolean isEnabled()
|
||||
@ -513,13 +502,30 @@ public final class App extends Application {
|
||||
return tryGetBooleanSetting("enabled", false);
|
||||
}
|
||||
|
||||
public String tryGetStringSetting(String name, String defaultValue)
|
||||
{
|
||||
return settings.getString(name, defaultValue);
|
||||
}
|
||||
|
||||
public int tryGetIntegerSetting(String name, int defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return settings.getInt(name, defaultValue);
|
||||
}
|
||||
catch (ClassCastException ex)
|
||||
{
|
||||
return Integer.parseInt(settings.getString(name, "" + defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean tryGetBooleanSetting(String name, boolean defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return settings.getBoolean(name, defaultValue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (ClassCastException ex)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
@ -530,10 +536,20 @@ public final class App extends Application {
|
||||
return tryGetBooleanSetting("network_failover", false);
|
||||
}
|
||||
|
||||
public boolean isForwardingSentMessagesEnabled()
|
||||
{
|
||||
return tryGetBooleanSetting("forward_sent", false);
|
||||
}
|
||||
|
||||
public boolean isTestMode()
|
||||
{
|
||||
return tryGetBooleanSetting("test_mode", false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean autoAddTestNumber()
|
||||
{
|
||||
return tryGetBooleanSetting("auto_add_test_number", false);
|
||||
}
|
||||
|
||||
public boolean getKeepInInbox()
|
||||
{
|
||||
@ -542,12 +558,17 @@ public final class App extends Application {
|
||||
|
||||
public boolean ignoreShortcodes()
|
||||
{
|
||||
return tryGetBooleanSetting("ignore_shortcodes", true);
|
||||
return tryGetBooleanSetting("ignore_shortcodes", false);
|
||||
}
|
||||
|
||||
public boolean ignoreNonNumeric()
|
||||
{
|
||||
return tryGetBooleanSetting("ignore_non_numeric", true);
|
||||
return tryGetBooleanSetting("ignore_non_numeric", false);
|
||||
}
|
||||
|
||||
public int getSettingsVersion()
|
||||
{
|
||||
return tryGetIntegerSetting("settings_version", 0);
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
@ -628,15 +649,25 @@ public final class App extends Application {
|
||||
sendBroadcast(new Intent(App.LOG_CHANGED_INTENT));
|
||||
}
|
||||
|
||||
public boolean isUpgradeAvailable()
|
||||
{
|
||||
return tryGetIntegerSetting("market_version", 0) > packageInfo.versionCode;
|
||||
}
|
||||
|
||||
public String getMarketVersionName()
|
||||
{
|
||||
return settings.getString("market_version_name", "?");
|
||||
}
|
||||
|
||||
/*
|
||||
* Changes whenever we change the beginning of the displayed log.
|
||||
* If it doesn't change, the Main activity can update the log view much
|
||||
* faster by using TextView.append() instead of TextView.setText()
|
||||
*/
|
||||
public int getLogEpoch()
|
||||
public synchronized int getLogEpoch()
|
||||
{
|
||||
return logEpoch;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized CharSequence getDisplayedLog()
|
||||
{
|
||||
@ -665,9 +696,9 @@ public final class App extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public MmsUtils getMmsUtils()
|
||||
public MessagingUtils getMessagingUtils()
|
||||
{
|
||||
return mmsUtils;
|
||||
return messagingUtils;
|
||||
}
|
||||
|
||||
private List<String> testPhoneNumbers;
|
||||
@ -763,20 +794,48 @@ public final class App extends Application {
|
||||
return values;
|
||||
}
|
||||
|
||||
public boolean isPhoneNumberInList(String phoneNumber, List<String> phoneNumbers)
|
||||
{
|
||||
int phoneLen = phoneNumber.length();
|
||||
|
||||
for (String otherNumber : phoneNumbers)
|
||||
{
|
||||
if (otherNumber == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phoneNumber.equals(otherNumber))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int otherLen = otherNumber.length();
|
||||
|
||||
// fuzzy matching to account for different versions of same phone number (+, area codes, country codes)
|
||||
if ((otherLen >= 7 && phoneNumber.endsWith(otherNumber)) ||
|
||||
phoneLen >= 7 && otherNumber.endsWith(phoneNumber))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isForwardablePhoneNumber(String phoneNumber)
|
||||
{
|
||||
if (isTestMode())
|
||||
{
|
||||
return getTestPhoneNumbers().contains(phoneNumber);
|
||||
{
|
||||
return isPhoneNumberInList(phoneNumber, getTestPhoneNumbers());
|
||||
}
|
||||
|
||||
if (getIgnoredPhoneNumbers().contains(phoneNumber))
|
||||
if (isPhoneNumberInList(phoneNumber, getIgnoredPhoneNumbers()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int numDigits = 0;
|
||||
int length = phoneNumber.length();
|
||||
int length = (phoneNumber == null) ? 0 : phoneNumber.length();
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
@ -949,15 +1008,19 @@ public final class App extends Application {
|
||||
|
||||
if (networkInfo == null || !networkInfo.isConnected())
|
||||
{
|
||||
amqpConsumer.stopAsync();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
amqpConsumer.startDelayed(5000);
|
||||
|
||||
int networkType = networkInfo.getType();
|
||||
|
||||
if (networkType == activeNetworkType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
activeNetworkType = networkType;
|
||||
log("Connected to " + networkInfo.getTypeName());
|
||||
@ -1007,5 +1070,25 @@ public final class App extends Application {
|
||||
public synchronized void addQueuedTask(HttpTask task)
|
||||
{
|
||||
queuedTasks.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseHelper getDatabaseHelper()
|
||||
{
|
||||
return dbHelper;
|
||||
}
|
||||
|
||||
public MessagingObserver getMessagingObserver()
|
||||
{
|
||||
return messagingObserver;
|
||||
}
|
||||
|
||||
public CallListener getCallListener()
|
||||
{
|
||||
return callListener;
|
||||
}
|
||||
|
||||
public AmqpConsumer getAmqpConsumer()
|
||||
{
|
||||
return amqpConsumer;
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import java.util.List;
|
||||
|
||||
public class CheckMmsInboxService extends IntentService
|
||||
{
|
||||
private App app;
|
||||
private MmsUtils mmsUtils;
|
||||
|
||||
public CheckMmsInboxService(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public CheckMmsInboxService()
|
||||
{
|
||||
this("CheckMmsInboxService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
app = (App)this.getApplicationContext();
|
||||
|
||||
mmsUtils = app.getMmsUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
List<IncomingMms> messages = mmsUtils.getMessagesInInbox();
|
||||
for (IncomingMms mms : messages)
|
||||
{
|
||||
if (mmsUtils.isNewMms(mms))
|
||||
{
|
||||
app.log("New MMS id=" + mms.getId() + " in inbox");
|
||||
// prevent forwarding MMS messages that existed in inbox
|
||||
// before EnvayaSMS started, or re-forwarding MMS multiple
|
||||
// times if we don't delete them.
|
||||
mmsUtils.markOldMms(mms);
|
||||
|
||||
if (mms.isForwardable())
|
||||
{
|
||||
app.inbox.forwardMessage(mms);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Ignoring incoming MMS from " + mms.getFrom());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
270
src/org/envaya/sms/DatabaseHelper.java
Normal file
270
src/org/envaya/sms/DatabaseHelper.java
Normal file
@ -0,0 +1,270 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
|
||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public static final String DATABASE_NAME = "envayasms.db";
|
||||
public static final int DATABASE_VERSION = 4;
|
||||
|
||||
private App app;
|
||||
|
||||
public DatabaseHelper(App app)
|
||||
{
|
||||
super(app, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public void onCreate(SQLiteDatabase db)
|
||||
{
|
||||
// Persisted backup of incoming messages have not been forwarded to server.
|
||||
// allows us to restore pending messages after restart if phone runs out of batteries
|
||||
// or app crashes.
|
||||
|
||||
db.execSQL("CREATE TABLE pending_incoming_messages ("
|
||||
+ "`_id` INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
+ "`message_type` VARCHAR,"
|
||||
+ "`messaging_id` INTEGER," // id in Messaging app database (if applicable)
|
||||
+ "`from_number` VARCHAR,"
|
||||
+ "`to_number` VARCHAR,"
|
||||
+ "`message` TEXT,"
|
||||
+ "`direction` INTEGER,"
|
||||
+ "`timestamp` INTEGER"
|
||||
+ ")");
|
||||
|
||||
db.execSQL("CREATE TABLE pending_outgoing_messages ("
|
||||
+ "`_id` INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
+ "`message_type` VARCHAR,"
|
||||
+ "`from_number` VARCHAR,"
|
||||
+ "`to_number` VARCHAR,"
|
||||
+ "`message` TEXT,"
|
||||
+ "`priority` INTEGER,"
|
||||
+ "`server_id` TEXT"
|
||||
+ ")");
|
||||
}
|
||||
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
|
||||
{
|
||||
db.execSQL("DROP TABLE IF EXISTS pending_incoming_messages");
|
||||
db.execSQL("DROP TABLE IF EXISTS pending_outgoing_messages");
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
public synchronized void restorePendingMessages()
|
||||
{
|
||||
restorePendingIncomingMessages();
|
||||
restorePendingOutgoingMessages();
|
||||
}
|
||||
|
||||
public synchronized void restorePendingIncomingMessages()
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor c = db.query("pending_incoming_messages", null, null, null, null, null, null);
|
||||
|
||||
int idIndex = c.getColumnIndex("_id");
|
||||
int messageTypeIndex = c.getColumnIndex("message_type");
|
||||
int messagingIdIndex = c.getColumnIndex("messaging_id");
|
||||
int fromIndex = c.getColumnIndex("from_number");
|
||||
int toIndex = c.getColumnIndex("to_number");
|
||||
int messageIndex = c.getColumnIndex("message");
|
||||
int directionIndex = c.getColumnIndex("direction");
|
||||
int timestampIndex = c.getColumnIndex("timestamp");
|
||||
|
||||
while (c.moveToNext())
|
||||
{
|
||||
long id = c.getLong(idIndex);
|
||||
String messageType = c.getString(messageTypeIndex);
|
||||
long messagingId = c.getLong(messagingIdIndex);
|
||||
|
||||
IncomingMessage message;
|
||||
|
||||
if (App.MESSAGE_TYPE_SMS.equals(messageType))
|
||||
{
|
||||
message = new IncomingSms(app);
|
||||
}
|
||||
else if (App.MESSAGE_TYPE_MMS.equals(messageType))
|
||||
{
|
||||
message = new IncomingMms(app);
|
||||
}
|
||||
else if (App.MESSAGE_TYPE_CALL.equals(messageType))
|
||||
{
|
||||
message = new IncomingCall(app);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Unknown message type " + messageType);
|
||||
continue;
|
||||
}
|
||||
|
||||
message.setMessagingId(messagingId);
|
||||
message.setPersistedId(id);
|
||||
message.setMessageBody(c.getString(messageIndex));
|
||||
message.setFrom(c.getString(fromIndex));
|
||||
message.setTo(c.getString(toIndex));
|
||||
|
||||
int directionInt = c.getInt(directionIndex);
|
||||
IncomingMessage.Direction direction = (directionInt == IncomingMessage.Direction.Sent.ordinal()) ?
|
||||
IncomingMessage.Direction.Sent : IncomingMessage.Direction.Incoming;
|
||||
|
||||
message.setDirection(direction);
|
||||
message.setTimestamp(c.getLong(timestampIndex));
|
||||
|
||||
app.inbox.forwardMessage(message);
|
||||
}
|
||||
c.close();
|
||||
}
|
||||
|
||||
public synchronized void restorePendingOutgoingMessages()
|
||||
{
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor c = db.query("pending_outgoing_messages", null, null, null, null, null, null);
|
||||
|
||||
int idIndex = c.getColumnIndex("_id");
|
||||
int messageTypeIndex = c.getColumnIndex("message_type");
|
||||
int fromIndex = c.getColumnIndex("from_number");
|
||||
int toIndex = c.getColumnIndex("to_number");
|
||||
int messageIndex = c.getColumnIndex("message");
|
||||
int priorityIndex = c.getColumnIndex("priority");
|
||||
int serverIdIndex = c.getColumnIndex("server_id");
|
||||
|
||||
while (c.moveToNext())
|
||||
{
|
||||
long id = c.getLong(idIndex);
|
||||
String messageType = c.getString(messageTypeIndex);
|
||||
|
||||
OutgoingMessage message;
|
||||
|
||||
if (App.MESSAGE_TYPE_SMS.equals(messageType))
|
||||
{
|
||||
message = new OutgoingSms(app);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Unknown message type " + messageType);
|
||||
continue;
|
||||
}
|
||||
|
||||
message.setPersistedId(id);
|
||||
message.setMessageBody(c.getString(messageIndex));
|
||||
message.setFrom(c.getString(fromIndex));
|
||||
|
||||
String to = c.getString(toIndex);
|
||||
|
||||
if (to == null || "null".equals(to))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
message.setTo(to);
|
||||
|
||||
message.setPriority(c.getInt(priorityIndex));
|
||||
message.setServerId(c.getString(serverIdIndex));
|
||||
|
||||
app.outbox.sendMessage(message);
|
||||
}
|
||||
c.close();
|
||||
}
|
||||
|
||||
public synchronized void insertPendingMessage(IncomingMessage message)
|
||||
{
|
||||
if (message.isPersisted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("message_type", message.getMessageType());
|
||||
values.put("messaging_id", message.getMessagingId());
|
||||
values.put("from_number", message.getFrom());
|
||||
values.put("to_number", message.getTo());
|
||||
values.put("message", message.getMessageBody());
|
||||
values.put("direction", message.getDirection().ordinal());
|
||||
values.put("timestamp", message.getTimestamp());
|
||||
|
||||
try
|
||||
{
|
||||
long messageId = db.insertOrThrow("pending_incoming_messages", null, values);
|
||||
message.setPersistedId(messageId);
|
||||
}
|
||||
catch (SQLException ex)
|
||||
{
|
||||
app.logError("Error saving message to database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void insertPendingMessage(OutgoingMessage message)
|
||||
{
|
||||
if (message.isPersisted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("message_type", message.getMessageType());
|
||||
values.put("from_number", message.getFrom());
|
||||
values.put("to_number", message.getTo());
|
||||
values.put("priority", message.getPriority());
|
||||
values.put("message", message.getMessageBody());
|
||||
values.put("server_id", message.getServerId());
|
||||
|
||||
try
|
||||
{
|
||||
long messageId = db.insertOrThrow("pending_outgoing_messages", null, values);
|
||||
message.setPersistedId(messageId);
|
||||
}
|
||||
catch (SQLException ex)
|
||||
{
|
||||
app.logError("Error saving message to database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void deletePendingMessage(IncomingMessage message)
|
||||
{
|
||||
if (!message.isPersisted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
int rows = db.delete("pending_incoming_messages", "_id = ?", new String[] {"" + message.getPersistedId() });
|
||||
if (rows == 0)
|
||||
{
|
||||
app.log("Error deleting pending message from database");
|
||||
}
|
||||
else
|
||||
{
|
||||
message.setPersistedId(0);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void deletePendingMessage(OutgoingMessage message)
|
||||
{
|
||||
if (!message.isPersisted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
int rows = db.delete("pending_outgoing_messages", "_id = ?", new String[] {"" + message.getPersistedId() });
|
||||
if (rows == 0)
|
||||
{
|
||||
app.log("Error deleting pending message from database");
|
||||
}
|
||||
else
|
||||
{
|
||||
message.setPersistedId(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -34,7 +34,9 @@ public class Inbox {
|
||||
}
|
||||
|
||||
incomingMessages.put(uri, message);
|
||||
app.log("Received "+message.getDescription());
|
||||
app.log("Received "+message.getDisplayType());
|
||||
|
||||
app.getDatabaseHelper().insertPendingMessage(message);
|
||||
|
||||
enqueueMessage(message);
|
||||
}
|
||||
@ -83,6 +85,8 @@ public class Inbox {
|
||||
{
|
||||
numForwardingMessages--;
|
||||
}
|
||||
|
||||
app.getDatabaseHelper().deletePendingMessage(message);
|
||||
|
||||
app.log(message.getDescription() + " deleted");
|
||||
notifyChanged();
|
||||
@ -98,7 +102,8 @@ public class Inbox {
|
||||
}
|
||||
notifyChanged();
|
||||
|
||||
numForwardingMessages--;
|
||||
numForwardingMessages--;
|
||||
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
|
||||
@ -111,17 +116,12 @@ public class Inbox {
|
||||
|
||||
notifyChanged();
|
||||
|
||||
if (message instanceof IncomingMms)
|
||||
{
|
||||
IncomingMms mms = (IncomingMms)message;
|
||||
if (!app.getKeepInInbox())
|
||||
{
|
||||
app.log("Deleting MMS " + mms.getId() + " from inbox...");
|
||||
app.getMmsUtils().deleteFromInbox(mms);
|
||||
}
|
||||
}
|
||||
|
||||
message.onForwardComplete();
|
||||
|
||||
numForwardingMessages--;
|
||||
|
||||
app.getDatabaseHelper().deletePendingMessage(message);
|
||||
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
|
||||
|
@ -5,25 +5,17 @@
|
||||
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()
|
||||
public IncomingCall(App app)
|
||||
{
|
||||
long id = nextId;
|
||||
nextId++;
|
||||
return id;
|
||||
super(app);
|
||||
}
|
||||
|
||||
public String getDisplayType()
|
||||
@ -44,6 +36,6 @@ public class IncomingCall extends IncomingMessage {
|
||||
|
||||
public Uri getUri()
|
||||
{
|
||||
return Uri.withAppendedPath(App.INCOMING_URI, "call/" + id);
|
||||
return Uri.withAppendedPath(App.INCOMING_URI, "call/" + Uri.encode(from) + "/" + timestamp);
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,29 @@ import org.apache.http.message.BasicNameValuePair;
|
||||
|
||||
public abstract class IncomingMessage extends QueuedMessage {
|
||||
|
||||
protected String from;
|
||||
protected String from; // phone number of other party, for messages with direction=Direction.Incoming
|
||||
|
||||
protected String to; // phone number of other party, for messages with direction=Direction.Sent
|
||||
|
||||
protected Direction direction = Direction.Incoming;
|
||||
|
||||
protected long messagingId; // _id from Messaging app content provider tables (if applicable)
|
||||
|
||||
protected String message = "";
|
||||
protected long timestamp; // unix timestamp in milliseconds
|
||||
|
||||
protected long timeReceived; // SystemClock.elapsedRealtime
|
||||
protected long timeCreated; // SystemClock.elapsedRealtime
|
||||
|
||||
private ProcessingState state = ProcessingState.None;
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Incoming, // a message that was received by this phone
|
||||
|
||||
Sent // Message was sent via Messaging app (so it's "Incoming" from
|
||||
// the phone to the server, but we don't actually send it)
|
||||
}
|
||||
|
||||
public enum ProcessingState
|
||||
{
|
||||
None, // not doing anything with this sms now... just sitting around
|
||||
@ -25,13 +40,33 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
Forwarded
|
||||
}
|
||||
|
||||
public IncomingMessage(App app)
|
||||
{
|
||||
super(app);
|
||||
}
|
||||
|
||||
public IncomingMessage(App app, String from, long timestamp)
|
||||
{
|
||||
super(app);
|
||||
this.from = from;
|
||||
this.timestamp = timestamp;
|
||||
|
||||
this.timeReceived = SystemClock.elapsedRealtime();
|
||||
if (from == null)
|
||||
{
|
||||
from = "";
|
||||
}
|
||||
|
||||
this.from = from;
|
||||
this.timestamp = timestamp;
|
||||
this.timeCreated = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
public void setDirection(Direction direction)
|
||||
{
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public Direction getDirection()
|
||||
{
|
||||
return this.direction;
|
||||
}
|
||||
|
||||
public String getMessageBody()
|
||||
@ -39,9 +74,14 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessageBody(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public long getAge()
|
||||
{
|
||||
return SystemClock.elapsedRealtime() - timeReceived;
|
||||
return SystemClock.elapsedRealtime() - timeCreated;
|
||||
}
|
||||
|
||||
public long getTimestamp()
|
||||
@ -49,6 +89,11 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public ProcessingState getProcessingState()
|
||||
{
|
||||
return state;
|
||||
@ -61,14 +106,47 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
|
||||
public boolean isForwardable()
|
||||
{
|
||||
return app.isForwardablePhoneNumber(from);
|
||||
if (direction == Direction.Sent)
|
||||
{
|
||||
return app.isForwardingSentMessagesEnabled() && app.isForwardablePhoneNumber(to);
|
||||
}
|
||||
else
|
||||
{
|
||||
return app.isForwardablePhoneNumber(from);
|
||||
}
|
||||
}
|
||||
|
||||
public String getFrom()
|
||||
{
|
||||
return from;
|
||||
}
|
||||
|
||||
|
||||
public void setFrom(String from)
|
||||
{
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public String getTo()
|
||||
{
|
||||
return to;
|
||||
}
|
||||
|
||||
public void setTo(String to)
|
||||
{
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public long getMessagingId()
|
||||
{
|
||||
return messagingId;
|
||||
}
|
||||
|
||||
public void setMessagingId(long messagingId)
|
||||
{
|
||||
this.messagingId = messagingId;
|
||||
}
|
||||
|
||||
|
||||
protected Intent getRetryIntent() {
|
||||
Intent intent = new Intent(app, IncomingMessageRetry.class);
|
||||
intent.setData(this.getUri());
|
||||
@ -92,7 +170,14 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return getDisplayType() + " from " + getFrom();
|
||||
if (direction == Direction.Sent)
|
||||
{
|
||||
return "Sent " + getDisplayType() + " to " + getTo();
|
||||
}
|
||||
else
|
||||
{
|
||||
return getDisplayType() + " from " + getFrom();
|
||||
}
|
||||
}
|
||||
|
||||
public void tryForwardToServer()
|
||||
@ -107,15 +192,30 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
|
||||
public abstract String getMessageType();
|
||||
|
||||
public void onForwardComplete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected ForwarderTask getForwarderTask()
|
||||
{
|
||||
return new ForwarderTask(this,
|
||||
ForwarderTask task = 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())
|
||||
new BasicNameValuePair("timestamp", "" + getTimestamp())
|
||||
);
|
||||
|
||||
if (direction == Direction.Sent)
|
||||
{
|
||||
task.addParam("action", App.ACTION_FORWARD_SENT);
|
||||
task.addParam("to", getTo());
|
||||
}
|
||||
else
|
||||
{
|
||||
task.addParam("action", App.ACTION_INCOMING);
|
||||
task.addParam("from", getFrom());
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
@ -14,59 +14,60 @@ import org.apache.http.entity.mime.content.ContentBody;
|
||||
import org.envaya.sms.task.ForwarderTask;
|
||||
|
||||
public class IncomingMms extends IncomingMessage {
|
||||
List<MmsPart> parts;
|
||||
long id;
|
||||
String contentLocation;
|
||||
private List<MmsPart> parts;
|
||||
|
||||
public IncomingMms(App app, String from, long timestamp, long id)
|
||||
public IncomingMms(App app, long timestamp, long messagingId)
|
||||
{
|
||||
super(app, from, timestamp);
|
||||
this.parts = new ArrayList<MmsPart>();
|
||||
this.id = id;
|
||||
}
|
||||
super(app, null, timestamp);
|
||||
this.messagingId = messagingId;
|
||||
}
|
||||
|
||||
public IncomingMms(App app)
|
||||
{
|
||||
super(app);
|
||||
}
|
||||
|
||||
public String getDisplayType()
|
||||
@Override
|
||||
public String getFrom()
|
||||
{
|
||||
return "MMS";
|
||||
if (from == null || from.length() == 0)
|
||||
{
|
||||
// lazy-load sender number from Messaging database as needed
|
||||
from = app.getMessagingUtils().getMmsSenderNumber(messagingId);
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
public List<MmsPart> getParts()
|
||||
{
|
||||
if (parts == null)
|
||||
{
|
||||
// lazy-load mms parts from Messaging database as needed
|
||||
this.parts = new ArrayList<MmsPart>();
|
||||
for (MmsPart part : app.getMessagingUtils().getMmsParts(messagingId))
|
||||
{
|
||||
parts.add(part);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public void addPart(MmsPart part)
|
||||
{
|
||||
parts.add(part);
|
||||
}
|
||||
|
||||
public long getId()
|
||||
public String getDisplayType()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getContentLocation()
|
||||
{
|
||||
return contentLocation;
|
||||
}
|
||||
|
||||
public void setContentLocation(String contentLocation)
|
||||
{
|
||||
this.contentLocation = contentLocation;
|
||||
}
|
||||
|
||||
return "MMS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("MMS id=");
|
||||
builder.append(id);
|
||||
builder.append(messagingId);
|
||||
builder.append(" from=");
|
||||
builder.append(from);
|
||||
builder.append(":\n");
|
||||
|
||||
for (MmsPart part : parts)
|
||||
for (MmsPart part : getParts())
|
||||
{
|
||||
builder.append(" ");
|
||||
builder.append(part.toString());
|
||||
@ -84,7 +85,7 @@ public class IncomingMms extends IncomingMessage {
|
||||
|
||||
JSONArray partsMetadata = new JSONArray();
|
||||
|
||||
for (MmsPart part : parts)
|
||||
for (MmsPart part : getParts())
|
||||
{
|
||||
String formFieldName = "part" + i;
|
||||
String text = part.getText();
|
||||
@ -148,7 +149,7 @@ public class IncomingMms extends IncomingMessage {
|
||||
@Override
|
||||
public String getMessageBody()
|
||||
{
|
||||
for (MmsPart part : parts)
|
||||
for (MmsPart part : getParts())
|
||||
{
|
||||
if ("text/plain".equals(part.getContentType()))
|
||||
{
|
||||
@ -161,11 +162,21 @@ public class IncomingMms extends IncomingMessage {
|
||||
|
||||
public Uri getUri()
|
||||
{
|
||||
return Uri.withAppendedPath(App.INCOMING_URI, "mms/" + id);
|
||||
return Uri.withAppendedPath(App.INCOMING_URI, "mms/" + messagingId);
|
||||
}
|
||||
|
||||
public String getMessageType()
|
||||
{
|
||||
return App.MESSAGE_TYPE_MMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForwardComplete()
|
||||
{
|
||||
if (!app.getKeepInInbox())
|
||||
{
|
||||
app.log("Deleting MMS " + getMessagingId() + " from inbox...");
|
||||
app.getMessagingUtils().deleteFromMmsInbox(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import android.net.Uri;
|
||||
import android.telephony.SmsMessage;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.List;
|
||||
import org.envaya.sms.task.ForwarderTask;
|
||||
|
||||
|
||||
public class IncomingSms extends IncomingMessage {
|
||||
@ -34,12 +33,11 @@ public class IncomingSms extends IncomingMessage {
|
||||
message = message + smsPart.getMessageBody();
|
||||
}
|
||||
}
|
||||
|
||||
// constructor for SMS retrieved from Messaging inbox
|
||||
public IncomingSms(App app, String from, String message, long timestampMillis) {
|
||||
super(app, from, timestampMillis);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public IncomingSms(App app)
|
||||
{
|
||||
super(app);
|
||||
}
|
||||
|
||||
public String getDisplayType()
|
||||
{
|
||||
|
196
src/org/envaya/sms/JsonUtils.java
Normal file
196
src/org/envaya/sms/JsonUtils.java
Normal file
@ -0,0 +1,196 @@
|
||||
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class JsonUtils {
|
||||
|
||||
public static JSONObject parseResponse(HttpResponse response)
|
||||
throws IOException, JSONException
|
||||
{
|
||||
String responseBody = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
|
||||
return new JSONObject(responseBody);
|
||||
}
|
||||
|
||||
public static String getErrorText(JSONObject json)
|
||||
{
|
||||
JSONObject errorObject = json.optJSONObject("error");
|
||||
return errorObject != null ? errorObject.optString("message") : null;
|
||||
}
|
||||
|
||||
public static void processEvents(JSONObject json, App app, String defaultTo)
|
||||
throws JSONException
|
||||
{
|
||||
JSONArray events = json.optJSONArray("events");
|
||||
if (events != null)
|
||||
{
|
||||
int numEvents = events.length();
|
||||
for (int i = 0; i < numEvents; i++)
|
||||
{
|
||||
JsonUtils.processEvent(events.getJSONObject(i), app, defaultTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void processEvent(JSONObject json, App app, String defaultTo)
|
||||
throws JSONException
|
||||
{
|
||||
String event = json.getString("event");
|
||||
|
||||
if (App.EVENT_SEND.equals(event))
|
||||
{
|
||||
for (OutgoingMessage message : JsonUtils.getMessagesList(json, app, defaultTo))
|
||||
{
|
||||
app.outbox.sendMessage(message);
|
||||
}
|
||||
}
|
||||
else if (App.EVENT_LOG.equals(event))
|
||||
{
|
||||
app.log(json.getString("message"));
|
||||
}
|
||||
else if (App.EVENT_SETTINGS.equals(event))
|
||||
{
|
||||
JsonUtils.updateSettings(json, app);
|
||||
}
|
||||
else if (App.EVENT_CANCEL.equals(event))
|
||||
{
|
||||
String id = json.getString("id");
|
||||
|
||||
OutgoingMessage message = app.outbox.getMessage(OutgoingMessage.getUriForServerId(id));
|
||||
if (message != null && message.isCancelable())
|
||||
{
|
||||
app.outbox.deleteMessage(message);
|
||||
app.outbox.maybeDequeueMessage();
|
||||
}
|
||||
}
|
||||
else if (App.EVENT_CANCEL_ALL.equals(event))
|
||||
{
|
||||
for (OutgoingMessage message : app.outbox.getMessages())
|
||||
{
|
||||
if (message.isCancelable())
|
||||
{
|
||||
app.outbox.deleteMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Unknown event" + event);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateSettings(JSONObject json, App app)
|
||||
throws JSONException
|
||||
{
|
||||
JSONObject settingsObject = json.optJSONObject("settings");
|
||||
|
||||
if (settingsObject != null && settingsObject.length() > 0)
|
||||
{
|
||||
SharedPreferences.Editor settings = PreferenceManager.getDefaultSharedPreferences(app).edit();
|
||||
|
||||
Iterator it = settingsObject.keys();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = (String)it.next();
|
||||
Object value = settingsObject.get(name);
|
||||
|
||||
if (value instanceof String)
|
||||
{
|
||||
settings.putString(name, (String)value);
|
||||
}
|
||||
else if (value instanceof Boolean)
|
||||
{
|
||||
settings.putBoolean(name, (Boolean)value);
|
||||
}
|
||||
else if (value instanceof Integer)
|
||||
{
|
||||
settings.putInt(name, (Integer)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Unknown setting type " + value.getClass().getName() + " for name " + name);
|
||||
}
|
||||
}
|
||||
|
||||
settings.commit();
|
||||
app.log("Updated app settings");
|
||||
app.configuredChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static App app;
|
||||
|
||||
// like JSONObject.optString but doesn't convert null to "null"
|
||||
private static String optString(JSONObject json, String key, String defaultValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object value = json.get(key);
|
||||
|
||||
if (value == null || JSONObject.NULL.equals(value))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
catch (JSONException ex)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<OutgoingMessage> getMessagesList(JSONObject json, App app, String defaultTo)
|
||||
throws JSONException
|
||||
{
|
||||
List<OutgoingMessage> messages = new ArrayList<OutgoingMessage>();
|
||||
JSONArray messagesList = json.optJSONArray("messages");
|
||||
JsonUtils.app = app;
|
||||
if (messagesList != null)
|
||||
{
|
||||
int numMessages = messagesList.length();
|
||||
|
||||
for (int i = 0; i < numMessages; i++)
|
||||
{
|
||||
JSONObject messageObject = messagesList.getJSONObject(i);
|
||||
|
||||
OutgoingMessage message = OutgoingMessage.newFromMessageType(app,
|
||||
optString(messageObject, "type", App.MESSAGE_TYPE_SMS));
|
||||
|
||||
message.setFrom(app.getPhoneNumber());
|
||||
|
||||
String to = optString(messageObject, "to", defaultTo);
|
||||
|
||||
if (to == null || "".equals(to) || "null".equals(to))
|
||||
{
|
||||
app.log("Received invalid SMS from server (missing recipient)");
|
||||
continue;
|
||||
}
|
||||
|
||||
String body = optString(messageObject, "message","");
|
||||
|
||||
message.setTo(to);
|
||||
message.setServerId(optString(messageObject, "id", null));
|
||||
message.setPriority(messageObject.optInt("priority", 0));
|
||||
message.setMessageBody(body);
|
||||
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
}
|
54
src/org/envaya/sms/MessagingObserver.java
Normal file
54
src/org/envaya/sms/MessagingObserver.java
Normal file
@ -0,0 +1,54 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import org.envaya.sms.service.CheckMessagingService;
|
||||
import android.content.Intent;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import java.util.List;
|
||||
|
||||
public final class MessagingObserver extends ContentObserver {
|
||||
|
||||
// constants from android.provider.Telephony
|
||||
public static final Uri OBSERVER_URI = Uri.parse("content://mms-sms/");
|
||||
|
||||
private App app;
|
||||
|
||||
public MessagingObserver(App app) {
|
||||
super(new Handler());
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
app.getContentResolver().registerContentObserver(OBSERVER_URI, true, this);
|
||||
|
||||
MessagingUtils messagingUtils = app.getMessagingUtils();
|
||||
|
||||
for (IncomingMms mms : messagingUtils.getMessagesInMmsInbox())
|
||||
{
|
||||
messagingUtils.markSeenMms(mms);
|
||||
}
|
||||
|
||||
for (IncomingSms sms : messagingUtils.getSentSmsMessages())
|
||||
{
|
||||
messagingUtils.markSeenSentSms(sms);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister()
|
||||
{
|
||||
app.getContentResolver().unregisterContentObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(final boolean selfChange) {
|
||||
super.onChange(selfChange);
|
||||
if (!selfChange)
|
||||
{
|
||||
// check MMS inbox in an IntentService since it may be slow
|
||||
// and we only want to do one check at a time
|
||||
app.startService(new Intent(app, CheckMessagingService.class));
|
||||
}
|
||||
}
|
||||
}
|
115
src/org/envaya/sms/MmsUtils.java → src/org/envaya/sms/MessagingUtils.java
Executable file → Normal file
115
src/org/envaya/sms/MmsUtils.java → src/org/envaya/sms/MessagingUtils.java
Executable file → Normal file
@ -7,39 +7,42 @@ import android.net.Uri;
|
||||
import java.util.*;
|
||||
|
||||
/*
|
||||
* Utilities for parsing IncomingMms from the MMS content provider tables,
|
||||
* as defined by android.provider.Telephony
|
||||
* Utilities for parsing MMS and SMS messages from the content provider tables
|
||||
* of the Messaging app, as defined by android.provider.Telephony
|
||||
*
|
||||
* Analogous to com.google.android.mms.pdu.PduPersister from
|
||||
* MMS parsing is analogous to com.google.android.mms.pdu.PduPersister from
|
||||
* core/java/com/google/android/mms/pdu in the base Android framework
|
||||
* (https://github.com/android/platform_frameworks_base)
|
||||
*/
|
||||
public class MmsUtils
|
||||
public class MessagingUtils
|
||||
{
|
||||
// constants from android.provider.Telephony
|
||||
public static final Uri OBSERVER_URI = Uri.parse("content://mms-sms/");
|
||||
public static final Uri INBOX_URI = Uri.parse("content://mms/inbox");
|
||||
public static final Uri PART_URI = Uri.parse("content://mms/part");
|
||||
public static final Uri MMS_INBOX_URI = Uri.parse("content://mms/inbox");
|
||||
public static final Uri MMS_PART_URI = Uri.parse("content://mms/part");
|
||||
|
||||
public static final Uri SENT_SMS_URI = Uri.parse("content://sms/sent");
|
||||
|
||||
// constants from com.google.android.mms.pdu.PduHeaders
|
||||
private static final int PDU_HEADER_FROM = 0x89;
|
||||
private static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
|
||||
|
||||
// todo -- prevent unbounded growth?
|
||||
// todo -- prevent (very slow) unbounded growth?
|
||||
private final Set<Long> seenMmsIds = new HashSet<Long>();
|
||||
|
||||
private final Set<Long> seenSentSmsIds = new HashSet<Long>();
|
||||
|
||||
private App app;
|
||||
private ContentResolver contentResolver;
|
||||
|
||||
public MmsUtils(App app)
|
||||
public MessagingUtils(App app)
|
||||
{
|
||||
this.app = app;
|
||||
this.contentResolver = app.getContentResolver();
|
||||
}
|
||||
|
||||
private List<MmsPart> getMmsParts(long id)
|
||||
public List<MmsPart> getMmsParts(long id)
|
||||
{
|
||||
Cursor cur = contentResolver.query(PART_URI, new String[] {
|
||||
Cursor cur = contentResolver.query(MMS_PART_URI, new String[] {
|
||||
"_id", "ct", "name", "text", "cid", "_data"
|
||||
}, "mid = ?", new String[] { "" + id }, null);
|
||||
|
||||
@ -67,7 +70,8 @@ public class MmsUtils
|
||||
|
||||
if (name == null || name.length() == 0)
|
||||
{
|
||||
name = UUID.randomUUID().toString();
|
||||
// POST request for incoming MMS will fail if the filename is empty
|
||||
name = UUID.randomUUID().toString().substring(0, 8);
|
||||
}
|
||||
|
||||
part.setName(name);
|
||||
@ -90,7 +94,7 @@ public class MmsUtils
|
||||
/*
|
||||
* see com.google.android.mms.pdu.PduPersister.loadAddress
|
||||
*/
|
||||
private String getSenderNumber(long mmsId) {
|
||||
public String getMmsSenderNumber(long mmsId) {
|
||||
|
||||
Uri uri = Uri.parse("content://mms/"+mmsId+"/addr");
|
||||
|
||||
@ -112,44 +116,41 @@ public class MmsUtils
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
public List<IncomingMms> getMessagesInMmsInbox()
|
||||
{
|
||||
return getMessagesInMmsInbox(false);
|
||||
}
|
||||
|
||||
public List<IncomingMms> getMessagesInInbox()
|
||||
public synchronized List<IncomingMms> getMessagesInMmsInbox(boolean newMessagesOnly)
|
||||
{
|
||||
// the M-Retrieve.conf messages are the 'actual' MMS messages
|
||||
String m_type = "" + MESSAGE_TYPE_RETRIEVE_CONF;
|
||||
|
||||
Cursor c = contentResolver.query(INBOX_URI,
|
||||
Cursor c = contentResolver.query(MMS_INBOX_URI,
|
||||
new String[] {"_id", "ct_l", "date"},
|
||||
"m_type = ? ", new String[] { m_type }, null);
|
||||
"m_type = ? ", new String[] { m_type },
|
||||
"_id desc limit 30");
|
||||
|
||||
List<IncomingMms> messages = new ArrayList<IncomingMms>();
|
||||
|
||||
while (c.moveToNext())
|
||||
{
|
||||
long id = c.getLong(0);
|
||||
long date = c.getLong(2);
|
||||
long id = c.getLong(0);
|
||||
|
||||
String from = getSenderNumber(id);
|
||||
|
||||
if (from == null)
|
||||
if (newMessagesOnly && seenMmsIds.contains(id))
|
||||
{
|
||||
app.log("Ignoring MMS "+id+" for now because sender number is null");
|
||||
// avoid fetching all the info for old MMS messages if we're only interested in new ones
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
long date = c.getLong(2);
|
||||
|
||||
IncomingMms mms = new IncomingMms(app,
|
||||
from,
|
||||
date * 1000, // MMS timestamp is in seconds for some reason,
|
||||
// while everything else is in ms
|
||||
id);
|
||||
|
||||
mms.setContentLocation(c.getString(1));
|
||||
|
||||
for (MmsPart part : getMmsParts(id))
|
||||
{
|
||||
mms.addPart(part);
|
||||
}
|
||||
|
||||
messages.add(mms);
|
||||
}
|
||||
c.close();
|
||||
@ -157,9 +158,9 @@ public class MmsUtils
|
||||
return messages;
|
||||
}
|
||||
|
||||
public synchronized boolean deleteFromInbox(IncomingMms mms)
|
||||
public synchronized boolean deleteFromMmsInbox(IncomingMms mms)
|
||||
{
|
||||
long id = mms.getId();
|
||||
long id = mms.getMessagingId();
|
||||
|
||||
Uri uri = Uri.parse("content://mms/inbox/" + id);
|
||||
int res = contentResolver.delete(uri, null, null);
|
||||
@ -181,15 +182,53 @@ public class MmsUtils
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
public synchronized void markOldMms(IncomingMms mms)
|
||||
public synchronized void markSeenMms(IncomingMms mms)
|
||||
{
|
||||
long id = mms.getId();
|
||||
long id = mms.getMessagingId();
|
||||
seenMmsIds.add(id);
|
||||
}
|
||||
|
||||
public synchronized boolean isNewMms(IncomingMms mms)
|
||||
public synchronized List<IncomingSms> getSentSmsMessages()
|
||||
{
|
||||
long id = mms.getId();
|
||||
return !seenMmsIds.contains(id);
|
||||
return getSentSmsMessages(false);
|
||||
}
|
||||
|
||||
public synchronized List<IncomingSms> getSentSmsMessages(boolean newMessagesOnly)
|
||||
{
|
||||
Cursor c = contentResolver.query(SENT_SMS_URI,
|
||||
new String[]{"_id", "address", "body", "date"}, null, null,
|
||||
"_id desc limit 30");
|
||||
|
||||
// SMS messages sent via Messaging app are considered as IncomingSms (with direction=Direction.Sent)
|
||||
// because they're incoming to the server (whereas OutgoingSms would indicate a message we will try to send)
|
||||
List<IncomingSms> messages = new ArrayList<IncomingSms>();
|
||||
|
||||
while (c.moveToNext())
|
||||
{
|
||||
long id = c.getLong(0);
|
||||
|
||||
if (newMessagesOnly && seenSentSmsIds.contains(id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IncomingSms sms = new IncomingSms(app);
|
||||
sms.setMessagingId(id);
|
||||
sms.setTo(c.getString(1));
|
||||
sms.setMessageBody(c.getString(2));
|
||||
sms.setTimestamp(c.getLong(3));
|
||||
sms.setDirection(IncomingSms.Direction.Sent);
|
||||
|
||||
messages.add(sms);
|
||||
}
|
||||
c.close();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public synchronized void markSeenSentSms(IncomingSms sms)
|
||||
{
|
||||
long id = sms.getMessagingId();
|
||||
seenSentSmsIds.add(id);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Handler;
|
||||
import java.util.List;
|
||||
|
||||
final class MmsObserver extends ContentObserver {
|
||||
|
||||
private App app;
|
||||
|
||||
public MmsObserver(App app) {
|
||||
super(new Handler());
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public void register()
|
||||
{
|
||||
app.getContentResolver().registerContentObserver(
|
||||
MmsUtils.OBSERVER_URI, true, this);
|
||||
|
||||
MmsUtils mmsUtils = app.getMmsUtils();
|
||||
|
||||
List<IncomingMms> messages = mmsUtils.getMessagesInInbox();
|
||||
for (IncomingMms mms : messages)
|
||||
{
|
||||
mmsUtils.markOldMms(mms);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister()
|
||||
{
|
||||
app.getContentResolver().unregisterContentObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(final boolean selfChange) {
|
||||
super.onChange(selfChange);
|
||||
if (!selfChange)
|
||||
{
|
||||
// check MMS inbox in an IntentService since it may be slow
|
||||
// and we only want to do one check at a time
|
||||
app.startService(new Intent(app, CheckMmsInboxService.class));
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@ -18,7 +17,6 @@ import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.envaya.sms.receiver.DequeueOutgoingMessageReceiver;
|
||||
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
||||
import org.envaya.sms.task.HttpTask;
|
||||
|
||||
public class Outbox {
|
||||
@ -81,7 +79,7 @@ public class Outbox {
|
||||
else {
|
||||
logMessage = "queued";
|
||||
}
|
||||
String smsDesc = sms.getLogName();
|
||||
String smsDesc = sms.getDisplayType();
|
||||
|
||||
if (serverId != null) {
|
||||
app.log("Notifying server " + smsDesc + " " + logMessage);
|
||||
@ -115,31 +113,33 @@ public class Outbox {
|
||||
return outgoingMessages.get(uri);
|
||||
}
|
||||
|
||||
public synchronized void messageSent(OutgoingMessage sms)
|
||||
public synchronized void messageSent(OutgoingMessage message)
|
||||
{
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Sent);
|
||||
message.setProcessingState(OutgoingMessage.ProcessingState.Sent);
|
||||
|
||||
sms.clearSendTimeout();
|
||||
message.clearSendTimeout();
|
||||
|
||||
notifyMessageStatus(sms, App.STATUS_SENT, "");
|
||||
notifyMessageStatus(message, App.STATUS_SENT, "");
|
||||
|
||||
Uri uri = sms.getUri();
|
||||
Uri uri = message.getUri();
|
||||
|
||||
outgoingMessages.remove(uri);
|
||||
|
||||
addRecentSentMessage(sms);
|
||||
addRecentSentMessage(message);
|
||||
|
||||
notifyChanged();
|
||||
app.getDatabaseHelper().deletePendingMessage(message);
|
||||
|
||||
notifyChanged();
|
||||
|
||||
numSendingOutgoingMessages--;
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
|
||||
private synchronized void addRecentSentMessage(OutgoingMessage sms)
|
||||
private synchronized void addRecentSentMessage(OutgoingMessage message)
|
||||
{
|
||||
if (sms.getServerId() != null)
|
||||
if (message.getServerId() != null)
|
||||
{
|
||||
Uri uri = sms.getUri();
|
||||
Uri uri = message.getUri();
|
||||
|
||||
recentSentMessageUris.add(uri);
|
||||
recentSentMessageUriOrder.add(uri);
|
||||
@ -152,21 +152,23 @@ public class Outbox {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void messageFailed(OutgoingMessage sms, String error)
|
||||
public synchronized void messageFailed(OutgoingMessage message, String error)
|
||||
{
|
||||
sms.clearSendTimeout();
|
||||
message.clearSendTimeout();
|
||||
|
||||
if (sms.scheduleRetry())
|
||||
if (message.scheduleRetry())
|
||||
{
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Scheduled);
|
||||
message.setProcessingState(OutgoingMessage.ProcessingState.Scheduled);
|
||||
}
|
||||
else
|
||||
{
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.None);
|
||||
message.setProcessingState(OutgoingMessage.ProcessingState.None);
|
||||
}
|
||||
notifyChanged();
|
||||
notifyMessageStatus(sms, App.STATUS_FAILED, error);
|
||||
notifyMessageStatus(message, App.STATUS_FAILED, error);
|
||||
|
||||
app.getDatabaseHelper().deletePendingMessage(message);
|
||||
|
||||
numSendingOutgoingMessages--;
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
@ -185,18 +187,21 @@ public class Outbox {
|
||||
|
||||
Uri uri = message.getUri();
|
||||
if (outgoingMessages.containsKey(uri)) {
|
||||
app.debug("Duplicate outgoing " + message.getLogName() + ", skipping");
|
||||
app.debug("Duplicate outgoing " + message.getDisplayType() + ", skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (recentSentMessageUris.contains(uri))
|
||||
{
|
||||
app.debug("Outgoing " + message.getLogName() + " already sent, re-notifying server");
|
||||
app.debug("Outgoing " + message.getDisplayType() + " already sent, re-notifying server");
|
||||
notifyMessageStatus(message, App.STATUS_SENT, "");
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingMessages.put(uri, message);
|
||||
outgoingMessages.put(uri, message);
|
||||
|
||||
app.getDatabaseHelper().insertPendingMessage(message);
|
||||
|
||||
enqueueMessage(message);
|
||||
}
|
||||
|
||||
@ -213,6 +218,8 @@ public class Outbox {
|
||||
numSendingOutgoingMessages--;
|
||||
}
|
||||
|
||||
app.getDatabaseHelper().deletePendingMessage(message);
|
||||
|
||||
notifyMessageStatus(message, App.STATUS_CANCELLED,
|
||||
"deleted by user");
|
||||
app.log(message.getDescription() + " deleted");
|
||||
|
@ -17,7 +17,7 @@ public abstract class OutgoingMessage extends QueuedMessage {
|
||||
private String from;
|
||||
private String to;
|
||||
private int priority;
|
||||
private int localId;
|
||||
private int localId;
|
||||
private static int nextLocalId = 1;
|
||||
|
||||
private ProcessingState state = ProcessingState.None;
|
||||
@ -48,6 +48,11 @@ public abstract class OutgoingMessage extends QueuedMessage {
|
||||
return state;
|
||||
}
|
||||
|
||||
public static OutgoingMessage newFromMessageType(App app, String type)
|
||||
{
|
||||
return new OutgoingSms(app);
|
||||
}
|
||||
|
||||
public void setProcessingState(ProcessingState status)
|
||||
{
|
||||
this.state = status;
|
||||
@ -157,12 +162,11 @@ public abstract class OutgoingMessage extends QueuedMessage {
|
||||
return getDisplayType() + " to " + getTo();
|
||||
}
|
||||
|
||||
abstract String getLogName();
|
||||
|
||||
public void validate() throws ValidationException
|
||||
{
|
||||
}
|
||||
|
||||
abstract String getMessageType();
|
||||
abstract ScheduleInfo scheduleSend();
|
||||
abstract void send(ScheduleInfo schedule);
|
||||
|
||||
|
@ -11,9 +11,9 @@ public class OutgoingSms extends OutgoingMessage {
|
||||
super(app);
|
||||
}
|
||||
|
||||
public String getLogName()
|
||||
public String getMessageType()
|
||||
{
|
||||
return "SMS";
|
||||
return App.MESSAGE_TYPE_SMS;
|
||||
}
|
||||
|
||||
private ArrayList<String> _bodyParts;
|
||||
@ -65,7 +65,7 @@ public class OutgoingSms extends OutgoingMessage {
|
||||
|
||||
if (numRetries == 0)
|
||||
{
|
||||
app.log("Sending " + getDescription());
|
||||
app.log("Sending " + getDisplayType());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -105,9 +105,16 @@ public class OutgoingSms extends OutgoingMessage {
|
||||
|
||||
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");
|
||||
if (app.isTestMode() && app.autoAddTestNumber())
|
||||
{
|
||||
app.addTestPhoneNumber(to);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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();
|
||||
|
@ -14,12 +14,29 @@ public abstract class QueuedMessage
|
||||
protected int numRetries = 0;
|
||||
protected Date dateCreated = new Date();
|
||||
public App app;
|
||||
|
||||
protected long persistedId = 0; // _id of row in pending_incoming_messages or pending_outgoing_messages table (0 if not stored)
|
||||
|
||||
public QueuedMessage(App app)
|
||||
{
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public boolean isPersisted()
|
||||
{
|
||||
return persistedId != 0;
|
||||
}
|
||||
|
||||
public long getPersistedId()
|
||||
{
|
||||
return persistedId;
|
||||
}
|
||||
|
||||
public void setPersistedId(long id)
|
||||
{
|
||||
this.persistedId = id;
|
||||
}
|
||||
|
||||
public Date getDateCreated()
|
||||
{
|
||||
return dateCreated;
|
||||
|
@ -1,18 +1,25 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
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();
|
||||
@ -38,4 +45,53 @@ public class XmlUtils {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<OutgoingMessage> getMessagesList(Document xml, App app, String defaultTo)
|
||||
{
|
||||
List<OutgoingMessage> messages = new ArrayList<OutgoingMessage>();
|
||||
|
||||
Element messagesElement = (Element) xml.getElementsByTagName("messages").item(0);
|
||||
if (messagesElement != null)
|
||||
{
|
||||
NodeList messageNodes = messagesElement.getChildNodes();
|
||||
int numNodes = messageNodes.getLength();
|
||||
for (int i = 0; i < numNodes; i++)
|
||||
{
|
||||
Element messageElement = (Element) messageNodes.item(i);
|
||||
|
||||
String nodeName = messageElement.getNodeName();
|
||||
|
||||
OutgoingMessage message = OutgoingMessage.newFromMessageType(app, nodeName);
|
||||
|
||||
message.setFrom(app.getPhoneNumber());
|
||||
|
||||
String to = messageElement.getAttribute("to");
|
||||
|
||||
message.setTo("".equals(to) ? defaultTo : to);
|
||||
|
||||
String serverId = messageElement.getAttribute("id");
|
||||
|
||||
message.setServerId("".equals(serverId) ? 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);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
|
||||
package org.envaya.sms.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class BootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
// just want to initialize App class to start outgoing message poll timer
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ public class ExpansionPackInstallReceiver extends BroadcastReceiver
|
||||
|
||||
String action = intent.getAction();
|
||||
|
||||
boolean isInstalled = !"android.intent.action.PACKAGE_REMOVED".equals(action);
|
||||
|
||||
String packageName = intent.getData().getSchemeSpecificPart();
|
||||
|
||||
if (packageName != null)
|
||||
|
22
src/org/envaya/sms/receiver/NudgeReceiver.java
Normal file
22
src/org/envaya/sms/receiver/NudgeReceiver.java
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
package org.envaya.sms.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.App;
|
||||
|
||||
public class NudgeReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
// intentional side-effect: initialize App class to start outgoing message poll timer,
|
||||
// and send any pending incoming messages that were persisted to DB before reboot.
|
||||
|
||||
//App app = (App)context.getApplicationContext();
|
||||
|
||||
//app.debug("Nudged by " + intent.getAction());
|
||||
//app.log(".");
|
||||
}
|
||||
}
|
22
src/org/envaya/sms/receiver/StartAmqpConsumer.java
Normal file
22
src/org/envaya/sms/receiver/StartAmqpConsumer.java
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
package org.envaya.sms.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.App;
|
||||
|
||||
public class StartAmqpConsumer extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final App app = (App) context.getApplicationContext();
|
||||
|
||||
if (!app.isEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
app.getAmqpConsumer().startAsync();
|
||||
}
|
||||
}
|
44
src/org/envaya/sms/service/AmqpConsumerService.java
Normal file
44
src/org/envaya/sms/service/AmqpConsumerService.java
Normal file
@ -0,0 +1,44 @@
|
||||
package org.envaya.sms.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.AmqpConsumer;
|
||||
import org.envaya.sms.App;
|
||||
|
||||
public class AmqpConsumerService extends IntentService {
|
||||
|
||||
private App app;
|
||||
|
||||
public AmqpConsumerService(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public AmqpConsumerService()
|
||||
{
|
||||
this("AmqpConsumerService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
app = (App)this.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
boolean start = intent.getBooleanExtra("start", false);
|
||||
|
||||
AmqpConsumer consumer = app.getAmqpConsumer();
|
||||
if (start)
|
||||
{
|
||||
consumer.startBlocking();
|
||||
}
|
||||
else
|
||||
{
|
||||
consumer.stopBlocking();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
34
src/org/envaya/sms/service/AmqpHeartbeatService.java
Normal file
34
src/org/envaya/sms/service/AmqpHeartbeatService.java
Normal file
@ -0,0 +1,34 @@
|
||||
package org.envaya.sms.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.AmqpConsumer;
|
||||
import org.envaya.sms.App;
|
||||
|
||||
public class AmqpHeartbeatService extends IntentService {
|
||||
|
||||
private App app;
|
||||
|
||||
public AmqpHeartbeatService(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public AmqpHeartbeatService()
|
||||
{
|
||||
this("AmqpHeartbeatService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
app = (App)this.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
AmqpConsumer consumer = app.getAmqpConsumer();
|
||||
consumer.sendHeartbeatBlocking();
|
||||
}
|
||||
}
|
91
src/org/envaya/sms/service/CheckMessagingService.java
Normal file
91
src/org/envaya/sms/service/CheckMessagingService.java
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
package org.envaya.sms.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMms;
|
||||
import org.envaya.sms.IncomingSms;
|
||||
import org.envaya.sms.MessagingUtils;
|
||||
import java.util.List;
|
||||
|
||||
public class CheckMessagingService extends IntentService
|
||||
{
|
||||
private App app;
|
||||
private MessagingUtils messagingUtils;
|
||||
|
||||
public CheckMessagingService(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public CheckMessagingService()
|
||||
{
|
||||
this("CheckMessagingService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
app = (App)this.getApplicationContext();
|
||||
|
||||
messagingUtils = app.getMessagingUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
checkNewSentSms();
|
||||
|
||||
checkNewMms();
|
||||
}
|
||||
|
||||
private void checkNewSentSms()
|
||||
{
|
||||
List<IncomingSms> messages = messagingUtils.getSentSmsMessages(true);
|
||||
for (IncomingSms sms : messages)
|
||||
{
|
||||
messagingUtils.markSeenSentSms(sms);
|
||||
|
||||
if (sms.isForwardable())
|
||||
{
|
||||
app.log("SMS id=" + sms.getMessagingId() + " sent via Messaging app");
|
||||
app.inbox.forwardMessage(sms);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Ignoring SMS sent via Messaging app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNewMms()
|
||||
{
|
||||
List<IncomingMms> messages = messagingUtils.getMessagesInMmsInbox(true);
|
||||
for (IncomingMms mms : messages)
|
||||
{
|
||||
String from = mms.getFrom();
|
||||
if (from == null || from.length() == 0)
|
||||
{
|
||||
// sender phone number may not be written to Messaging database yet
|
||||
continue;
|
||||
}
|
||||
|
||||
// prevent forwarding MMS messages that existed in inbox
|
||||
// before EnvayaSMS started, or re-forwarding MMS multiple
|
||||
// times if we don't delete them.
|
||||
messagingUtils.markSeenMms(mms);
|
||||
|
||||
if (mms.isForwardable())
|
||||
{
|
||||
app.log("New MMS id=" + mms.getMessagingId() + " in inbox");
|
||||
app.inbox.forwardMessage(mms);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("Ignoring incoming MMS from " + mms.getFrom());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
src/org/envaya/sms/service/EnabledChangedService.java
Normal file
66
src/org/envaya/sms/service/EnabledChangedService.java
Normal file
@ -0,0 +1,66 @@
|
||||
package org.envaya.sms.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.IntentService;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.receiver.NudgeReceiver;
|
||||
|
||||
public class EnabledChangedService extends IntentService {
|
||||
|
||||
private App app;
|
||||
|
||||
public EnabledChangedService(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public EnabledChangedService()
|
||||
{
|
||||
this("EnabledChangedService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
app = (App)this.getApplicationContext();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
TelephonyManager telephony = (TelephonyManager)
|
||||
getSystemService(Context.TELEPHONY_SERVICE);
|
||||
|
||||
startService(new Intent(app, ForegroundService.class));
|
||||
|
||||
app.setOutgoingMessageAlarm();
|
||||
|
||||
AlarmManager alarmManager = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||
alarmManager.cancel(PendingIntent.getBroadcast(app, 0, new Intent(app, NudgeReceiver.class), 0));
|
||||
|
||||
if (app.isEnabled())
|
||||
{
|
||||
app.getMessagingObserver().register();
|
||||
|
||||
telephony.listen(app.getCallListener(), PhoneStateListener.LISTEN_CALL_STATE);
|
||||
|
||||
app.getDatabaseHelper().restorePendingMessages();
|
||||
|
||||
app.getAmqpConsumer().startAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.getMessagingObserver().unregister();
|
||||
telephony.listen(app.getCallListener(), PhoneStateListener.LISTEN_NONE);
|
||||
|
||||
app.getAmqpConsumer().stopAsync();
|
||||
}
|
||||
}
|
||||
}
|
15
src/org/envaya/sms/ForegroundService.java → src/org/envaya/sms/service/ForegroundService.java
Executable file → Normal file
15
src/org/envaya/sms/ForegroundService.java → src/org/envaya/sms/service/ForegroundService.java
Executable file → Normal file
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.envaya.sms;
|
||||
package org.envaya.sms.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@ -23,10 +23,12 @@ import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import org.envaya.sms.ui.LogView;
|
||||
import org.envaya.sms.ui.Main;
|
||||
|
||||
/*
|
||||
* Service running in foreground to make sure App instance stays
|
||||
@ -159,13 +161,12 @@ public class ForegroundService extends Service {
|
||||
System.currentTimeMillis());
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||
new Intent(this, LogView.class), 0);
|
||||
new Intent(this, Main.class), 0);
|
||||
|
||||
notification.setLatestEventInfo(this,
|
||||
"EnvayaSMS running",
|
||||
text, contentIntent);
|
||||
CharSequence info = getText(R.string.running);
|
||||
notification.setLatestEventInfo(this, info, text, contentIntent);
|
||||
|
||||
startForegroundCompat(R.string.service_started, notification);
|
||||
startForegroundCompat(R.string.service_started, notification);
|
||||
}
|
||||
else
|
||||
{
|
@ -3,6 +3,8 @@ package org.envaya.sms.task;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.JsonUtils;
|
||||
import org.envaya.sms.XmlUtils;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
@ -17,6 +19,9 @@ 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.json.JSONObject;
|
||||
import org.w3c.dom.Document;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
|
||||
@ -34,6 +39,8 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
this.url = url;
|
||||
this.app = app;
|
||||
params = new ArrayList<BasicNameValuePair>(Arrays.asList(paramsArr));
|
||||
|
||||
params.add(new BasicNameValuePair("version", "" + app.getPackageInfo().versionCode));
|
||||
}
|
||||
|
||||
public void addParam(String name, String value)
|
||||
@ -51,7 +58,7 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
{
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
|
||||
httpPost.setHeader("User-Agent", "EnvayaSMS/" + app.getPackageInfo().versionName + " (Android; SDK "+Build.VERSION.SDK_INT + "; " + Build.MANUFACTURER + "; " + Build.MODEL+")");
|
||||
httpPost.setHeader("User-Agent", app.getText(R.string.app_name) + "/" + app.getPackageInfo().versionName + " (Android; SDK "+Build.VERSION.SDK_INT + "; " + Build.MANUFACTURER + "; " + Build.MODEL+")");
|
||||
|
||||
if (useMultipartPost)
|
||||
{
|
||||
@ -83,6 +90,7 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
try
|
||||
{
|
||||
post = makeHttpPost();
|
||||
|
||||
HttpClient client = app.getHttpClient();
|
||||
return client.execute(post);
|
||||
}
|
||||
@ -97,9 +105,10 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
if ((ex instanceof IOException)
|
||||
&& message != null && message.equals("Connection already shutdown"))
|
||||
{
|
||||
//app.log("Retrying request");
|
||||
// app.log("Retrying request");
|
||||
post = makeHttpPost();
|
||||
HttpClient client = app.getHttpClient();
|
||||
|
||||
return client.execute(post);
|
||||
}
|
||||
}
|
||||
@ -111,10 +120,35 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isValidContentType(String contentType)
|
||||
|
||||
protected String getErrorText(HttpResponse response)
|
||||
throws Exception
|
||||
{
|
||||
return true; // contentType.startsWith("text/xml");
|
||||
String contentType = getContentType(response);
|
||||
String error = null;
|
||||
|
||||
if (contentType.startsWith("application/json"))
|
||||
{
|
||||
JSONObject json = JsonUtils.parseResponse(response);
|
||||
error = JsonUtils.getErrorText(json);
|
||||
}
|
||||
else if (contentType.startsWith("text/xml"))
|
||||
{
|
||||
Document xml = XmlUtils.parseResponse(response);
|
||||
error = XmlUtils.getErrorText(xml);
|
||||
}
|
||||
|
||||
if (error == null)
|
||||
{
|
||||
error = "HTTP " + response.getStatusLine().getStatusCode();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
protected String getContentType(HttpResponse response)
|
||||
{
|
||||
Header contentTypeHeader = response.getFirstHeader("Content-Type");
|
||||
return (contentTypeHeader != null) ? contentTypeHeader.getValue() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -123,24 +157,13 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
{
|
||||
try
|
||||
{
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
Header contentTypeHeader = response.getFirstHeader("Content-Type");
|
||||
String contentType = (contentTypeHeader != null) ? contentTypeHeader.getValue() : "";
|
||||
|
||||
boolean validContentType = isValidContentType(contentType);
|
||||
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
|
||||
if (statusCode == 200)
|
||||
{
|
||||
if (validContentType)
|
||||
{
|
||||
handleResponse(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Invalid response type " + contentType);
|
||||
}
|
||||
handleResponse(response);
|
||||
}
|
||||
else if (statusCode >= 400 && statusCode <= 499 && validContentType)
|
||||
else if (statusCode >= 400 && statusCode <= 499)
|
||||
{
|
||||
handleErrorResponse(response);
|
||||
handleFailure();
|
||||
@ -174,11 +197,6 @@ public class BaseHttpTask extends AsyncTask<String, Void, HttpResponse> {
|
||||
|
||||
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
|
||||
|
@ -31,11 +31,17 @@ public class CheckConnectivityTask extends AsyncTask<String, Void, Boolean> {
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1000);
|
||||
|
||||
InetAddress addr = InetAddress.getByName(hostName);
|
||||
if (addr.isReachable(App.HTTP_CONNECTION_TIMEOUT))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex)
|
||||
{
|
||||
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
|
@ -3,7 +3,6 @@ package org.envaya.sms.task;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.OutgoingMessage;
|
||||
|
||||
public class ForwarderTask extends HttpTask {
|
||||
|
||||
@ -14,12 +13,6 @@ public class ForwarderTask extends HttpTask {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidContentType(String contentType)
|
||||
{
|
||||
return contentType.startsWith("text/xml");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultToAddress() {
|
||||
return message.getFrom();
|
||||
@ -27,17 +20,12 @@ public class ForwarderTask extends HttpTask {
|
||||
|
||||
@Override
|
||||
protected void handleResponse(HttpResponse response) throws Exception {
|
||||
|
||||
for (OutgoingMessage reply : parseResponseXML(response)) {
|
||||
app.outbox.sendMessage(reply);
|
||||
}
|
||||
app.inbox.messageForwarded(message);
|
||||
|
||||
app.inbox.messageForwarded(message);
|
||||
super.handleResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFailure() {
|
||||
app.inbox.messageFailed(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,32 +4,31 @@
|
||||
*/
|
||||
package org.envaya.sms.task;
|
||||
|
||||
import org.envaya.sms.XmlUtils;
|
||||
import org.envaya.sms.OutgoingSms;
|
||||
import org.envaya.sms.OutgoingMessage;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.JsonUtils;
|
||||
import org.envaya.sms.Base64Coder;
|
||||
import android.content.SharedPreferences;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.XmlUtils;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.preference.PreferenceManager;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class HttpTask extends BaseHttpTask {
|
||||
@ -97,11 +96,37 @@ public class HttpTask extends BaseHttpTask {
|
||||
return null;
|
||||
}
|
||||
|
||||
logEntries = app.getNewLogEntries();
|
||||
logEntries = app.getNewLogEntries();
|
||||
|
||||
params.add(new BasicNameValuePair("version", "" + app.getPackageInfo().versionCode));
|
||||
params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber()));
|
||||
params.add(new BasicNameValuePair("phone_id", app.getPhoneID()));
|
||||
params.add(new BasicNameValuePair("phone_token", app.getPhoneToken()));
|
||||
params.add(new BasicNameValuePair("send_limit", "" + app.getOutgoingMessageLimit()));
|
||||
params.add(new BasicNameValuePair("now", "" + System.currentTimeMillis()));
|
||||
params.add(new BasicNameValuePair("settings_version", "" + app.getSettingsVersion()));
|
||||
|
||||
Intent lastBatteryIntent = app.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
|
||||
if (lastBatteryIntent != null)
|
||||
{
|
||||
// BatteryManager.EXTRA_* constants introduced in API level 5 (2.0)
|
||||
int rawLevel = lastBatteryIntent.getIntExtra("level", -1);
|
||||
int scale = lastBatteryIntent.getIntExtra("scale", -1);
|
||||
|
||||
int pctLevel = (rawLevel > 0 && scale > 0) ? (rawLevel * 100 / scale) : rawLevel;
|
||||
|
||||
if (pctLevel >= 0)
|
||||
{
|
||||
params.add(new BasicNameValuePair("battery", "" + pctLevel));
|
||||
}
|
||||
|
||||
int plugged = lastBatteryIntent.getIntExtra("plugged", -1);
|
||||
|
||||
if (plugged >= 0)
|
||||
{
|
||||
params.add(new BasicNameValuePair("power", "" + plugged));
|
||||
}
|
||||
}
|
||||
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager)app.getSystemService(App.CONNECTIVITY_SERVICE);
|
||||
@ -133,56 +158,29 @@ public class HttpTask extends BaseHttpTask {
|
||||
protected String getDefaultToAddress()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
protected void handleResponseJSON(JSONObject json)
|
||||
throws JSONException
|
||||
{
|
||||
JsonUtils.processEvents(json, app, getDefaultToAddress());
|
||||
}
|
||||
|
||||
protected List<OutgoingMessage> parseResponseXML(HttpResponse response)
|
||||
protected void handleResponseXML(Document xml)
|
||||
throws IOException, ParserConfigurationException, SAXException
|
||||
{
|
||||
List<OutgoingMessage> messages = new ArrayList<OutgoingMessage>();
|
||||
Document xml = XmlUtils.parseResponse(response);
|
||||
|
||||
Element messagesElement = (Element) xml.getElementsByTagName("messages").item(0);
|
||||
if (messagesElement != null)
|
||||
for (OutgoingMessage message : XmlUtils.getMessagesList(xml, app, getDefaultToAddress()))
|
||||
{
|
||||
NodeList messageNodes = messagesElement.getChildNodes();
|
||||
int numNodes = messageNodes.getLength();
|
||||
for (int i = 0; i < numNodes; i++)
|
||||
{
|
||||
Element messageElement = (Element) messageNodes.item(i);
|
||||
|
||||
OutgoingMessage message = new OutgoingSms(app);
|
||||
|
||||
message.setFrom(app.getPhoneNumber());
|
||||
|
||||
String to = messageElement.getAttribute("to");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
app.outbox.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleUnknownContentType(String contentType)
|
||||
throws Exception
|
||||
{
|
||||
// old server API only mandated valid content type for action=outgoing
|
||||
app.log("Warning: Unknown response type " + contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFailure()
|
||||
@ -221,16 +219,38 @@ public class HttpTask extends BaseHttpTask {
|
||||
|
||||
@Override
|
||||
public void handleErrorResponse(HttpResponse response) throws Exception
|
||||
{
|
||||
Document xml = XmlUtils.parseResponse(response);
|
||||
String error = XmlUtils.getErrorText(xml);
|
||||
if (error != null)
|
||||
{
|
||||
app.log(getErrorText(response));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponse(HttpResponse response) throws Exception {
|
||||
|
||||
String contentType = getContentType(response);
|
||||
|
||||
if (contentType.startsWith("application/json"))
|
||||
{
|
||||
app.log(error);
|
||||
}
|
||||
String responseBody = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
|
||||
|
||||
JSONObject json = new JSONObject(responseBody);
|
||||
|
||||
handleResponseJSON(json);
|
||||
}
|
||||
else if (contentType.startsWith("text/xml"))
|
||||
{
|
||||
Document xml = XmlUtils.parseResponse(response);
|
||||
|
||||
handleResponseXML(xml);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.log("HTTP " +response.getStatusLine().getStatusCode());
|
||||
handleUnknownContentType(contentType);
|
||||
}
|
||||
|
||||
// if we get a valid server response after a connectivity error, then forward any pending messages
|
||||
if (app.hasConnectivityError())
|
||||
{
|
||||
app.onConnectivityRestored();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ package org.envaya.sms.task;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.OutgoingMessage;
|
||||
|
||||
public class PollerTask extends HttpTask {
|
||||
|
||||
@ -12,12 +11,6 @@ public class PollerTask extends HttpTask {
|
||||
super(app, new BasicNameValuePair("action", App.ACTION_OUTGOING));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidContentType(String contentType)
|
||||
{
|
||||
return contentType.startsWith("text/xml");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(HttpResponse response) {
|
||||
super.onPostExecute(response);
|
||||
@ -25,9 +18,9 @@ public class PollerTask extends HttpTask {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponse(HttpResponse response) throws Exception {
|
||||
for (OutgoingMessage reply : parseResponseXML(response)) {
|
||||
app.outbox.sendMessage(reply);
|
||||
}
|
||||
protected void handleUnknownContentType(String contentType)
|
||||
throws Exception
|
||||
{
|
||||
throw new Exception("Invalid response type " + contentType);
|
||||
}
|
||||
}
|
81
src/org/envaya/sms/ui/ExpansionPacks.java
Normal file
81
src/org/envaya/sms/ui/ExpansionPacks.java
Normal file
@ -0,0 +1,81 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.view.Menu;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
public class ExpansionPacks extends PreferenceActivity {
|
||||
|
||||
private App app;
|
||||
|
||||
private BroadcastReceiver installReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateInstallStatus();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.expansion_packs);
|
||||
|
||||
app = (App) getApplication();
|
||||
|
||||
IntentFilter installReceiverFilter = new IntentFilter();
|
||||
installReceiverFilter.addAction(App.EXPANSION_PACKS_CHANGED_INTENT);
|
||||
registerReceiver(installReceiver, installReceiverFilter);
|
||||
|
||||
updateInstallStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
unregisterReceiver(installReceiver);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public void updateInstallStatus()
|
||||
{
|
||||
PreferenceScreen screen = this.getPreferenceScreen();
|
||||
int numPrefs = screen.getPreferenceCount();
|
||||
|
||||
String basePackageName = app.getPackageName();
|
||||
|
||||
this.setTitle("Telerivet : SMS Rate Limit ("+app.getOutgoingMessageLimit()+")");
|
||||
|
||||
for(int i=0; i < numPrefs;i++)
|
||||
{
|
||||
Preference p = screen.getPreference(i);
|
||||
|
||||
String packageNum = p.getKey();
|
||||
|
||||
String packageName = basePackageName + "." + packageNum;
|
||||
|
||||
if (app.isSmsExpansionPackInstalled(packageName))
|
||||
{
|
||||
p.setSummary("Installed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
p.setSummary("Not installed.\nInstall to increase limit by 100 SMS/hour...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
this.finish();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public class Help extends Activity {
|
||||
|
||||
app = (App)getApplication();
|
||||
|
||||
String html = "<b>EnvayaSMS " + app.getPackageInfo().versionName + "</b><br /><br />"
|
||||
String html = "<b>"+getText(R.string.app_name)+" " + app.getPackageInfo().versionName + "</b><br /><br />"
|
||||
+ "Menu icons cc/by www.androidicons.com<br /><br />";
|
||||
|
||||
help.setText(Html.fromHtml(html));
|
||||
|
@ -2,21 +2,28 @@ package org.envaya.sms.ui;
|
||||
|
||||
import org.envaya.sms.task.HttpTask;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.*;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LogView extends Activity {
|
||||
|
||||
@ -29,8 +36,25 @@ public class LogView extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
private BroadcastReceiver settingsReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateUpgradeButton();
|
||||
updateInfo();
|
||||
}
|
||||
};
|
||||
|
||||
private BroadcastReceiver expansionPacksReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateInfo();
|
||||
}
|
||||
};
|
||||
|
||||
private ScrollView scrollView;
|
||||
private TextView info;
|
||||
private TextView log;
|
||||
private TextView heading;
|
||||
|
||||
private class TestTask extends HttpTask
|
||||
{
|
||||
@ -47,14 +71,31 @@ public class LogView extends Activity {
|
||||
|
||||
private int lastLogEpoch = -1;
|
||||
|
||||
public void updateLogView()
|
||||
public void updateUpgradeButton()
|
||||
{
|
||||
Button upgradeButton = (Button) this.findViewById(R.id.upgrade_button);
|
||||
boolean isUpgradeAvailable = app.isUpgradeAvailable();
|
||||
if (isUpgradeAvailable)
|
||||
{
|
||||
upgradeButton.setText("New version of app available ("+app.getMarketVersionName()+").\nClick to install...");
|
||||
upgradeButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
upgradeButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized void updateLogView()
|
||||
{
|
||||
int logEpoch = app.getLogEpoch();
|
||||
CharSequence displayedLog = app.getDisplayedLog();
|
||||
int logEpoch2 = app.getLogEpoch();
|
||||
|
||||
if (lastLogEpoch == logEpoch)
|
||||
if (lastLogEpoch == logEpoch && logEpoch == logEpoch2)
|
||||
{
|
||||
int beforeLen = info.getText().length();
|
||||
int beforeLen = log.getText().length();
|
||||
int afterLen = displayedLog.length();
|
||||
|
||||
if (beforeLen == afterLen)
|
||||
@ -62,11 +103,11 @@ public class LogView extends Activity {
|
||||
return;
|
||||
}
|
||||
|
||||
info.append(displayedLog, beforeLen, afterLen);
|
||||
log.append(displayedLog, beforeLen, afterLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.setText(displayedLog);
|
||||
log.setText(displayedLog);
|
||||
lastLogEpoch = logEpoch;
|
||||
}
|
||||
|
||||
@ -85,24 +126,227 @@ public class LogView extends Activity {
|
||||
setContentView(R.layout.log_view);
|
||||
PreferenceManager.setDefaultValues(this, R.xml.prefs, false);
|
||||
|
||||
scrollView = (ScrollView) this.findViewById(R.id.info_scroll);
|
||||
info = (TextView) this.findViewById(R.id.info);
|
||||
scrollView = (ScrollView) this.findViewById(R.id.log_scroll);
|
||||
|
||||
info.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
heading = (TextView) this.findViewById(R.id.heading);
|
||||
info = (TextView) this.findViewById(R.id.info);
|
||||
|
||||
//info.setMovementMethod(new ScrollingMovementMethod());
|
||||
updateInfo();
|
||||
|
||||
log = (TextView) this.findViewById(R.id.log);
|
||||
|
||||
log.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
updateUpgradeButton();
|
||||
updateLogView();
|
||||
|
||||
IntentFilter logReceiverFilter = new IntentFilter();
|
||||
logReceiverFilter.addAction(App.LOG_CHANGED_INTENT);
|
||||
registerReceiver(logReceiver, logReceiverFilter);
|
||||
registerReceiver(logReceiver, new IntentFilter(App.LOG_CHANGED_INTENT));
|
||||
registerReceiver(settingsReceiver, new IntentFilter(App.SETTINGS_CHANGED_INTENT));
|
||||
registerReceiver(expansionPacksReceiver, new IntentFilter(App.EXPANSION_PACKS_CHANGED_INTENT));
|
||||
|
||||
if (savedInstanceState == null)
|
||||
{
|
||||
if (getIntent().getBooleanExtra("configured", false))
|
||||
{
|
||||
showConfigureSuccessDialog();
|
||||
}
|
||||
else if (app.isUpgradeAvailable())
|
||||
{
|
||||
showUpgradeDialog();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
curDialog = savedInstanceState.getInt("cur_dialog", 0);
|
||||
if (curDialog == UPGRADE_DIALOG)
|
||||
{
|
||||
showUpgradeDialog();
|
||||
}
|
||||
else if (curDialog == CONFIGURE_SUCCESS_DIALOG)
|
||||
{
|
||||
showConfigureSuccessDialog();
|
||||
}
|
||||
else if (curDialog == SETTINGS_DIALOG)
|
||||
{
|
||||
showSettingsDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final int NO_DIALOG = 0;
|
||||
public static final int UPGRADE_DIALOG = 1;
|
||||
public static final int CONFIGURE_SUCCESS_DIALOG = 2;
|
||||
public static final int SETTINGS_DIALOG = 3;
|
||||
|
||||
private int curDialog = NO_DIALOG;
|
||||
|
||||
public void updateInfo()
|
||||
{
|
||||
boolean enabled = app.isEnabled();
|
||||
|
||||
heading.setText(Html.fromHtml(
|
||||
enabled ? "<b>" + getText(R.string.running) + " ("+app.getPhoneNumber()+")</b>"
|
||||
: "<b>" +getText(R.string.disabled) + "</b>"));
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
info.setText("New messages will be forwarded to server");
|
||||
|
||||
if (app.isTestMode())
|
||||
{
|
||||
info.append("\n(Test mode enabled)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info.setText("New messages will not be forwarded to server");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void infoClicked(View v)
|
||||
{
|
||||
startActivity(new Intent(this, Prefs.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putInt("cur_dialog", curDialog);
|
||||
}
|
||||
|
||||
public void showUpgradeDialog()
|
||||
{
|
||||
curDialog = UPGRADE_DIALOG;
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Upgrade available")
|
||||
.setMessage("A new version of the app is available ("+app.getMarketVersionName()+"). Do you want to upgrade now?")
|
||||
.setPositiveButton("OK", new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
upgradeClicked(null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Not Now", new DismissDialogListener())
|
||||
.setOnCancelListener(new DismissDialogListener())
|
||||
.setCancelable(true)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void showConfigureSuccessDialog()
|
||||
{
|
||||
curDialog = CONFIGURE_SUCCESS_DIALOG;
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("App configured successfully!")
|
||||
.setMessage("Now try using another mobile phone to send a SMS to this phone.\n\nYou should be able to see the message on your Messages page on telerivet.com, and send replies.")
|
||||
.setPositiveButton("OK", new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
showSettingsDialog();
|
||||
}
|
||||
})
|
||||
.setOnCancelListener(new DismissDialogListener())
|
||||
.setCancelable(true)
|
||||
.show();
|
||||
}
|
||||
|
||||
public String getSettingsSummary()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (app.getKeepInInbox())
|
||||
{
|
||||
builder.append("- New messages kept in Messaging inbox\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.append("- New messages not kept in Messaging inbox\n");
|
||||
}
|
||||
|
||||
if (app.callNotificationsEnabled())
|
||||
{
|
||||
builder.append("- Call notifications enabled\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.append("- Call notifications disabled\n");
|
||||
}
|
||||
|
||||
List<String> ignoredNumbers = app.getIgnoredPhoneNumbers();
|
||||
boolean ignoreShortcodes = app.ignoreShortcodes();
|
||||
boolean ignoreNonNumeric = app.ignoreNonNumeric();
|
||||
boolean testMode = app.isTestMode();
|
||||
|
||||
builder.append("- Send up to " + app.getOutgoingMessageLimit()+ " SMS/hour\n");
|
||||
|
||||
if (ignoredNumbers.isEmpty() && !ignoreShortcodes && !ignoreNonNumeric && !testMode)
|
||||
{
|
||||
builder.append("- Forward messages from all phone numbers");
|
||||
}
|
||||
else if (testMode)
|
||||
{
|
||||
builder.append("- Forward messages only from certain phone numbers");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.append("- Ignore messages from some phone numbers");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void showSettingsDialog()
|
||||
{
|
||||
curDialog = SETTINGS_DIALOG;
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Verify Settings")
|
||||
.setMessage(getSettingsSummary())
|
||||
.setPositiveButton("OK", new DismissDialogListener())
|
||||
.setNegativeButton("Change", new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
curDialog = NO_DIALOG;
|
||||
startActivity(new Intent(LogView.this, Prefs.class));
|
||||
}
|
||||
})
|
||||
.setOnCancelListener(new DismissDialogListener())
|
||||
.setCancelable(true)
|
||||
.show();
|
||||
}
|
||||
|
||||
public class DismissDialogListener implements OnClickListener, OnCancelListener
|
||||
{
|
||||
public void onCancel(DialogInterface dialog)
|
||||
{
|
||||
curDialog = NO_DIALOG;
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
curDialog = NO_DIALOG;
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public void upgradeClicked(View v)
|
||||
{
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=" + app.getPackageInfo().applicationInfo.packageName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
this.unregisterReceiver(logReceiver);
|
||||
unregisterReceiver(logReceiver);
|
||||
unregisterReceiver(settingsReceiver);
|
||||
unregisterReceiver(expansionPacksReceiver);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -119,8 +363,8 @@ public class LogView extends Activity {
|
||||
case R.id.retry_now:
|
||||
app.retryStuckMessages();
|
||||
return true;
|
||||
case R.id.forward_inbox:
|
||||
startActivity(new Intent(this, MessagingInbox.class));
|
||||
case R.id.forward_saved:
|
||||
startActivity(new Intent(this, MessagingSmsInbox.class));
|
||||
return true;
|
||||
case R.id.pending:
|
||||
startActivity(new Intent(this, PendingMessages.class));
|
||||
|
23
src/org/envaya/sms/ui/Main.java
Normal file
23
src/org/envaya/sms/ui/Main.java
Normal file
@ -0,0 +1,23 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import org.envaya.sms.ui.Prefs;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.ui.LogView;
|
||||
|
||||
public class Main extends Activity {
|
||||
|
||||
private App app;
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
app = (App)getApplication();
|
||||
|
||||
startActivity(new Intent(this, LogView.class));
|
||||
}
|
||||
}
|
115
src/org/envaya/sms/ui/MessagingInbox.java → src/org/envaya/sms/ui/MessagingForwarder.java
Executable file → Normal file
115
src/org/envaya/sms/ui/MessagingInbox.java → src/org/envaya/sms/ui/MessagingForwarder.java
Executable file → Normal file
@ -5,29 +5,28 @@ import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.*;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import android.widget.Toast;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.IncomingSms;
|
||||
import org.envaya.sms.R;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class MessagingInbox extends ListActivity {
|
||||
public abstract class MessagingForwarder extends ListActivity {
|
||||
|
||||
private App app;
|
||||
protected App app;
|
||||
|
||||
private Cursor cur;
|
||||
abstract int getMessageCount();
|
||||
abstract IncomingMessage getMessageAtPosition(int position);
|
||||
abstract void initListAdapter();
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
@ -37,24 +36,59 @@ public class MessagingInbox extends ListActivity {
|
||||
app = (App) getApplication();
|
||||
|
||||
setContentView(R.layout.inbox);
|
||||
|
||||
final String[] inboxTypeClasses = new String[] {
|
||||
"org.envaya.sms.ui.MessagingSmsInbox",
|
||||
"org.envaya.sms.ui.MessagingMmsInbox",
|
||||
"org.envaya.sms.ui.MessagingSentSms",
|
||||
};
|
||||
|
||||
final String[] inboxTypeNames = new String[] {
|
||||
"SMS Inbox",
|
||||
"MMS Inbox",
|
||||
"Sent SMS"
|
||||
};
|
||||
|
||||
// undocumented API; see
|
||||
// core/java/android/provider/Telephony.java
|
||||
Spinner spinner = (Spinner) findViewById(R.id.inbox_selector);
|
||||
|
||||
Uri inboxUri = Uri.parse("content://sms/inbox");
|
||||
ArrayAdapter<String> inboxTypeAdapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, inboxTypeNames);
|
||||
inboxTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(inboxTypeAdapter);
|
||||
|
||||
cur = getContentResolver().query(inboxUri,
|
||||
new String[] { "_id", "address", "body", "date" }, null, null,
|
||||
"_id desc limit 50");
|
||||
final String className = this.getClass().getCanonicalName();
|
||||
int classIndex = Arrays.asList(inboxTypeClasses).indexOf(className);
|
||||
if (classIndex != -1)
|
||||
{
|
||||
spinner.setSelection(classIndex);
|
||||
}
|
||||
|
||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
|
||||
R.layout.inbox_item,
|
||||
cur,
|
||||
new String[] {"address","body"},
|
||||
new int[] {R.id.inbox_address, R.id.inbox_body});
|
||||
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
public void onItemSelected(AdapterView<?> parent,
|
||||
View view, int pos, long id) {
|
||||
String cls = inboxTypeClasses[pos];
|
||||
|
||||
if (!className.equals(cls))
|
||||
{
|
||||
try
|
||||
{
|
||||
finish();
|
||||
startActivity(new Intent(app, Class.forName(cls)));
|
||||
}
|
||||
catch (ClassNotFoundException ex)
|
||||
{
|
||||
app.logError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onNothingSelected(AdapterView parent) {
|
||||
}
|
||||
});
|
||||
|
||||
setListAdapter(adapter);
|
||||
|
||||
initListAdapter();
|
||||
|
||||
ListView listView = getListView();
|
||||
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@ -63,9 +97,9 @@ public class MessagingInbox extends ListActivity {
|
||||
{
|
||||
final IncomingMessage message = getMessageAtPosition(position);
|
||||
|
||||
final CharSequence[] options = {"Forward", "Cancel"};
|
||||
final CharSequence[] options = {"Forward to server", "Cancel"};
|
||||
|
||||
new AlertDialog.Builder(MessagingInbox.this)
|
||||
new AlertDialog.Builder(MessagingForwarder.this)
|
||||
.setTitle(message.getDescription())
|
||||
.setItems(options, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
@ -73,44 +107,29 @@ public class MessagingInbox extends ListActivity {
|
||||
if (which == 0)
|
||||
{
|
||||
app.inbox.forwardMessage(message);
|
||||
showToast("Forwarding " + message.getDescription());
|
||||
showToast("Forwarding " + message.getDescription() + " to server");
|
||||
}
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void showToast(String text)
|
||||
{
|
||||
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public IncomingMessage getMessageAtPosition(int position)
|
||||
{
|
||||
int addressIndex = cur.getColumnIndex("address");
|
||||
int bodyIndex = cur.getColumnIndex("body");
|
||||
int dateIndex = cur.getColumnIndex("date");
|
||||
|
||||
cur.moveToPosition(position);
|
||||
|
||||
String address = cur.getString(addressIndex);
|
||||
String body = cur.getString(bodyIndex);
|
||||
long date = cur.getLong(dateIndex);
|
||||
|
||||
return new IncomingSms(app, address, body, date);
|
||||
}
|
||||
}
|
||||
|
||||
public void forwardAllClicked() {
|
||||
final int count = cur.getCount();
|
||||
final int count = getMessageCount();
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
app.inbox.forwardMessage(getMessageAtPosition(i));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,9 +156,9 @@ public class MessagingInbox extends ListActivity {
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem forwardItem = menu.findItem(R.id.forward_all);
|
||||
|
||||
int numMessages = cur.getCount();
|
||||
int numMessages = getMessageCount();
|
||||
forwardItem.setEnabled(numMessages > 0);
|
||||
forwardItem.setTitle("Forward All (" + numMessages + ")");
|
||||
forwardItem.setTitle("Forward all to server (" + numMessages + ")");
|
||||
|
||||
return true;
|
||||
}
|
67
src/org/envaya/sms/ui/MessagingMmsInbox.java
Normal file
67
src/org/envaya/sms/ui/MessagingMmsInbox.java
Normal file
@ -0,0 +1,67 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import org.envaya.sms.IncomingMms;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import org.envaya.sms.R;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class MessagingMmsInbox extends MessagingForwarder
|
||||
{
|
||||
private List<IncomingMms> messages;
|
||||
|
||||
public IncomingMessage getMessageAtPosition(int position)
|
||||
{
|
||||
return messages.get(position);
|
||||
}
|
||||
|
||||
public int getMessageCount()
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
public void initListAdapter() {
|
||||
// undocumented API; see
|
||||
// core/java/android/provider/Telephony.java
|
||||
|
||||
messages = app.getMessagingUtils().getMessagesInMmsInbox();
|
||||
|
||||
final LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final DateFormat dateFormat = new SimpleDateFormat("dd MMM HH:mm:ss");
|
||||
|
||||
ArrayAdapter<IncomingMms> arrayAdapter = new ArrayAdapter<IncomingMms>(this,
|
||||
R.layout.inbox_item,
|
||||
messages.toArray(new IncomingMms[]{})) {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = convertView;
|
||||
if (v == null) {
|
||||
v = inflater.inflate(R.layout.inbox_item, null);
|
||||
}
|
||||
IncomingMms mms = messages.get(position);
|
||||
if (mms == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TextView addrText = (TextView) v.findViewById(R.id.inbox_address);
|
||||
TextView bodyText = (TextView) v.findViewById(R.id.inbox_body);
|
||||
|
||||
addrText.setText(mms.getFrom() + " (" + dateFormat.format(new Date(mms.getTimestamp())) + ")");
|
||||
bodyText.setText(mms.getMessageBody());
|
||||
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
setListAdapter(arrayAdapter);
|
||||
}
|
||||
}
|
79
src/org/envaya/sms/ui/MessagingSentSms.java
Normal file
79
src/org/envaya/sms/ui/MessagingSentSms.java
Normal file
@ -0,0 +1,79 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.IncomingSms;
|
||||
import org.envaya.sms.R;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class MessagingSentSms extends MessagingForwarder {
|
||||
|
||||
private Cursor cur;
|
||||
|
||||
public IncomingMessage getMessageAtPosition(int position)
|
||||
{
|
||||
int addressIndex = cur.getColumnIndex("address");
|
||||
int bodyIndex = cur.getColumnIndex("body");
|
||||
int dateIndex = cur.getColumnIndex("date");
|
||||
|
||||
cur.moveToPosition(position);
|
||||
|
||||
IncomingSms sms = new IncomingSms(app);
|
||||
|
||||
sms.setDirection(IncomingMessage.Direction.Sent);
|
||||
sms.setTo(cur.getString(addressIndex));
|
||||
sms.setTimestamp(cur.getLong(dateIndex));
|
||||
sms.setMessageBody(cur.getString(bodyIndex));
|
||||
|
||||
return sms;
|
||||
}
|
||||
|
||||
public int getMessageCount()
|
||||
{
|
||||
return cur.getCount();
|
||||
}
|
||||
|
||||
public void initListAdapter() {
|
||||
// undocumented API; see
|
||||
// core/java/android/provider/Telephony.java
|
||||
|
||||
Uri inboxUri = Uri.parse("content://sms/sent");
|
||||
|
||||
cur = getContentResolver().query(inboxUri,
|
||||
new String[]{"_id", "address", "body", "date"}, null, null,
|
||||
"_id desc limit 50");
|
||||
|
||||
final LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final DateFormat dateFormat = new SimpleDateFormat("dd MMM HH:mm:ss");
|
||||
|
||||
CursorAdapter adapter = new CursorAdapter(this, cur) {
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return inflater.inflate(R.layout.inbox_item, null);
|
||||
}
|
||||
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
TextView addrText = (TextView) view.findViewById(R.id.inbox_address);
|
||||
TextView bodyText = (TextView) view.findViewById(R.id.inbox_body);
|
||||
|
||||
String address = cursor.getString(1);
|
||||
String body = cursor.getString(2);
|
||||
long date = cursor.getLong(3);
|
||||
|
||||
addrText.setText(address + " (" + dateFormat.format(new Date(date)) + ")");
|
||||
bodyText.setText(body);
|
||||
}
|
||||
};
|
||||
|
||||
setListAdapter(adapter);
|
||||
}
|
||||
}
|
78
src/org/envaya/sms/ui/MessagingSmsInbox.java
Normal file
78
src/org/envaya/sms/ui/MessagingSmsInbox.java
Normal file
@ -0,0 +1,78 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.IncomingSms;
|
||||
import org.envaya.sms.R;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class MessagingSmsInbox extends MessagingForwarder {
|
||||
|
||||
private Cursor cur;
|
||||
|
||||
public IncomingMessage getMessageAtPosition(int position)
|
||||
{
|
||||
int addressIndex = cur.getColumnIndex("address");
|
||||
int bodyIndex = cur.getColumnIndex("body");
|
||||
int dateIndex = cur.getColumnIndex("date");
|
||||
|
||||
cur.moveToPosition(position);
|
||||
|
||||
IncomingSms sms = new IncomingSms(app);
|
||||
|
||||
sms.setFrom(cur.getString(addressIndex));
|
||||
sms.setTimestamp(cur.getLong(dateIndex));
|
||||
sms.setMessageBody(cur.getString(bodyIndex));
|
||||
|
||||
return sms;
|
||||
}
|
||||
|
||||
public int getMessageCount()
|
||||
{
|
||||
return cur.getCount();
|
||||
}
|
||||
|
||||
public void initListAdapter() {
|
||||
// undocumented API; see
|
||||
// core/java/android/provider/Telephony.java
|
||||
|
||||
Uri inboxUri = Uri.parse("content://sms/inbox");
|
||||
|
||||
cur = getContentResolver().query(inboxUri,
|
||||
new String[]{"_id", "address", "body", "date"}, null, null,
|
||||
"_id desc limit 50");
|
||||
|
||||
final LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final DateFormat dateFormat = new SimpleDateFormat("dd MMM HH:mm:ss");
|
||||
|
||||
CursorAdapter adapter = new CursorAdapter(this, cur) {
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return inflater.inflate(R.layout.inbox_item, null);
|
||||
}
|
||||
|
||||
public void bindView(View view, Context context, Cursor cursor)
|
||||
{
|
||||
TextView addrText = (TextView) view.findViewById(R.id.inbox_address);
|
||||
TextView bodyText = (TextView) view.findViewById(R.id.inbox_body);
|
||||
|
||||
String address = cursor.getString(1);
|
||||
String body = cursor.getString(2);
|
||||
long date = cursor.getLong(3);
|
||||
|
||||
addrText.setText(address + " (" + dateFormat.format(new Date(date)) + ")");
|
||||
bodyText.setText(body);
|
||||
}
|
||||
};
|
||||
|
||||
setListAdapter(adapter);
|
||||
}
|
||||
}
|
@ -130,7 +130,7 @@ public class PendingMessages extends ListActivity {
|
||||
|
||||
displayedMessages = messages;
|
||||
|
||||
this.setTitle("EnvayaSMS : Pending Messages ("+messages.size()+")");
|
||||
this.setTitle(getText(R.string.pending_messages_title) + " ("+messages.size()+")");
|
||||
|
||||
final LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final DateFormat longFormat = new SimpleDateFormat("dd MMM hh:mm:ss");
|
||||
|
@ -1,15 +1,16 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.*;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.SettingNotFoundException;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.Menu;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.R;
|
||||
@ -18,6 +19,14 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
|
||||
private App app;
|
||||
|
||||
private BroadcastReceiver installReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
updatePrefSummary(screen.findPreference("send_limit"));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -32,8 +41,19 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
{
|
||||
updatePrefSummary(screen.getPreference(i));
|
||||
}
|
||||
|
||||
IntentFilter installReceiverFilter = new IntentFilter();
|
||||
installReceiverFilter.addAction(App.EXPANSION_PACKS_CHANGED_INTENT);
|
||||
registerReceiver(installReceiver, installReceiverFilter);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
unregisterReceiver(installReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume(){
|
||||
super.onResume();
|
||||
@ -50,11 +70,21 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
|
||||
|
||||
if (key.equals("outgoing_interval"))
|
||||
{
|
||||
app.setOutgoingMessageAlarm();
|
||||
}
|
||||
else if (key.startsWith("amqp_"))
|
||||
{
|
||||
if (app.isAmqpEnabled())
|
||||
{
|
||||
app.getAmqpConsumer().startAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.getAmqpConsumer().stopAsync();
|
||||
}
|
||||
}
|
||||
else if (key.equals("wifi_sleep_policy"))
|
||||
{
|
||||
int value;
|
||||
@ -107,15 +137,21 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
}
|
||||
else if (key.equals("enabled"))
|
||||
{
|
||||
app.log(app.isEnabled() ? "SMS Gateway started." : "SMS Gateway stopped.");
|
||||
app.log(app.isEnabled() ? getText(R.string.started) : getText(R.string.stopped));
|
||||
app.enabledChanged();
|
||||
}
|
||||
|
||||
sendBroadcast(new Intent(App.SETTINGS_CHANGED_INTENT));
|
||||
updatePrefSummary(findPreference(key));
|
||||
}
|
||||
|
||||
private void updatePrefSummary(Preference p)
|
||||
{
|
||||
if (p == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String key = p.getKey();
|
||||
|
||||
if ("wifi_sleep_policy".equals(key))
|
||||
@ -144,11 +180,32 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
p.setSummary("Wi-Fi will stay connected when the phone sleeps");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("send_limit".equals(key))
|
||||
{
|
||||
int limit = app.getOutgoingMessageLimit();
|
||||
String limitStr = "Send up to " + limit + " SMS per hour.";
|
||||
|
||||
if (limit < 300)
|
||||
{
|
||||
limitStr += "\nClick to increase limit...";
|
||||
}
|
||||
|
||||
p.setSummary(limitStr);
|
||||
}
|
||||
else if ("help".equals(key))
|
||||
{
|
||||
p.setSummary(app.getPackageInfo().versionName);
|
||||
}
|
||||
else if (p instanceof PreferenceCategory)
|
||||
{
|
||||
PreferenceCategory category = (PreferenceCategory)p;
|
||||
int numPreferences = category.getPreferenceCount();
|
||||
for (int i = 0; i < numPreferences; i++)
|
||||
{
|
||||
updatePrefSummary(category.getPreference(i));
|
||||
}
|
||||
}
|
||||
else if (p instanceof ListPreference) {
|
||||
p.setSummary(((ListPreference)p).getEntry());
|
||||
}
|
||||
@ -160,7 +217,7 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
{
|
||||
p.setSummary("(not set)");
|
||||
}
|
||||
else if (p.getKey().equals("password"))
|
||||
else if (textPref.getEditText().getTransformationMethod() instanceof PasswordTransformationMethod)
|
||||
{
|
||||
p.setSummary("********");
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
@ -20,6 +21,8 @@ public class TestPhoneNumbers extends ListActivity {
|
||||
|
||||
private App app;
|
||||
|
||||
private CheckBox autoAddOutgoing;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
@ -27,6 +30,9 @@ public class TestPhoneNumbers extends ListActivity {
|
||||
|
||||
app = (App)getApplication();
|
||||
|
||||
autoAddOutgoing = (CheckBox)findViewById(R.id.auto_add_outgoing);
|
||||
autoAddOutgoing.setChecked(app.autoAddTestNumber());
|
||||
|
||||
ListView lv = getListView();
|
||||
lv.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View view,
|
||||
@ -63,6 +69,13 @@ public class TestPhoneNumbers extends ListActivity {
|
||||
updateTestPhoneNumbers();
|
||||
}
|
||||
|
||||
public void autoAddOutgoingClicked(View v)
|
||||
{
|
||||
boolean checked = autoAddOutgoing.isChecked();
|
||||
app.log("Test Mode: automatically add outgoing message recipients set to " + (checked ? "YES" : "NO"));
|
||||
app.saveBooleanSetting("auto_add_test_number", checked);
|
||||
}
|
||||
|
||||
public void updateTestPhoneNumbers()
|
||||
{
|
||||
String[] senders = app.getTestPhoneNumbers().toArray(new String[]{});
|
||||
|
Loading…
Reference in New Issue
Block a user