Commit c183df0f authored by axet's avatar axet 🍄

Merge branch 'smsgate-1.9.4'

parents 4b3ffdf5 1146d901
Pipeline #36875996 passed with stage
in 1 minute and 30 seconds
......@@ -8,8 +8,8 @@ android {
applicationId "com.github.axet.smsgate"
minSdkVersion 9
targetSdkVersion 26
versionCode 212
versionName "1.9.3"
versionCode 213
versionName "1.9.4"
}
packagingOptions {
exclude 'META-INF/LICENSE'
......@@ -39,7 +39,7 @@ android {
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'com.github.axet:android-library:1.27.24' // implementation project(':android-library')
implementation 'com.github.axet:android-library:1.27.25' // implementation project(':android-library')
implementation 'com.android.support:design:25.3.1'
implementation 'com.intellij:annotations:12.0'
implementation 'com.beetstra.jutf7:jutf7:1.0.0'
......
......@@ -176,9 +176,6 @@
<data android:scheme="mmsto" />
</intent-filter>
</activity>
<activity
android:name=".activities.SendSMS"
android:exported="true" />
<!-- Service that delivers messages from the phone "quick response" -->
<service
......
......@@ -41,9 +41,13 @@ import java.util.HashMap;
public class MainActivity extends AppCompatActivity implements DialogInterface.OnDismissListener {
public static String SHARE = MainActivity.class.getCanonicalName() + ".SHARE";
public static String PERMS = MainActivity.class.getCanonicalName() + ".PERMS";
public static final String[] PERMISSIONS = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
public static final int SHARE_PERMS = 1;
public static final int RESULT_PERMS = 2;
private SectionsPagerAdapter mSectionsPagerAdapter;
OnBackHandler backHandler;
......@@ -52,10 +56,19 @@ public class MainActivity extends AppCompatActivity implements DialogInterface.O
TabLayout tabLayout;
AppBarLayout appbar;
Handler handler = new Handler();
Intent permsIntent;
boolean resumeShare;
Bundle shareArgs;
public static void start(Context context, String[] pp, Intent i) {
Intent intent = new Intent(context, MainActivity.class);
intent.setAction(PERMS);
intent.putExtra("perms", pp);
intent.putExtra("intent", i);
context.startActivity(intent);
}
public interface OnBackHandler {
void onBackPressed();
}
......@@ -173,7 +186,7 @@ public class MainActivity extends AppCompatActivity implements DialogInterface.O
shareArgs.putString("text", text);
shareArgs.putParcelableArrayList("uri", uu);
if (Storage.permitted(this, PERMISSIONS, 1)) {
if (Storage.permitted(this, PERMISSIONS, SHARE_PERMS)) {
shareDialog(shareArgs);
}
}
......@@ -184,6 +197,11 @@ public class MainActivity extends AppCompatActivity implements DialogInterface.O
dialog.setArguments(intent.getExtras());
dialog.show(fm, "");
}
if (a.equals(PERMS)) {
Storage.permitted(this, intent.getStringArrayExtra("perms"), RESULT_PERMS);
permsIntent = intent.getParcelableExtra("intent");
}
}
void setupTabs() {
......@@ -357,9 +375,15 @@ public class MainActivity extends AppCompatActivity implements DialogInterface.O
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (Storage.permitted(this, permissions)) {
switch (requestCode) {
case 1:
case SHARE_PERMS:
resumeShare = true;
break;
case RESULT_PERMS:
if (permsIntent != null) {
sendBroadcast(permsIntent);
permsIntent = null;
}
break;
}
} else {
Toast.makeText(this, R.string.not_permitted, Toast.LENGTH_SHORT).show();
......
package com.github.axet.smsgate.activities;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import com.github.axet.smsgate.providers.SMS;
import java.util.Date;
/**
* am start -n com.github.axet.smsgate/.activities.SendSMS -e phone +199988877766 -e msg "hello"
* <p>
* am start -n com.github.axet.smsgate/.activities.SendSMS -e phone 000100 -e msg "b"
*/
public class SendSMS extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle b = getIntent().getExtras();
sendSMS(this, b.getString("phone"), b.getString("msg"), null, null);
finish();
}
public static void sendSMS(Context context, String phone, String msg, Date date, String thread) {
SMS.send(context, phone, msg);
}
}
package com.github.axet.smsgate.app;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Telephony;
import android.telephony.SmsMessage;
import com.zegoggles.smssync.SmsConsts;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
// API19+ writing / deleting sms requires to be default SMS app. Use DefaultSMSPreferenceCompat
public class SmsStorage {
public static final String ID = "_id";
ContentResolver resolver;
public static HashMap<String, Message> getMessagesFromIntent(Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle == null)
return null;
Object[] pdusObj = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[pdusObj.length];
for (int i = 0; i < messages.length; i++) {
if (Build.VERSION.SDK_INT >= 23) {
String format = intent.getStringExtra("format");
messages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
}
}
HashMap<String, Message> map = new HashMap<>();
for (SmsMessage msg : messages) {
String id = msg.getOriginatingAddress() + " " + msg.getTimestampMillis();
Message m = map.get(id);
if (m != null) {
m.body += msg.getMessageBody();
map.put(id, m);
} else {
map.put(id, new InboxMessage(msg.getOriginatingAddress(), msg.getMessageBody()));
}
}
return map;
}
public static Map<String, String> getMessageMap(Cursor cursor) {
final String[] columns = cursor.getColumnNames();
final Map<String, String> map = new HashMap<>(columns.length);
for (String column : columns) {
String value;
try {
final int index = cursor.getColumnIndex(column);
if (index != -1) {
value = cursor.getString(index);
} else {
continue;
}
} catch (SQLiteException ignored) { // this can happen in case of BLOBS in the DB column type checking is API level >= 11
value = "[BLOB]";
}
map.put(column, value);
}
return map;
}
public static Message getMessage(Cursor cursor) {
Message m = new Message();
m.id = cursor.getLong(cursor.getColumnIndex(ID));
m.type = cursor.getInt(cursor.getColumnIndex(Telephony.Sms.TYPE));
m.date = cursor.getLong(cursor.getColumnIndex(Telephony.Sms.DATE));
m.phone = cursor.getString(cursor.getColumnIndex(Telephony.Sms.ADDRESS));
m.body = cursor.getString(cursor.getColumnIndex(Telephony.Sms.BODY));
m.thread = cursor.getString(cursor.getColumnIndex(Telephony.Sms.THREAD_ID));
return m;
}
public static class Message {
public long id;
public long date;
public int type;
public String phone;
public String body;
public String thread;
public Message() {
}
public Message(String p, String b) {
this.phone = p;
this.body = b;
}
}
public static class InboxMessage extends Message {
public InboxMessage(String p, String b) {
super(p, b);
type = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX;
}
}
public static class SentMessage extends Message {
public SentMessage(String p, String b) {
super(p, b);
type = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_SENT;
}
}
public SmsStorage(Context context) {
resolver = context.getContentResolver();
}
public void add(Message m) {
Date date;
if (m.date == 0)
date = new Date();
else
date = new Date(m.date);
ContentValues values = new ContentValues();
values.put(Telephony.Sms.ADDRESS, m.phone);
values.put(Telephony.Sms.BODY, m.body);
Uri uri;
int type = m.type;
if (m instanceof InboxMessage) {
uri = Telephony.Sms.Inbox.CONTENT_URI;
} else if (m instanceof SentMessage) {
uri = Telephony.Sms.Sent.CONTENT_URI;
} else {
throw new RuntimeException("unknown type");
}
values.put(Telephony.Sms.TYPE, type);
values.put(Telephony.Sms.PROTOCOL, 0);
values.put(Telephony.Sms.SERVICE_CENTER, "");
values.put(Telephony.Sms.DATE, date.getTime());
values.put(Telephony.Sms.STATUS, -1);
if (m.thread != null)
values.put(Telephony.Sms.THREAD_ID, m.thread);
values.put(Telephony.Sms.READ, 1);
Uri id = resolver.insert(uri, values);
m.id = ContentUris.parseId(id);
}
public Cursor query(long date) {
return resolver.query(Telephony.Sms.CONTENT_URI, null,
String.format("%s > ?", Telephony.Sms.DATE),
new String[]{String.valueOf(date)}, null);
}
public void delete(long id) {
resolver.delete(Uri.parse(Telephony.Sms.CONTENT_URI + "/" + id), null, null);
}
public void updateAllThreads() {
// thread dates + states might be wrong, we need to force a full update
// unfortunately there's no direct way to do that in the SDK, but passing a
// negative conversation id to delete should to the trick
resolver.delete(Uri.parse(Telephony.Sms.Conversations.CONTENT_URI + "/-1"), null, null);
}
public boolean exists(long date, String phone) {
// just assume equality on date+address+type
Cursor c = resolver.query(Telephony.Sms.CONTENT_URI, new String[]{ID},
String.format("%s = ? AND %s = ?", Telephony.Sms.DATE, Telephony.Sms.ADDRESS),
new String[]{String.valueOf(date), phone}, null);
boolean exists = false;
if (c != null) {
exists = c.getCount() > 0;
c.close();
}
return exists;
}
public ArrayList<Long> deleteThread(int thread) {
Cursor c = resolver.query(Telephony.Sms.CONTENT_URI, new String[]{ID, Telephony.Sms.THREAD_ID},
Telephony.Sms.THREAD_ID + " = ?",
new String[]{Integer.toString(thread)}, null);
if (c != null) {
ArrayList<Long> ids = new ArrayList<>();
while (c.moveToNext()) {
long id = c.getLong(0);
long threadId = c.getLong(1);
if (threadId == thread) {
delete(id);
ids.add(id);
}
}
c.close();
return ids;
}
return null;
}
public ArrayList<Long> deleteOld(long date) {
Cursor c = resolver.query(Telephony.Sms.CONTENT_URI, new String[]{ID},
Telephony.Sms.DATE + " < ?",
new String[]{Long.toString(date)}, null);
if (c != null) {
ArrayList<Long> ids = new ArrayList<>();
while (c.moveToNext()) {
long id = c.getLong(0);
delete(id);
ids.add(id);
}
c.close();
return ids;
}
return null;
}
}
......@@ -69,8 +69,8 @@ public class FirebaseConnectDialog extends AlertDialog {
refresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SMSApplication.from(getContext()).getKeyPair().generate();
SMSApplication.from(getContext()).save();
SMSApplication.from(getContext()).keys.generate();
SMSApplication.from(getContext()).keys.save();
FirebaseService.reset(getContext());
update();
}
......@@ -81,14 +81,13 @@ public class FirebaseConnectDialog extends AlertDialog {
public void onClick(View v) {
final OpenFileDialog.EditTextDialog editTextDialog = new OpenFileDialog.EditTextDialog(getContext());
editTextDialog.setTitle("PrivateKey");
editTextDialog.setText(SMSApplication.from(getContext()).getKeyPair().getSec());
editTextDialog.setText(SMSApplication.from(getContext()).keys.getSec());
editTextDialog.setPositiveButton("OK", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Bitcoin k = SMSApplication.from(getContext()).getKeyPair();
k.loadSec(editTextDialog.getText());
k.loadPub(k.sec);
SMSApplication.from(getContext()).save();
Bitcoin k = SMSApplication.from(getContext()).keys;
k.load(editTextDialog.getText());
SMSApplication.from(getContext()).keys.save();
update();
}
});
......@@ -110,7 +109,7 @@ public class FirebaseConnectDialog extends AlertDialog {
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_EMAIL, "");
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "SMSGate PrivateKey");
emailIntent.putExtra(Intent.EXTRA_TEXT, SMSApplication.from(getContext()).getKeyPair().getSec());
emailIntent.putExtra(Intent.EXTRA_TEXT, SMSApplication.from(getContext()).keys.getSec());
popup.setShareIntent(emailIntent);
popup.show();
......@@ -130,7 +129,7 @@ public class FirebaseConnectDialog extends AlertDialog {
}
public void update() {
mSyncDetailsLabel.setText(SMSApplication.from(getContext()).getKeyPair().getSec());
mSyncDetailsLabel.setText(SMSApplication.from(getContext()).keys.getSec());
SharedPreferences prefs = android.support.v7.preference.PreferenceManager.getDefaultSharedPreferences(getContext());
boolean connected = prefs.getBoolean(SMSApplication.PREF_FIREBASE, false);
......
......@@ -35,6 +35,7 @@ import com.github.axet.smsgate.app.SMSApplication;
import com.github.axet.smsgate.app.ScheduleTime;
import com.github.axet.smsgate.services.CommandsService;
import com.github.axet.androidlibrary.services.DeviceAdmin;
import com.github.axet.smsgate.services.ScheduleService;
import java.util.Calendar;
......@@ -66,17 +67,18 @@ public class RebootDialogFragment extends DialogFragment {
edit.commit();
}
public static void schedule(Context context) {
public static long schedule(Context context) {
ScheduleTime s = getSchedule(context);
s.fired();
saveSchedule(context, s);
Intent intent = (new Intent(context, CommandsService.class));
intent.setAction(CommandsService.REBOOT);
if (s.enabled) {
AlarmManager.setExact(context, s.next, intent);
return s.next;
} else {
AlarmManager.cancel(context, intent);
return 0;
}
}
......@@ -264,6 +266,6 @@ public class RebootDialogFragment extends DialogFragment {
void save() {
// date/time already saved
saveSchedule(getContext(), schedule);
RebootDialogFragment.schedule(getContext());
ScheduleService.startIfEnabled(getContext());
}
}
......@@ -58,7 +58,7 @@ public class ShareIncomingFragment extends DialogFragment {
protected Intent doInBackground(ArrayList<Bundle>... urls) {
ArrayList<Bundle> uris = urls[0];
Bitcoin key = SMSApplication.from(getActivity()).getKeyPair();
Bitcoin key = SMSApplication.from(getActivity()).keys;
Intent intent = new Intent();
ArrayList<Uri> uu = new ArrayList<>();
......
package com.github.axet.smsgate.fragments;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
......@@ -29,14 +26,11 @@ import android.widget.TextView;
import android.widget.Toast;
import com.github.axet.smsgate.R;
import com.github.axet.smsgate.activities.MainActivity;
import com.github.axet.smsgate.app.SMSApplication;
import com.github.axet.smsgate.app.ScheduleSMS;
import com.github.axet.smsgate.dialogs.ScheduleEditDialogFragment;
import com.github.axet.smsgate.services.ScheduleService;
import java.util.List;
import jp.wasabeef.recyclerview.animators.OvershootInLeftAnimator;
/**
......@@ -50,26 +44,6 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
ScheduleEditDialogFragment dialog;
Schedulers schedulers;
public static String RELOAD = SchedulersFragment.class.getCanonicalName() + "_RELOAD";
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String a = intent.getAction();
if (a == null)
return;
if (a.equals(RELOAD)) {
load();
}
}
};
public static void reload(Context context) {
Intent intent = new Intent();
intent.setAction(RELOAD);
context.sendBroadcast(intent);
}
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private final int[] ATTRS = new int[]{android.R.attr.listDivider};
......@@ -127,10 +101,10 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
}
public class Schedulers extends RecyclerView.Adapter<SchedulerViewHolder> {
List<ScheduleSMS> items;
SMSApplication.ScheduleSMSStorage items;
public Schedulers(List<ScheduleSMS> items) {
this.items = items;
public Schedulers() {
this.items = SMSApplication.from(getContext()).items;
}
@Override
......@@ -149,7 +123,7 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
@Override
public void onClick(View v) {
item.enabled = holder.enabled.isChecked();
SMSApplication.save(getActivity(), items);
items.save();
ScheduleService.startIfEnabled(getContext());
}
});
......@@ -201,12 +175,12 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
fm = ((MainActivity) getActivity()).getSupportFragmentManager();
fm = getActivity().getSupportFragmentManager();
app = SMSApplication.from(getActivity());
View rootView = inflater.inflate(R.layout.fragment_schedulers, container, false);
schedulers = new Schedulers(SMSApplication.load(getActivity()));
schedulers = new Schedulers();
list = (RecyclerView) rootView.findViewById(R.id.section_label);
list.setAdapter(schedulers);
......@@ -228,10 +202,6 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
prefs.registerOnSharedPreferenceChangeListener(this);
IntentFilter ff = new IntentFilter();
ff.addAction(RELOAD);
getContext().registerReceiver(receiver, ff);
return rootView;
}
......@@ -300,14 +270,14 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
ScheduleEditDialogFragment.ScheduleResult r = (ScheduleEditDialogFragment.ScheduleResult) dialog;
if (r.delete) {
schedulers.remove(r.pos);
SMSApplication.save(getActivity(), schedulers.items);
schedulers.items.save();
}
if (r.save) {
if (r.pos == -1)
schedulers.add(r.schedule);
else
schedulers.set(r.pos, r.schedule);
SMSApplication.save(getActivity(), schedulers.items);
schedulers.items.save();
}
ScheduleService.startIfEnabled(getContext());
}
......@@ -321,12 +291,9 @@ public class SchedulersFragment extends Fragment implements DialogInterface.OnDi
prefs.unregisterOnSharedPreferenceChangeListener(this);
}
void load() {
schedulers.items = SMSApplication.load(getActivity());
schedulers.notifyDataSetChanged();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.startsWith(SMSApplication.SCHEDULER_ITEM))
schedulers.notifyDataSetChanged();
}
}
......@@ -34,7 +34,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.support.v7.preference.CheckBoxPreference;
......@@ -48,16 +47,15 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.github.axet.androidlibrary.services.WifiKeepService;
import com.github.axet.androidlibrary.widgets.AboutPreferenceCompat;
import com.github.axet.androidlibrary.widgets.OptimizationPreferenceCompat;
import com.github.axet.smsgate.BuildConfig;
import com.github.axet.smsgate.R;
import com.github.axet.smsgate.activities.MainActivity;
import com.github.axet.smsgate.app.SMSApplication;
import com.github.axet.smsgate.services.NotificationListener;
import com.github.axet.smsgate.services.NotificationService;
import com.github.axet.smsgate.services.OnBootReceiver;
import com.github.axet.smsgate.services.ScheduleService;
import com.github.axet.smsgate.services.SmsReplyService;
import com.github.axet.androidlibrary.widgets.AdminPreferenceCompat;
import com.github.axet.smsgate.widgets.ApplicationsPreference;
......@@ -129,6 +127,41 @@ public class SettingsFragment extends PreferenceFragmentCompat implements MainAc
Uri mAuthorizeUri;
BroadcastReceiver receiver;
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof NameFormatPreferenceCompat) {
preference.setSummary(((NameFormatPreferenceCompat) preference).getFormatted(stringValue));
} else if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
} else {
preference.setSummary(stringValue);
}
return true;
}
};
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager.getDefaultSharedPreferences(preference.getContext()).getAll().get(preference.getKey()));
}
@Override
public void onCreatePreferences(Bundle bundle, String s) {
Context context = getActivity();
......@@ -211,6 +244,9 @@ public class SettingsFragment extends PreferenceFragmentCompat implements MainAc
DefaultSMSPreferenceCompat sms = (DefaultSMSPreferenceCompat) findPreference(SMSApplication.PREF_DEFAULTSMS);
sms.onResume();
Preference delete = findPreference(SMSApplication.PREF_DELETE);
bindPreferenceSummaryToValue(delete);
}
@Override
......@@ -849,9 +885,8 @@ public class SettingsFragment extends PreferenceFragmentCompat implements MainAc
if (key.equals(SMSApplication.PREF_REBOOT)) {
((RebootPreferenceCompat) findPreference(SMSApplication.PREF_REBOOT)).updateReboot();
}
if (key.equals(SMSApplication.PREF_WIFIRESTART)) {
WifiKeepService.startIfEnabled(getContext(), sharedPreferences.getBoolean(SMSApplication.PREF_WIFIRESTART, false));
}
if (key.equals(SMSApplication.PREF_WIFIRESTART))
ScheduleService.startIfEnabled(getContext