diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..2ffaa1897d994ba9a9d767faee5f5d0ade7da0c1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,21 @@ +image: "registry.gitlab.e.foundation:5000/e/apps/docker-android-apps-cicd:latest" + +stages: +- build + +before_script: +- export GRADLE_USER_HOME=$(pwd)/.gradle +- chmod +x ./gradlew + +cache: + key: ${CI_PROJECT_ID} + paths: + - .gradle/ + +build: + stage: build + script: + - ./gradlew build + artifacts: + paths: + - build/outputs/apk diff --git a/build.gradle b/build.gradle index dfb1e3f6eec0115411ea0a3b530207933de17f70..d47c91bbb775f9758d7354762532020839ea54d6 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ android { } defaultConfig { - applicationId "fr.unix_experience.owncloud_sms" + applicationId "e.foundation.sms_sync" versionCode 70 versionName "2.0.6" minSdkVersion 16 diff --git a/ncsms-android.iml b/ncsms-android.iml new file mode 100644 index 0000000000000000000000000000000000000000..eee803d24ac73362720a6b3daa12b85b36ce4d48 --- /dev/null +++ b/ncsms-android.iml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ncsmsgo/ncsmsgo.aar b/ncsmsgo/ncsmsgo.aar index fd4719f888b2d89ab7c674370fafe313d119750a..bb4e15bc176a8bffed7b5b29de52e1ac795afb45 100644 Binary files a/ncsmsgo/ncsmsgo.aar and b/ncsmsgo/ncsmsgo.aar differ diff --git a/ncsmsgo/ncsmsgo.iml b/ncsmsgo/ncsmsgo.iml index 3599347fbbf2b831a9902bc286435f95b206c6cc..39cbca89a4ece69fde4ec05b844c196e170fb566 100644 --- a/ncsmsgo/ncsmsgo.iml +++ b/ncsmsgo/ncsmsgo.iml @@ -4,6 +4,8 @@ diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 7a9f8d0c5f1431677a7d6dfae53c022d9b8d04cb..cf4155c88a631e36322d8b7ebcaee4680edc7396 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -33,13 +33,53 @@ + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + - - + + + + + + android:label="@string/title_activity_select_contact" /> + android:label="@string/account_actions" /> + android:label="@string/restore_all_messages" /> + android:theme="@style/OcSmsTheme.NoActionBar" /> \ No newline at end of file diff --git a/src/main/aidl/foundation/e/message/IRestoreService.aidl b/src/main/aidl/foundation/e/message/IRestoreService.aidl new file mode 100644 index 0000000000000000000000000000000000000000..36c9651a03aeb20a40a0f2ae7e4a816e30fdd88a --- /dev/null +++ b/src/main/aidl/foundation/e/message/IRestoreService.aidl @@ -0,0 +1,7 @@ +package foundation.e.message; + + +interface IRestoreService { + oneway void restoreMessages(in String messagesJson); + +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/activities/MainActivity.java b/src/main/java/fr/unix_experience/owncloud_sms/activities/MainActivity.java index 585d5796e0f16ab64ff0279402b9753e7237a6fd..dd65f2ddca9541c84a87740f3d30389204a5c1c2 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/activities/MainActivity.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/activities/MainActivity.java @@ -30,7 +30,9 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.PowerManager; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; @@ -48,6 +50,7 @@ import fr.unix_experience.owncloud_sms.R; import fr.unix_experience.owncloud_sms.activities.remote_account.AccountListActivity; import fr.unix_experience.owncloud_sms.engine.ASyncSMSSync.SyncTask; import fr.unix_experience.owncloud_sms.engine.ConnectivityMonitor; +import fr.unix_experience.owncloud_sms.engine.SmsSenderService; import fr.unix_experience.owncloud_sms.enums.PermissionID; import fr.unix_experience.owncloud_sms.prefs.PermissionChecker; @@ -81,6 +84,17 @@ public class MainActivity extends AppCompatActivity drawer = findViewById(R.id.drawer_layout); setupDrawer(); drawer.openDrawer(GravityCompat.START); + startService(new Intent(this, SmsSenderService.class)); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Intent intent = new Intent(); + String packageName = getPackageName(); + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + if (!pm.isIgnoringBatteryOptimizations(packageName)) { + intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + packageName)); + startActivity(intent); + } + } } protected void setupToolbar() { diff --git a/src/main/java/fr/unix_experience/owncloud_sms/activities/remote_account/RestoreMessagesActivity.java b/src/main/java/fr/unix_experience/owncloud_sms/activities/remote_account/RestoreMessagesActivity.java index 616771800e75d449ed21ac189187f44081e17e5f..cab1cf01671d848809b0966f226294d4c3f3b163 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/activities/remote_account/RestoreMessagesActivity.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/activities/remote_account/RestoreMessagesActivity.java @@ -17,15 +17,12 @@ package fr.unix_experience.owncloud_sms.activities.remote_account; * along with this program. If not, see . */ -import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Bundle; import android.provider.Telephony; -import android.support.v4.app.ActivityCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.util.Log; @@ -37,6 +34,7 @@ import android.widget.TextView; import fr.unix_experience.owncloud_sms.R; import fr.unix_experience.owncloud_sms.engine.ASyncSMSRecovery; +import fr.unix_experience.owncloud_sms.engine.ASyncSMSSender; import fr.unix_experience.owncloud_sms.engine.ConnectivityMonitor; public class RestoreMessagesActivity extends AppCompatActivity { diff --git a/src/main/java/fr/unix_experience/owncloud_sms/broadcast_receivers/SmsSenderIntentReceiver.java b/src/main/java/fr/unix_experience/owncloud_sms/broadcast_receivers/SmsSenderIntentReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..40eaa1b7ca652be48a4f5eaec53a66d7fa885ab2 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/broadcast_receivers/SmsSenderIntentReceiver.java @@ -0,0 +1,136 @@ +package fr.unix_experience.owncloud_sms.broadcast_receivers; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; + +import org.json.JSONException; + +import fr.unix_experience.owncloud_sms.R; +import fr.unix_experience.owncloud_sms.engine.ASyncSMSSender; +import fr.unix_experience.owncloud_sms.engine.AndroidSmsFetcher; +import fr.unix_experience.owncloud_sms.engine.OCSMSOwnCloudClient; +import fr.unix_experience.owncloud_sms.engine.SmsEntry; +import fr.unix_experience.owncloud_sms.engine.SmsSenderService; +import fr.unix_experience.owncloud_sms.enums.MailboxID; +import fr.unix_experience.owncloud_sms.exceptions.OCSyncException; +import fr.unix_experience.owncloud_sms.providers.SmsDataProvider; +import fr.unix_experience.owncloud_sms.providers.SmsSendStackProvider; +import ncsmsgo.SmsBuffer; +import ncsmsgo.SmsMessage; + +public class SmsSenderIntentReceiver extends BroadcastReceiver { + private static final int ALARM_ID = 1001; + + @Override + public void onReceive(Context context, Intent intent) { + if(intent.getStringExtra("sms") != null){ + SmsMessage message = null; + try { + Account account = null; + for (Account account1: AccountManager.get(context).getAccountsByType(intent.getStringExtra("account_type"))){ + if(account1.name.equals(intent.getStringExtra("account_name"))){ + account = account1; + break; + } + } + if(account == null){ + return; + } + + OCSMSOwnCloudClient client = new OCSMSOwnCloudClient(context, account); + message = SmsSendStackProvider.smsMessageFromString(intent.getStringExtra("sms")); + SmsBuffer smsBuffer = new SmsBuffer(); + SmsEntry entry = readMailBox(context, smsBuffer, message.getId(), Uri.parse(intent.getStringExtra("uri"))); + SmsSendStackProvider stackProvider = SmsSendStackProvider.getInstance(context); + if(entry != null) { + stackProvider.setSent(message, entry.sent ? 3 : 5); + client.deleteMessage(message.getAddress(), message.getDate()); + client.doPushRequest(smsBuffer); + if(entry.sent) + stackProvider.setSent(message, 2); + } else { + stackProvider.setSent(message, 6); + client.deleteMessage(message.getAddress(), message.getDate()); + smsBuffer.push(message.getId(), + message.getId(), + MailboxID.SENT.ordinal(), + message.getType(), + System.currentTimeMillis(), + message.getAddress(), + message.getCardNumber(), + message.getCardSlot(), + message.getIccId(), + message.getDeviceName(), + message.getCarrierName(), + message.getMessage(), + 6, + "true", + "true") ; + client.doPushRequest(smsBuffer); + + } + + } catch (JSONException | OCSyncException e) { + e.printStackTrace(); + } + + } else + context.startService(new Intent(context, SmsSenderService.class)); + } + + + + + private SmsEntry readMailBox(Context context, SmsBuffer smsBuffer,long nc_id, Uri uri) { + Cursor c = new SmsDataProvider(context).query(Uri.parse(MailboxID.SENT.getURI()), SmsDataProvider.messageFields, "_id = ?", new String[] { uri.getLastPathSegment() }, null); + SmsEntry entry = null; + if(c!=null) { + do { + entry = new SmsEntry(); + for (int idx = 0; idx < c.getColumnCount(); idx++) { + AndroidSmsFetcher.handleProviderColumn(context, c, idx, entry); + } + entry.device_name = Build.MODEL; + // Mailbox ID is required by server + entry.mailboxId = MailboxID.SENT.ordinal(); + + break; + + } + while (c.moveToNext()); + c.close(); + } + + if(entry != null) { + smsBuffer.push(entry.id, + nc_id, + MailboxID.SENT.ordinal(), + entry.type, + entry.date, + entry.address, + entry.card_number, + entry.card_slot, + entry.icc_id, + entry.device_name, + entry.carrier_name, + entry.body, + 1, + "true", + "true") ; + return entry; + } + return null; + } + +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSender.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSender.java new file mode 100644 index 0000000000000000000000000000000000000000..abca65950d0046aa6677a6b39bf43ee362797b7f --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSender.java @@ -0,0 +1,220 @@ +package fr.unix_experience.owncloud_sms.engine; + +import android.accounts.Account; +import android.app.PendingIntent; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.preference.PreferenceManager; +import android.provider.Telephony; +import android.telephony.SmsManager; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.util.Log; + +import java.util.List; +import java.util.Random; + +import fr.unix_experience.owncloud_sms.activities.remote_account.RestoreMessagesActivity; +import fr.unix_experience.owncloud_sms.broadcast_receivers.SmsSenderIntentReceiver; +import fr.unix_experience.owncloud_sms.enums.MailboxID; +import fr.unix_experience.owncloud_sms.providers.SmsDataProvider; +import fr.unix_experience.owncloud_sms.providers.SmsSendStackProvider; +import ncsmsgo.SmsMessage; +import ncsmsgo.SmsMessagesResponse; + +/* + * Copyright (c) 2014-2016, Loic Blot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +public interface ASyncSMSSender { + class SMSSenderSyncTask extends AsyncTask { + private final Context _context; + private final Account _account; + + public SMSSenderSyncTask(Context context, Account account) { + _context = context; + _account = account; + } + + @Override + protected Void doInBackground(Void... params) { + // This feature is only available for Android 4.4 and greater + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + return null; + } + + if (!new ConnectivityMonitor(_context).isValid()) { + Log.e(ASyncSMSSender.TAG, "Restore connectivity problems, aborting"); + return null; + } + Log.i(ASyncSMSSender.TAG, "Starting background recovery"); + Long start = PreferenceManager.getDefaultSharedPreferences(_context).getLong("last_sync_sender_"+_account.name,0); + + OCSMSOwnCloudClient client = new OCSMSOwnCloudClient(_context, _account); + SmsDataProvider smsDataProvider = new SmsDataProvider(_context); + SmsMessagesResponse obj = client.retrieveSomeMessages(start, 500); + if (obj == null) { + Log.i(ASyncSMSSender.TAG, "Retrieved returns failure"); + return null; + } + SmsSendStackProvider stackProvider = SmsSendStackProvider.getInstance(_context); + + Integer nb = 0; + while ((obj != null) && (obj.getLastID() != start)) { + Log.i(TAG, "Retrieving messages from " + Long.toString(start) + + " to " + Long.toString(obj.getLastID())); + SmsMessage message; + while ((message = obj.getNextMessage()) != null) { + int mbid = (int) message.getMailbox(); + // Ignore invalid mailbox + if (mbid > MailboxID.ALL.getId()) { + Log.e(ASyncSMSSender.TAG, "Invalid mailbox found: " + mbid); + continue; + } + + + String address = message.getAddress(); + String body = message.getMessage(); + int type = (int) message.getType(); + if (address.isEmpty() || body.isEmpty()) { + Log.e(ASyncSMSSender.TAG, "Invalid SMS message found: " + message.toString()); + continue; + } + + MailboxID mailbox_id = MailboxID.fromInt(mbid); + + String date = Long.toString(message.getDate()); + + // Ignore already existing messages + + if(message.getSent() == 0 && message.getMailbox() == 1){ + if (stackProvider.messageExists(address, body, date) || smsDataProvider.messageExists(address, body, date, mailbox_id)) { + continue; + } + + stackProvider.addMessage(message, _account.name); + nb++; + if ((nb % 10) == 0) { + publishProgress(nb); + } + + } + + } + + start = obj.getLastID(); + PreferenceManager.getDefaultSharedPreferences(_context).edit().putLong("last_sync_sender_"+_account.name,start).apply(); + + if (!new ConnectivityMonitor(_context).isValid()) { + Log.e(ASyncSMSSender.TAG, "Restore connectivity problems, aborting"); + return null; + } + obj = client.retrieveSomeMessages(start, 500); + } + + // Force this refresh to fix dates + _context.getContentResolver().delete(Uri.parse("content://sms/conversations/-1"), + null, null); + + publishProgress(nb); + + Log.i(ASyncSMSSender.TAG, "Finishing background recovery"); + return null; + } + + @Override + protected void onProgressUpdate(Integer... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + SMSSendTask smsSendTask = new SMSSendTask(_context, _account); + smsSendTask.execute(); + } + } + + class SMSSendTask extends AsyncTask { + private final Context _context; + private final Account _account; + + public SMSSendTask(Context context, Account account) { + _context = context; + _account = account; + + } + + @Override + protected Void doInBackground(Void... params) { + SmsSendStackProvider stackProvider = SmsSendStackProvider.getInstance(_context); + List smsToSend = stackProvider.getMessagestoSend(_account.name); + for (SmsMessage message : smsToSend) { + if(message.getSent() == 0){ + int subscriptionId = -1; + if (Build.VERSION.SDK_INT>=22) { + SubscriptionManager subscriptionManager = (SubscriptionManager) _context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + for(SubscriptionInfo i: subscriptionManager.getActiveSubscriptionInfoList()){ + if((message.getCardNumber().isEmpty() || i.getNumber() == null || i.getNumber().equals(message.getCardNumber())) + && (message.getCarrierName().isEmpty() || i.getCarrierName().equals(message.getCarrierName())) + && (message.getCardSlot() == -1 || i.getSimSlotIndex() == message.getCardSlot())){ + subscriptionId = i.getSubscriptionId(); + break; + } + } + + } else subscriptionId = -2; + Intent intent = new Intent(); + intent.putExtra("sms", SmsSendStackProvider.smsMessageToJsonString(message)); + intent.putExtra("account_type", _account.type); + intent.putExtra("account_name", _account.name); + intent.putExtra("sent_time",System.currentTimeMillis()); + intent.setClass(_context, SmsSenderIntentReceiver.class); + SmsManager smsManager; + if(subscriptionId >=0) + smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId); + else if (subscriptionId == -2) + smsManager = SmsManager.getDefault(); + else{ + stackProvider.setSent(message, 5); + continue; + } + smsManager.sendTextMessage(message.getAddress(), null, message.getMessage(), + PendingIntent.getBroadcast(_context, new Random().nextInt(1000)+1200, intent,PendingIntent.FLAG_UPDATE_CURRENT), null); + stackProvider.setSent(message, 2); + + } + + } + + + return null; + } + + + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + } + } + + String TAG = ASyncSMSSender.class.getSimpleName(); +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSync.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSync.java index 77ca15aae3bd2669a6b026c3a39cc820942fc459..3b3157e8105745662ae03c036b957bf1176d838b 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSync.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/ASyncSMSSync.java @@ -20,6 +20,7 @@ package fr.unix_experience.owncloud_sms.engine; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; +import android.content.ContentResolver; import android.content.Context; import android.os.AsyncTask; import android.util.Log; @@ -109,6 +110,9 @@ public interface ASyncSMSSync { // Notify that we are syncing SMS for (Account element : myAccountList) { + if(!ContentResolver.getSyncAutomatically(element, _context.getString(R.string.account_authority))){ + continue; + } try { OCSMSOwnCloudClient _client = new OCSMSOwnCloudClient(_context, element); // Fetch API version first to do some early verifications diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/AndroidSmsFetcher.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/AndroidSmsFetcher.java index f149b592ad90dc3790b0aadcb5ca5aac520d81a5..d1f68e6681e064850216b24a5db2d2e49ab87486 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/engine/AndroidSmsFetcher.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/AndroidSmsFetcher.java @@ -19,6 +19,9 @@ package fr.unix_experience.owncloud_sms.engine; import android.content.Context; import android.database.Cursor; +import android.os.Build; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.util.Log; import org.json.JSONArray; @@ -48,17 +51,25 @@ public class AndroidSmsFetcher { SmsEntry entry = new SmsEntry(); for (int idx = 0; idx < c.getColumnCount(); idx++) { - handleProviderColumn(c, idx, entry); + handleProviderColumn(_context, c, idx, entry); } - + entry.device_name = Build.MODEL; // Mailbox ID is required by server entry.mailboxId = mbID.ordinal(); + entry.sent = true; smsBuffer.push(entry.id, + -1, mbID.ordinal(), entry.type, entry.date, entry.address, + entry.card_number, + entry.card_slot, + entry.icc_id, + entry.device_name, + entry.carrier_name, entry.body, + entry.sent ? 1 : 0, entry.read ? "true" : "false", entry.seen ? "true" : "false"); } @@ -106,10 +117,11 @@ public class AndroidSmsFetcher { // We create a list of strings to store results SmsEntry entry = new SmsEntry(); SmsBuffer results = new SmsBuffer(); + entry.device_name = Build.MODEL; Integer mboxId = -1; for (int idx = 0; idx < c.getColumnCount(); idx++) { - Integer rid = handleProviderColumn(c, idx, entry); + Integer rid = handleProviderColumn(_context, c, idx, entry); if (rid != -1) { mboxId = rid; } @@ -120,13 +132,21 @@ public class AndroidSmsFetcher { * mboxId is greater than server mboxId by 1 because types * aren't indexed in the same mean */ + entry.sent = true; entry.mailboxId = mboxId - 1; results.push(entry.id, + -1, mbID.ordinal(), entry.type, entry.date, entry.address, + entry.card_number, + entry.card_slot, + entry.icc_id, + entry.device_name, + entry.carrier_name, entry.body, + entry.sent ? 1 : 0, entry.read ? "true" : "false", entry.seen ? "true" : "false"); @@ -164,7 +184,7 @@ public class AndroidSmsFetcher { c.close(); } - private Integer handleProviderColumn(Cursor c, int idx, SmsEntry entry) { + public static Integer handleProviderColumn(Context context, Cursor c, int idx, SmsEntry entry) { String colName = c.getColumnName(idx); // Id column is must be an integer @@ -172,6 +192,19 @@ public class AndroidSmsFetcher { case "_id": entry.id = c.getInt(idx); break; + case "sub_id": + if(Build.VERSION.SDK_INT>=22) { + SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo(c.getInt(idx)); + if(info!=null) { + entry.card_number = info.getNumber(); + entry.card_slot = info.getSimSlotIndex(); + entry.carrier_name = info.getCarrierName().toString(); + } else + entry.card_number = ""; + } + else entry.card_number = ""; + break; case "type": entry.type = c.getInt(idx); return c.getInt(idx); diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/OCHttpClient.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/OCHttpClient.java index 56050a1e878e2e14aa3698dd371e6968753427c3..c20688aea9e5cad733a2047f97dbdf1714611523 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/engine/OCHttpClient.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/OCHttpClient.java @@ -27,6 +27,7 @@ import fr.unix_experience.owncloud_sms.enums.OCSyncErrorType; import fr.unix_experience.owncloud_sms.exceptions.OCSyncException; import fr.unix_experience.owncloud_sms.providers.AndroidVersionProvider; import ncsmsgo.SmsBuffer; +import ncsmsgo.SmsDeleteResponse; import ncsmsgo.SmsHTTPClient; import ncsmsgo.SmsIDListResponse; import ncsmsgo.SmsMessagesResponse; @@ -111,4 +112,11 @@ public class OCHttpClient { handleEarlyHTTPStatus(httpStatus); return new Pair<>(httpStatus, smr); } + + Pair deleteMessage(String address, long date) throws OCSyncException { + SmsDeleteResponse spr = _smsHttpClient.doDeleteMessageCall(address, date); + int httpStatus = (int) _smsHttpClient.getLastHTTPStatus(); + handleEarlyHTTPStatus(httpStatus); + return new Pair<>(httpStatus, spr); + } } diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/OCSMSOwnCloudClient.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/OCSMSOwnCloudClient.java index fb68247aad87d94f26473837ade6d20f47d80368..75f1e5ee3c94e79ed66557a40efd9e58dbf3bde7 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/engine/OCSMSOwnCloudClient.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/OCSMSOwnCloudClient.java @@ -31,6 +31,7 @@ import fr.unix_experience.owncloud_sms.enums.OCSyncErrorType; import fr.unix_experience.owncloud_sms.exceptions.OCSyncException; import fr.unix_experience.owncloud_sms.prefs.OCSMSSharedPrefs; import ncsmsgo.SmsBuffer; +import ncsmsgo.SmsDeleteResponse; import ncsmsgo.SmsIDListResponse; import ncsmsgo.SmsMessagesResponse; import ncsmsgo.SmsPhoneListResponse; @@ -46,7 +47,7 @@ public class OCSMSOwnCloudClient { _serverAPIVersion = 1; AccountManager accountManager = AccountManager.get(context); - String ocURI = accountManager.getUserData(account, "ocURI"); + String ocURI = accountManager.getUserData(account, "oc_base_url"); if (ocURI == null) { throw new IllegalStateException(context.getString(R.string.err_sync_account_unparsable)); } @@ -54,7 +55,7 @@ public class OCSMSOwnCloudClient { try { URL serverURL = new URL(ocURI); _http = new OCHttpClient(context, - serverURL, accountManager.getUserData(account, "ocLogin"), + serverURL, accountManager.getUserData(account, "email_address"), accountManager.getPassword(account)); } catch (MalformedURLException e) { throw new IllegalStateException(context.getString(R.string.err_sync_account_unparsable)); @@ -121,7 +122,7 @@ public class OCSMSOwnCloudClient { Log.i(OCSMSOwnCloudClient.TAG, "LastMessageDate set to: " + smsBuffer.getLastMessageDate()); } - SmsMessagesResponse retrieveSomeMessages(Long start, Integer limit) { + public SmsMessagesResponse retrieveSomeMessages(Long start, Integer limit) { // This is not allowed by server if (limit > OCSMSOwnCloudClient.SERVER_RECOVERY_MSG_LIMIT) { Log.e(OCSMSOwnCloudClient.TAG, "Message recovery limit exceeded"); @@ -153,4 +154,21 @@ public class OCSMSOwnCloudClient { private static final String TAG = OCSMSOwnCloudClient.class.getSimpleName(); + public void deleteMessage(String address, long date) { + address = address.replace("+", "%2B"); + Pair response; + try { + response = _http.deleteMessage(address, date); + } catch (OCSyncException e) { + Log.e(OCSMSOwnCloudClient.TAG, "Request failed."); + return; + } + + if (response.second == null) { + Log.e(OCSMSOwnCloudClient.TAG, + "Invalid response received from server, either messages or last_id field is missing."); + return; + } + + } } diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsEntry.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsEntry.java index 5319c7a9c2e16c04e6d18f4a9062ec8bf7915254..9fd6dfaebd47b5cf0fa65666bf8545c072fe852d 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsEntry.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsEntry.java @@ -23,7 +23,14 @@ public class SmsEntry { public int type; public boolean read; public boolean seen; + public boolean sent; public long date; public String address; public String body; + public String card_number; + public int card_slot; + public String device_name; + public String carrier_name; + public String icc_id; + } diff --git a/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsSenderService.java b/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsSenderService.java new file mode 100644 index 0000000000000000000000000000000000000000..077374fd038c8604c9e4ef8adc330991b25de469 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/engine/SmsSenderService.java @@ -0,0 +1,113 @@ +package fr.unix_experience.owncloud_sms.engine; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SyncResult; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.v4.app.JobIntentService; +import android.util.Log; + +import fr.unix_experience.owncloud_sms.R; +import fr.unix_experience.owncloud_sms.broadcast_receivers.SmsSenderIntentReceiver; + +public class SmsSenderService extends Service { + private static final int ALARM_ID = 1002; + private static final int JOB_ID = 1040; + + public SmsSenderService() { + } + + + // Storage for an instance of the sync adapter + private static SmsSenderSyncAdapter _adapter = null; + // Object to use as a thread-safe lock + private static final Object sSyncAdapterLock = new Object(); + /* + * Instantiate the sync adapter object. + */ + @Override + public void onCreate() { + /* + * Create the sync adapter as a singleton. + * Set the sync adapter as syncable + * Disallow parallel syncs + */ + synchronized (sSyncAdapterLock) { + if (_adapter == null) { + _adapter = new SmsSenderSyncAdapter(getApplicationContext(), true); + } + } + } + /** + * Return an object that allows the system to invoke + * the sync adapter. + * + */ + @Override + public IBinder onBind(Intent intent) { + /* + * Get the object that allows external processes + * to call onPerformSync(). The object is created + * in the base class code when the SyncAdapter + * constructors call super() + */ + return _adapter.getSyncAdapterBinder(); + } + + public int onStartCommand(Intent intent, int flags, int startId) { + int re = super.onStartCommand(intent, flags, startId); + startSenderProcess(this); + return re; + } + + public static void startSenderProcess(Context context){ + Account[] accountList = + AccountManager.get(context).getAccountsByType(context.getString(R.string.account_type)); + for(Account account:accountList){ + + if(!ContentResolver.getSyncAutomatically(account, context.getString(R.string.account_sender_authority))){ + continue; + } + + new ASyncSMSSender.SMSSenderSyncTask(context,account).execute(); + } + + // planNextLaunch(context); + } + + + + public static void planNextLaunch(Context context){ + AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, SmsSenderService.class); + int next = Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(context).getString("sync_frequency", "2")); + if(next == -1) + return; + PendingIntent alarmIntent = PendingIntent.getService(context, ALARM_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT); + if(Build.VERSION.SDK_INT>Build.VERSION_CODES.M) + alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + next*10*1000, alarmIntent); + } + + private class SmsSenderSyncAdapter extends AbstractThreadedSyncAdapter { + public SmsSenderSyncAdapter(Context applicationContext, boolean b) { + super(applicationContext, b); + } + + @Override + public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) { + new ASyncSMSSender.SMSSenderSyncTask(getContext(),account).execute(); + } + } +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/observers/SmsObserver.java b/src/main/java/fr/unix_experience/owncloud_sms/observers/SmsObserver.java index 0b1289561e609c4679715c79d19a70a511b288a4..41f7a691c17bde4bd93707fbdd633438026211d2 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/observers/SmsObserver.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/observers/SmsObserver.java @@ -20,6 +20,7 @@ package fr.unix_experience.owncloud_sms.observers; import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; +import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; @@ -47,7 +48,6 @@ public class SmsObserver extends ContentObserver implements ASyncSMSSync { PermissionID.REQUEST_SMS)) { return; } - super.onChange(selfChange); Log.i(SmsObserver.TAG, "onChange SmsObserver"); diff --git a/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsBackupProvider.java b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsBackupProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..8f7019d4871d3dd6eb829c2437d7a2bb23b7f712 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsBackupProvider.java @@ -0,0 +1,74 @@ +package fr.unix_experience.owncloud_sms.providers; + +/* + * Copyright (c) 2014-2015, Loic Blot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class SmsBackupProvider extends ContentProvider { + // WARNING: mandatory + public SmsBackupProvider() {} + public SmsBackupProvider(Context ct) { + super(); + _context = ct; + } + + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { + return null; + } + + @Override + public String getType(@NonNull Uri uri) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + private Context _context; + private static final String TAG = SmsBackupProvider.class.getSimpleName(); +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsDataProvider.java b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsDataProvider.java index 5ff733df3ae6b2bd0bd1921ab113dd4f2d393c0b..66d69326f5d67e582d1503c8de39ce4ef16e1a93 100644 --- a/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsDataProvider.java +++ b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsDataProvider.java @@ -29,7 +29,7 @@ import fr.unix_experience.owncloud_sms.enums.MailboxID; import fr.unix_experience.owncloud_sms.prefs.OCSMSSharedPrefs; public class SmsDataProvider extends ContentProvider { - static String[] messageFields = { + public static String[] messageFields = { "read", "date", "address", @@ -37,6 +37,7 @@ public class SmsDataProvider extends ContentProvider { "body", "_id", "type", + "sub_id", //"length(address)" // For debug purposes }; diff --git a/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsRestoreProvider.java b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsRestoreProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..3271362f2d97a3f46f9fedce89ca67aa3dfc30f4 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsRestoreProvider.java @@ -0,0 +1,74 @@ +package fr.unix_experience.owncloud_sms.providers; + +/* + * Copyright (c) 2014-2015, Loic Blot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class SmsRestoreProvider extends ContentProvider { + // WARNING: mandatory + public SmsRestoreProvider() {} + public SmsRestoreProvider(Context ct) { + super(); + _context = ct; + } + + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { + return null; + } + + @Override + public String getType(@NonNull Uri uri) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + private Context _context; + private static final String TAG = SmsRestoreProvider.class.getSimpleName(); +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsSendProvider.java b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsSendProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..81748c4e1412d9f54d148d1cf9202cc557d1a966 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsSendProvider.java @@ -0,0 +1,74 @@ +package fr.unix_experience.owncloud_sms.providers; + +/* + * Copyright (c) 2014-2015, Loic Blot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class SmsSendProvider extends ContentProvider { + // WARNING: mandatory + public SmsSendProvider() {} + public SmsSendProvider(Context ct) { + super(); + _context = ct; + } + + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { + return null; + } + + @Override + public String getType(@NonNull Uri uri) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + // TODO Auto-generated method stub + return 0; + } + + private Context _context; + private static final String TAG = SmsSendProvider.class.getSimpleName(); +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsSendStackProvider.java b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsSendStackProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..8bedcfb4e5c787191a19fc464c4d9019271f1cfa --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/providers/SmsSendStackProvider.java @@ -0,0 +1,235 @@ +package fr.unix_experience.owncloud_sms.providers; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.telephony.SmsManager; +import android.util.JsonReader; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import fr.unix_experience.owncloud_sms.enums.MailboxID; +import ncsmsgo.SmsMessage; + +public class SmsSendStackProvider { + private static final String DATABASE_NAME = "sms_db"; + private static final int DATABASE_VERSION = 4 + + + ; + private static final String TABLE_NAME = "send_stack"; + private static final String KEY_MESSAGE = "message"; + /* + 0: not sent, + 1: sent, + 2: pending, + 3: sent but status not synced + 5: won't send + 5: failed to send + + */ + private static final String KEY_SENT = "sent"; + private static final String KEY_ADDRESS = "address"; + private static final String KEY_MAILBOX_ID = "mb_is"; + private static final String KEY_DATE = "date"; + private static final String KEY_BODY = "body"; + private static final String KEY_ACCOUNT = "account"; + public static final String CREATE_TABLE = "create table " + TABLE_NAME + "( " + + KEY_SENT + " INTEGER," + + KEY_ACCOUNT + " text not null," + + KEY_ADDRESS + " text not null," + + KEY_MAILBOX_ID + " INTEGER not null," + + KEY_DATE + " text not null," + + KEY_BODY + " INTEGER not null," + + KEY_MESSAGE + " text not null," + +" PRIMARY KEY ("+KEY_ADDRESS+","+KEY_BODY+","+KEY_DATE+","+KEY_MAILBOX_ID+" ));"; + private static SmsSendStackProvider sSmsSendStackProvider; + private final Context mContext; + private final DatabaseHelper mDatabaseHelper; + + public SmsSendStackProvider(Context context){ + mContext = context; + mDatabaseHelper = new DatabaseHelper(context); + } + + public void addMessage(SmsMessage message, String accountName, int conflit) { + synchronized (mDatabaseHelper.lock) { + SQLiteDatabase sqliteDatabase = mDatabaseHelper.open(); + ContentValues initialValues = new ContentValues(); + initialValues.put(KEY_MESSAGE,smsMessageToJsonString(message)); + initialValues.put(KEY_SENT,message.getSent()); + initialValues.put(KEY_ADDRESS,message.getAddress()); + initialValues.put(KEY_MAILBOX_ID,message.getMailbox()); + initialValues.put(KEY_DATE,message.getDate()); + initialValues.put(KEY_BODY,message.getMessage()); + initialValues.put(KEY_ACCOUNT,accountName); + sqliteDatabase.insertWithOnConflict(TABLE_NAME, null, initialValues, conflit); + mDatabaseHelper.close(); + } + + } + + public void addMessage(SmsMessage message, String accountName) { + try { + addMessage(message, accountName, SQLiteDatabase.CONFLICT_NONE); + } catch (android.database.sqlite.SQLiteConstraintException e){ + + } + } + public static SmsSendStackProvider getInstance(Context context){ + if(sSmsSendStackProvider ==null) + sSmsSendStackProvider = new SmsSendStackProvider(context); + return sSmsSendStackProvider; + } + + public boolean messageExists() { + List messages = new ArrayList<>(); + synchronized (mDatabaseHelper.lock) { + SQLiteDatabase sqliteDatabase = mDatabaseHelper.open(); + Cursor cursor = sqliteDatabase.query(TABLE_NAME, new String[]{KEY_MESSAGE, KEY_SENT}, KEY_SENT + "= ?", new String[]{"0"}, null, null, null); + if (cursor.getCount() > 0) { + mDatabaseHelper.close(); + cursor.close(); + return true; + } + mDatabaseHelper.close(); + cursor.close(); + } + return false; + } + public boolean messageExists(String address, String body, String date) { + synchronized (mDatabaseHelper.lock) { + SQLiteDatabase sqliteDatabase = mDatabaseHelper.open(); + Cursor c = sqliteDatabase.query(TABLE_NAME, new String[]{KEY_SENT}, + "address = ? AND body = ? AND date = ?", + new String[]{address, body, date}, null, null, null); + if (c == null) { + return false; + } + + boolean exists = c.getCount() > 0; + c.close(); + + return exists; + } + } + public static SmsMessage smsMessageFromString(String msg) throws JSONException { + SmsMessage message = new SmsMessage(); + JSONObject object = new JSONObject(msg); + message.setAddress(object.getString("Address")); + message.setId(object.getLong("Id")); + message.setMailbox(object.getInt("Mailbox")); + message.setMessage(object.getString("Message")); + message.setSent(object.getInt("Sent")); + message.setCardSlot(object.getLong("CardSlot")); + message.setCardNumber(object.getString("CardNumber")); + message.setIccId(object.getString("IccId")); + message.setDeviceName(object.getString("DeviceName")); + message.setCarrierName(object.getString("CarrierName")); + message.setType(object.getInt("Type")); + message.setDate(object.getLong("Date")); + + return message; + } + public List getMessagestoSend(String accountName) { + List messages = new ArrayList<>(); + synchronized (mDatabaseHelper.lock) { + SQLiteDatabase sqliteDatabase = mDatabaseHelper.open(); + Cursor cursor = sqliteDatabase.query(TABLE_NAME, new String[]{KEY_MESSAGE, KEY_SENT}, KEY_SENT + "= ? AND "+KEY_ACCOUNT+" = ? ", new String[]{"0", accountName}, null, null, null); + if (cursor.getCount() > 0) { + while(cursor.moveToNext()){ + try { + messages.add(smsMessageFromString(cursor.getString(cursor.getColumnIndex(KEY_MESSAGE)))); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + cursor.close(); + mDatabaseHelper.close(); + } + + return messages; + } + //Quoting (SmsMessage.toString wasn't quoting) + public static String smsMessageToJsonString(SmsMessage message) { + StringBuilder var1 = new StringBuilder(); + var1.append("{"); + var1.append("Id:").append(message.getId()).append(","); + var1.append("Address:").append(JSONObject.quote(message.getAddress())).append(","); + var1.append("Mailbox:").append(message.getMailbox()).append(","); + var1.append("Message:").append(JSONObject.quote(message.getMessage())).append(","); + var1.append("Sent:").append(message.getSent()).append(","); + var1.append("CardNumber:").append(JSONObject.quote(message.getCardNumber())).append(","); + var1.append("CardSlot:").append(message.getCardSlot()).append(","); + var1.append("IccId:").append(JSONObject.quote(message.getIccId())).append(","); + var1.append("DeviceName:").append(JSONObject.quote(message.getDeviceName())).append(","); + var1.append("CarrierName:").append(JSONObject.quote(message.getCarrierName())).append(","); + var1.append("Type:").append(message.getType()).append(","); + var1.append("Date:").append(message.getDate()); + return var1.append("}").toString(); + } + + public void setSent(SmsMessage message, int sent) { + message.setSent(sent); + updateMessage(message); + } + + private void updateMessage(SmsMessage message) { + synchronized (mDatabaseHelper.lock) { + SQLiteDatabase sqliteDatabase = mDatabaseHelper.open(); + ContentValues initialValues = new ContentValues(); + initialValues.put(KEY_MESSAGE,smsMessageToJsonString(message)); + initialValues.put(KEY_SENT,message.getSent()); + + sqliteDatabase.update(TABLE_NAME, initialValues, KEY_ADDRESS+"=? AND "+KEY_MAILBOX_ID+"= ? AND "+KEY_DATE+"= ? AND "+KEY_BODY +" = ?", + new String[]{message.getAddress(), message.getMailbox()+"", message.getDate()+"", message.getMessage()}); + mDatabaseHelper.close(); + } + } + + private class DatabaseHelper extends SQLiteOpenHelper { + + public Object lock = new Object(); + private DatabaseHelper mDatabaseHelper; + + DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // This method is only called once when the database is created for the first time + db.execSQL(CREATE_TABLE); + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("drop table "+TABLE_NAME); + db.execSQL(CREATE_TABLE); + + + } + + public SQLiteDatabase open(){ + if(mDatabaseHelper == null) + mDatabaseHelper = new DatabaseHelper(mContext); + return mDatabaseHelper.getWritableDatabase(); + } + + public void close(){ + if(mDatabaseHelper == null) + mDatabaseHelper.close(); + } + } +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsBackupAdapter.java b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsBackupAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..666bcd3fede0254806a932e79ebf31f8211fc2e0 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsBackupAdapter.java @@ -0,0 +1,93 @@ +package fr.unix_experience.owncloud_sms.sync_adapters; + +/* + * Copyright (c) 2014-2015, Loic Blot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import android.accounts.Account; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.Context; +import android.content.SyncResult; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; + +import fr.unix_experience.owncloud_sms.R; +import fr.unix_experience.owncloud_sms.engine.AndroidSmsFetcher; +import fr.unix_experience.owncloud_sms.engine.ConnectivityMonitor; +import fr.unix_experience.owncloud_sms.engine.OCSMSOwnCloudClient; +import fr.unix_experience.owncloud_sms.enums.OCSMSNotificationType; +import fr.unix_experience.owncloud_sms.exceptions.OCSyncException; +import fr.unix_experience.owncloud_sms.notifications.OCSMSNotificationUI; +import fr.unix_experience.owncloud_sms.prefs.OCSMSSharedPrefs; +import ncsmsgo.SmsBuffer; + +class SmsBackupAdapter extends AbstractThreadedSyncAdapter { + + SmsBackupAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, + ContentProviderClient provider, SyncResult syncResult) { + + if (new OCSMSSharedPrefs(getContext()).showSyncNotifications()) { + OCSMSNotificationUI.notify(getContext(), getContext().getString(R.string.sync_title), + getContext().getString(R.string.sync_inprogress), OCSMSNotificationType.SYNC.ordinal()); + } + + try { + // This feature is only available for Android 4.4 and greater + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + return; + } + + if (!new ConnectivityMonitor(getContext()).isValid()) { + Log.e(TAG, "Restore connectivity problems, aborting"); + return; + } + + Log.i(TAG, "Starting background backup"); + Long start = PreferenceManager.getDefaultSharedPreferences(getContext()).getLong("last_sync_backup_"+account.name,0); + SmsBuffer smsBuffer = new SmsBuffer(); + new AndroidSmsFetcher(getContext()).bufferMessagesSinceDate(smsBuffer, start); + try { + OCSMSOwnCloudClient _client = new OCSMSOwnCloudClient(getContext(), account); + Log.i(TAG, "Server API version: " + _client.getServerAPIVersion()); + _client.doPushRequest(smsBuffer); + OCSMSNotificationUI.cancel(getContext()); + PreferenceManager.getDefaultSharedPreferences(getContext()).edit().putLong("last_sync_backup_"+account.name,smsBuffer.getLastMessageDate()).apply(); + + Log.i(TAG, "Finishing background backup"); + } catch (IllegalStateException e) { // Fail to read account data + OCSMSNotificationUI.notify(getContext(), getContext().getString(R.string.fatal_error), + e.getMessage(), OCSMSNotificationType.SYNC_FAILED.ordinal()); + } catch (OCSyncException e) { + Log.e(TAG, getContext().getString(e.getErrorId())); + OCSMSNotificationUI.notify(getContext(), getContext().getString(R.string.fatal_error), + e.getMessage(), OCSMSNotificationType.SYNC_FAILED.ordinal()); + } + + } catch (IllegalStateException e) { + OCSMSNotificationUI.notify(getContext(), getContext().getString(R.string.fatal_error), + e.getMessage(), OCSMSNotificationType.SYNC_FAILED.ordinal()); + } + } + + private static final String TAG = SmsBackupAdapter.class.getSimpleName(); +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsBackupService.java b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsBackupService.java new file mode 100644 index 0000000000000000000000000000000000000000..0f7c1672d5c40698f54af50cd798bdea80a9de8e --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsBackupService.java @@ -0,0 +1,68 @@ +package fr.unix_experience.owncloud_sms.sync_adapters; + +/* + * Copyright (c) 2014-2015, Loic Blot + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class SmsBackupService extends Service { + // Storage for an instance of the sync adapter + private static SmsBackupAdapter _adapter = null; + // Object to use as a thread-safe lock + private static final Object sSyncAdapterLock = new Object(); + /* + * Instantiate the sync adapter object. + */ + @Override + public void onCreate() { + /* + * Create the sync adapter as a singleton. + * Set the sync adapter as syncable + * Disallow parallel syncs + */ + synchronized (SmsBackupService.sSyncAdapterLock) { + if (SmsBackupService._adapter == null) { + SmsBackupService._adapter = new SmsBackupAdapter(getApplicationContext(), true); + } + } + } + /** + * Return an object that allows the system to invoke + * the sync adapter. + * + */ + @Override + public IBinder onBind(Intent intent) { + /* + * Get the object that allows external processes + * to call onPerformSync(). The object is created + * in the base class code when the SyncAdapter + * constructors call super() + */ + return SmsBackupService._adapter.getSyncAdapterBinder(); + } +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsRestoreAdapter.java b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsRestoreAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..75f81bc69a74d62a8f9fd81012e0d514bfeee19a --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsRestoreAdapter.java @@ -0,0 +1,193 @@ +package fr.unix_experience.owncloud_sms.sync_adapters; + +/* + * Copyright (c) 2014-2015, Loic Blot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import android.accounts.Account; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ComponentName; +import android.content.ContentProviderClient; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SyncResult; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.provider.Telephony; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import foundation.e.message.IRestoreService; +import fr.unix_experience.owncloud_sms.R; +import fr.unix_experience.owncloud_sms.engine.ConnectivityMonitor; +import fr.unix_experience.owncloud_sms.engine.OCSMSOwnCloudClient; +import fr.unix_experience.owncloud_sms.enums.MailboxID; +import fr.unix_experience.owncloud_sms.enums.OCSMSNotificationType; +import fr.unix_experience.owncloud_sms.notifications.OCSMSNotificationUI; +import fr.unix_experience.owncloud_sms.prefs.OCSMSSharedPrefs; +import fr.unix_experience.owncloud_sms.providers.SmsDataProvider; +import ncsmsgo.SmsMessage; +import ncsmsgo.SmsMessagesResponse; + +class SmsRestoreAdapter extends AbstractThreadedSyncAdapter { + + private String mMessages; + + SmsRestoreAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, + ContentProviderClient provider, SyncResult syncResult) { + + if (new OCSMSSharedPrefs(getContext()).showSyncNotifications()) { + OCSMSNotificationUI.notify(getContext(), getContext().getString(R.string.sync_title), + getContext().getString(R.string.sync_inprogress), OCSMSNotificationType.SYNC.ordinal()); + } + + try { + // This feature is only available for Android 4.4 and greater + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + return; + } + + if (!new ConnectivityMonitor(getContext()).isValid()) { + Log.e(TAG, "Restore connectivity problems, aborting"); + return; + } + + Log.i(TAG, "Starting background recovery"); + Long start = PreferenceManager.getDefaultSharedPreferences(getContext()).getLong("last_sync_restore_"+account.name,0); + + OCSMSOwnCloudClient client = new OCSMSOwnCloudClient(getContext(), account); + SmsDataProvider smsDataProvider = new SmsDataProvider(getContext()); + SmsMessagesResponse obj = client.retrieveSomeMessages(start, 500); + if (obj == null) { + Log.i(TAG, "Retrieved returns failure"); + return; + } + Integer nb = 0; + JSONArray toRestore = new JSONArray(); + while ((obj != null) && (obj.getLastID() != start)) { + Log.i(TAG, "Retrieving messages from " + Long.toString(start) + + " to " + Long.toString(obj.getLastID())); + SmsMessage message; + while ((message = obj.getNextMessage()) != null) { + int mbid = (int) message.getMailbox(); + // Ignore invalid mailbox + if (mbid > MailboxID.ALL.getId()) { + Log.e(TAG, "Invalid mailbox found: " + mbid); + continue; + } + + String address = message.getAddress(); + String body = message.getMessage(); + int type = (int) message.getType(); + if (address.isEmpty() || body.isEmpty()) { + Log.e(TAG, "Invalid SMS message found: " + message.toString()); + continue; + } + + MailboxID mailbox_id = MailboxID.fromInt(mbid); + + String date = Long.toString(message.getDate()); + // Ignore already existing messages + if (smsDataProvider.messageExists(address, body, date, mailbox_id)) { + Log.i(TAG, "Message exists"); + continue; + } + Log.i(TAG, "Adding message"); + JSONObject messageRestore = new JSONObject(); + messageRestore.put("address", address); + messageRestore.put("body", body); + messageRestore.put("date", date); + messageRestore.put("type", type); + messageRestore.put("mailbox", mailbox_id.getURI()); + toRestore.put(messageRestore); + + nb++; + } + + start = obj.getLastID(); + PreferenceManager.getDefaultSharedPreferences(getContext()).edit().putLong("last_sync_restore_"+account.name,start).apply(); + if (!new ConnectivityMonitor(getContext()).isValid()) { + Log.e(TAG, "Restore connectivity problems, aborting"); + } + obj = client.retrieveSomeMessages(start, 500); + } + mMessages = toRestore.toString(); + connectRestoreService(); + + Log.i(TAG, "Finishing background recovery"); + OCSMSNotificationUI.cancel(getContext()); + } catch (IllegalStateException e) { + OCSMSNotificationUI.notify(getContext(), getContext().getString(R.string.fatal_error), + e.getMessage(), OCSMSNotificationType.SYNC_FAILED.ordinal()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + private void connectRestoreService() { + + try { + Intent intentService = new Intent(); + intentService.setComponent(new ComponentName("foundation.e.message", "com.moez.QKSMS.feature.service.ESmsRestoreService")); + + if (!getContext().bindService(intentService, mConnection, Context.BIND_AUTO_CREATE)) { + Log.d(TAG, "Binding to RestoreService returned false"); + throw new IllegalStateException("Binding to RestoreService returned false"); + } + } catch (SecurityException e) { + Log.e(TAG, "can't bind to RestoreService, check permission in Manifest"); + } + } + + /** + * Class for interacting with the main interface of the service. + */ + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.i(TAG, "RestoreService: onServiceConnected"); + + IRestoreService mService = IRestoreService.Stub.asInterface(service); + try { + mService.restoreMessages(mMessages); + } catch (RemoteException e) { + e.printStackTrace(); + } + getContext().unbindService(this); + } + + public void onServiceDisconnected(ComponentName className) { + + } + }; + + private static final String TAG = SmsRestoreAdapter.class.getSimpleName(); +} diff --git a/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsRestoreService.java b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsRestoreService.java new file mode 100644 index 0000000000000000000000000000000000000000..c5b547e76f40d92fd9b9b0ca5d59c2412b3ec931 --- /dev/null +++ b/src/main/java/fr/unix_experience/owncloud_sms/sync_adapters/SmsRestoreService.java @@ -0,0 +1,68 @@ +package fr.unix_experience.owncloud_sms.sync_adapters; + +/* + * Copyright (c) 2014-2015, Loic Blot + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class SmsRestoreService extends Service { + // Storage for an instance of the sync adapter + private static SmsRestoreAdapter _adapter = null; + // Object to use as a thread-safe lock + private static final Object sSyncAdapterLock = new Object(); + /* + * Instantiate the sync adapter object. + */ + @Override + public void onCreate() { + /* + * Create the sync adapter as a singleton. + * Set the sync adapter as syncable + * Disallow parallel syncs + */ + synchronized (SmsRestoreService.sSyncAdapterLock) { + if (SmsRestoreService._adapter == null) { + SmsRestoreService._adapter = new SmsRestoreAdapter(getApplicationContext(), true); + } + } + } + /** + * Return an object that allows the system to invoke + * the sync adapter. + * + */ + @Override + public IBinder onBind(Intent intent) { + /* + * Get the object that allows external processes + * to call onPerformSync(). The object is created + * in the base class code when the SyncAdapter + * constructors call super() + */ + return SmsRestoreService._adapter.getSyncAdapterBinder(); + } +} diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index c5dfd2db50c1530f3a74761e6e821cb8d67807c4..4cc02e83a1ed6d7edb79fa02a502ae5c126c7588 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -30,7 +30,7 @@ 7 - Nextcloud SMS + E SMS Sync Logo de connexion diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 507e5bbed87e50279e37b0b21e6991e3cd187a0a..ba9bc40bd1d8544570f747db00561feca12b732c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -30,11 +30,14 @@ 7 - Nextcloud SMS - fr.unix_experience.owncloud_sms - fr.unix_experience.owncloud_sms.datasync.provider - fr.unix_experience.owncloud_sms.datasync.slowsync_provider - fr.unix_experience.owncloud_sms + E SMS Sync + e.foundation.webdav.eelo + e.foundation.sms_sync.datasync.provider + e.foundation.sms_sender.datasync.provider + e.foundation.sms_backup.datasync.provider + e.foundation.sms_restore.datasync.provider + e.foundation.sms_sync.datasync.slowsync_provider + e.foundation.sms_sync Login logo @@ -44,6 +47,9 @@ SMS - Fast + SMS - Send messages + SMS - Backup messages + SMS - Restore messages Fast Sync frequency SMS - Slow and Secure Secure Slow Sync frequency diff --git a/src/main/res/xml/backup_adapter.xml b/src/main/res/xml/backup_adapter.xml new file mode 100644 index 0000000000000000000000000000000000000000..e5f1a0af286bee0f3efa7f81847f565c1973504b --- /dev/null +++ b/src/main/res/xml/backup_adapter.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/src/main/res/xml/restore_adapter.xml b/src/main/res/xml/restore_adapter.xml new file mode 100644 index 0000000000000000000000000000000000000000..d71f5db625ea980f068ecd393eaa22e81b5973e1 --- /dev/null +++ b/src/main/res/xml/restore_adapter.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/src/main/res/xml/sender_adapter.xml b/src/main/res/xml/sender_adapter.xml new file mode 100644 index 0000000000000000000000000000000000000000..36f3938bd087e63d5114ecccf7b48224f4a574c7 --- /dev/null +++ b/src/main/res/xml/sender_adapter.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file