diff --git a/app/build.gradle b/app/build.gradle index 8603de570..5292144b8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,6 +59,7 @@ dependencies { compile 'com.firebase:firebase-jobdispatcher:0.8.5' compile 'com.android.support:support-annotations:25.3.1' compile 'com.android.support:preference-v7:25.3.1' + compile 'org.sufficientlysecure:openpgp-api:12.0' testCompile 'junit:junit:4.12' testCompile 'org.robolectric:robolectric:3.6.1' diff --git a/app/src/main/java/com/zegoggles/smssync/activity/MainActivity.java b/app/src/main/java/com/zegoggles/smssync/activity/MainActivity.java index a952a8cd8..b68962741 100644 --- a/app/src/main/java/com/zegoggles/smssync/activity/MainActivity.java +++ b/app/src/main/java/com/zegoggles/smssync/activity/MainActivity.java @@ -144,6 +144,7 @@ public void onCreate(Bundle bundle) { if (bundle == null) { showFragment(new MainSettings(), null); } + if (preferences.shouldShowAboutDialog()) { showDialog(ABOUT); } diff --git a/app/src/main/java/com/zegoggles/smssync/activity/fragments/AdvancedSettings.java b/app/src/main/java/com/zegoggles/smssync/activity/fragments/AdvancedSettings.java index 8a2e9d8a5..ab0303676 100644 --- a/app/src/main/java/com/zegoggles/smssync/activity/fragments/AdvancedSettings.java +++ b/app/src/main/java/com/zegoggles/smssync/activity/fragments/AdvancedSettings.java @@ -1,5 +1,6 @@ package com.zegoggles.smssync.activity.fragments; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -47,8 +48,13 @@ import static com.zegoggles.smssync.preferences.Preferences.Keys.IMAP_SETTINGS; import static com.zegoggles.smssync.preferences.Preferences.Keys.MAX_ITEMS_PER_RESTORE; import static com.zegoggles.smssync.preferences.Preferences.Keys.MAX_ITEMS_PER_SYNC; +import static com.zegoggles.smssync.preferences.Preferences.Keys.ENCRYPTION_PROVIDER; +import static com.zegoggles.smssync.preferences.Preferences.Keys.ENCRYPTION_KEYS; import static com.zegoggles.smssync.utils.ListPreferenceHelper.initListPreference; +import org.openintents.openpgp.util.OpenPgpAppPreference; +import org.openintents.openpgp.util.OpenPgpKeyPreference; + public class AdvancedSettings extends SMSBackupPreferenceFragment { public static class Main extends AdvancedSettings { @@ -59,6 +65,67 @@ public void onResume() { } } + public static class Encryption extends AdvancedSettings { + private OpenPgpAppPreference pgp_prov; + private OpenPgpKeyPreference pgp_keys; + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + pgp_prov = (OpenPgpAppPreference) findPreference(ENCRYPTION_PROVIDER.key); + pgp_keys = (OpenPgpKeyPreference) findPreference(ENCRYPTION_KEYS.key); + } + + @Override + public void onResume() { + super.onResume(); + updateEncryption(null); + findPreference(ENCRYPTION_PROVIDER.key) + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + updateEncryption(newValue.toString()); + return true; + } + }); + } + + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (pgp_keys.handleOnActivityResult(requestCode, resultCode, data)) { + // handled by OpenPgpKeyPreference + return; + } + } + + private void updateEncryption(String new_prov) { + boolean enableOtherOptions; + + if (new_prov == null) { //we aren't given a new value, load the old one + new_prov = pgp_prov.getValue(); + } + + if(!new_prov.isEmpty()) { + String prov_simplename = pgp_prov.getEntryByValue(new_prov); + if (prov_simplename == null) { + pgp_prov.setSummary(getString(R.string.ui_encryption_missingprov)); + enableOtherOptions = false; + } else { + pgp_prov.setSummary(getString(R.string.ui_encryption_prov) + prov_simplename); + pgp_keys.setOpenPgpProvider(new_prov); + enableOtherOptions = true; + } + } else { + pgp_prov.setSummary(getString(R.string.ui_encryption_noprov)); + enableOtherOptions = false; + } + + //find other options and set enabled to enableOtherOptions + pgp_keys.setEnabled(enableOtherOptions); + return; + } + } + public static class Backup extends AdvancedSettings { private static final int REQUEST_CALL_LOG_PERMISSIONS = 0; private CheckBoxPreference callLogPreference; diff --git a/app/src/main/java/com/zegoggles/smssync/mail/MessageConverter.java b/app/src/main/java/com/zegoggles/smssync/mail/MessageConverter.java index 4470e8f54..9e10cd925 100644 --- a/app/src/main/java/com/zegoggles/smssync/mail/MessageConverter.java +++ b/app/src/main/java/com/zegoggles/smssync/mail/MessageConverter.java @@ -48,6 +48,8 @@ import static com.zegoggles.smssync.App.LOCAL_LOGV; import static com.zegoggles.smssync.App.TAG; +import org.openintents.openpgp.util.OpenPgpServiceConnection; + public class MessageConverter { private final Context context; private final ThreadHelper threadHelper = new ThreadHelper(); @@ -61,7 +63,8 @@ public MessageConverter(Context context, Preferences preferences, String userEmail, PersonLookup personLookup, - ContactAccessor contactAccessor) { + ContactAccessor contactAccessor, + OpenPgpServiceConnection serviceConnection) { this.context = context; markAsReadType = preferences.getMarkAsReadType(); this.personLookup = personLookup; @@ -84,9 +87,11 @@ public MessageConverter(Context context, this.personLookup, preferences.getMailSubjectPrefix(), allowedIds, + new MmsSupport(this.context.getContentResolver(), this.personLookup), preferences.getCallLogType(), - preferences.getDataTypePreferences()); + preferences.getDataTypePreferences(), + serviceConnection); } private boolean markAsSeen(DataType dataType, Map msgMap) { diff --git a/app/src/main/java/com/zegoggles/smssync/mail/MessageGenerator.java b/app/src/main/java/com/zegoggles/smssync/mail/MessageGenerator.java index 5b27734dd..4e6d9cebb 100644 --- a/app/src/main/java/com/zegoggles/smssync/mail/MessageGenerator.java +++ b/app/src/main/java/com/zegoggles/smssync/mail/MessageGenerator.java @@ -1,6 +1,7 @@ package com.zegoggles.smssync.mail; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.provider.CallLog; import android.provider.Telephony; @@ -20,7 +21,12 @@ import com.zegoggles.smssync.preferences.AddressStyle; import com.zegoggles.smssync.preferences.CallLogTypes; import com.zegoggles.smssync.preferences.DataTypePreferences; +import com.zegoggles.smssync.preferences.Preferences; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Locale; import java.util.Map; @@ -30,6 +36,9 @@ import static com.zegoggles.smssync.App.TAG; import static com.zegoggles.smssync.Consts.MMS_PART; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpServiceConnection; + class MessageGenerator { private static final String ERROR_PARSING_DATE = "error parsing date"; private final Context context; @@ -43,6 +52,8 @@ class MessageGenerator { private final MmsSupport mmsSupport; private final CallLogTypes callLogTypes; private final DataTypePreferences dataTypePreferences; + private final OpenPgpServiceConnection mServiceConnection; + private final Preferences preferences; MessageGenerator(Context context, Address userAddress, @@ -53,7 +64,8 @@ class MessageGenerator { @Nullable ContactGroupIds contactsToBackup, MmsSupport mmsSupport, CallLogTypes callLogTypes, - DataTypePreferences dataTypePreferences) { + DataTypePreferences dataTypePreferences, + OpenPgpServiceConnection serviceConnection) { this.headerGenerator = headerGenerator; this.userAddress = userAddress; this.addressStyle = addressStyle; @@ -63,8 +75,11 @@ class MessageGenerator { this.contactGroupIds = contactsToBackup; this.callFormatter = new CallFormatter(this.context.getResources()); this.mmsSupport = mmsSupport; + // this looks like it should be changed to comply with new style + this.preferences = new Preferences(this.context); this.dataTypePreferences = dataTypePreferences; this.callLogTypes = callLogTypes; + this.mServiceConnection = serviceConnection; } public @Nullable Message messageForDataType(Map msgMap, DataType dataType) throws MessagingException { @@ -85,7 +100,31 @@ class MessageGenerator { final Message msg = new MimeMessage(); msg.setSubject(getSubject(DataType.SMS, record)); - setBody(msg, new TextBody(msgMap.get(Telephony.TextBasedSmsColumns.BODY))); + + String msgTxt = msgMap.get(Telephony.TextBasedSmsColumns.BODY); + + if (mServiceConnection != null) { + Intent data = new Intent(); + ByteArrayOutputStream os; + InputStream is; + data.setAction(OpenPgpApi.ACTION_ENCRYPT); + data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, preferences.getEncryptionKeyID()); + data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); + try { + is = new ByteArrayInputStream(msgTxt.getBytes("UTF-8")); + os = new ByteArrayOutputStream(); + OpenPgpApi api = new OpenPgpApi(this.context, mServiceConnection.getService()); + Intent result = api.executeApi(data, is, os); + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: { + msgTxt = os.toString("UTF-8"); + break; + } + } + } catch (UnsupportedEncodingException e) {} + } + + setBody(msg, new TextBody(msgTxt)); final int messageType = toInt(msgMap.get(Telephony.TextBasedSmsColumns.TYPE)); if (Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX == messageType) { diff --git a/app/src/main/java/com/zegoggles/smssync/preferences/Preferences.java b/app/src/main/java/com/zegoggles/smssync/preferences/Preferences.java index 9eb40d854..4d92df301 100644 --- a/app/src/main/java/com/zegoggles/smssync/preferences/Preferences.java +++ b/app/src/main/java/com/zegoggles/smssync/preferences/Preferences.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.text.TextUtils; import android.support.annotation.StyleRes; import android.util.Log; import com.zegoggles.smssync.App; @@ -38,6 +39,8 @@ import static com.zegoggles.smssync.preferences.Preferences.Keys.CONFIRM_ACTION; import static com.zegoggles.smssync.preferences.Preferences.Keys.DARK_THEME; import static com.zegoggles.smssync.preferences.Preferences.Keys.ENABLE_AUTO_BACKUP; +import static com.zegoggles.smssync.preferences.Preferences.Keys.ENCRYPTION_PROVIDER; +import static com.zegoggles.smssync.preferences.Preferences.Keys.ENCRYPTION_KEYS; import static com.zegoggles.smssync.preferences.Preferences.Keys.FIRST_USE; import static com.zegoggles.smssync.preferences.Preferences.Keys.INCOMING_TIMEOUT_SECONDS; import static com.zegoggles.smssync.preferences.Preferences.Keys.LAST_VERSION_CODE; @@ -86,6 +89,8 @@ public enum Keys { WIFI_ONLY("wifi_only"), REFERENCE_UID("reference_uid"), MAIL_SUBJECT_PREFIX("mail_subject_prefix"), + ENCRYPTION_PROVIDER("openpgp_provider_list"), + ENCRYPTION_KEYS("pgp_keys"), RESTORE_STARRED_ONLY("restore_starred_only"), MARK_AS_READ_TYPES("mark_as_read_types"), MARK_AS_READ_ON_RESTORE("mark_as_read_on_restore"), @@ -226,6 +231,26 @@ public boolean isFirstBackup() { return true; } + public String getEncryptionProvider() { + return preferences.getString(ENCRYPTION_PROVIDER.key, ""); + } + + public long[] getEncryptionKeyID() { + long[] retval = new long[1]; + retval[0] = preferences.getLong(ENCRYPTION_KEYS.key, 0); + return retval; + } + + public boolean isEncryptionAvailable() { + if (TextUtils.isEmpty(getEncryptionProvider())) + return false; + + if (getEncryptionKeyID()[0] == 0) + return false; + + return true; + } + public boolean isFirstUse() { if (isFirstBackup() && !preferences.contains(FIRST_USE.key)) { preferences.edit().putBoolean(FIRST_USE.key, false).commit(); diff --git a/app/src/main/java/com/zegoggles/smssync/service/BackupTask.java b/app/src/main/java/com/zegoggles/smssync/service/BackupTask.java index 5f5307c19..91393d4c1 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/BackupTask.java +++ b/app/src/main/java/com/zegoggles/smssync/service/BackupTask.java @@ -46,6 +46,8 @@ import static com.zegoggles.smssync.service.state.SmsSyncState.FINISHED_BACKUP; import static com.zegoggles.smssync.service.state.SmsSyncState.LOGIN; +import org.openintents.openpgp.util.OpenPgpServiceConnection; + class BackupTask extends AsyncTask { private final SmsBackupService service; private final BackupItemsFetcher fetcher; @@ -55,6 +57,7 @@ class BackupTask extends AsyncTask { private final Preferences preferences; private final ContactAccessor contactAccessor; private final TokenRefresher tokenRefresher; + private final OpenPgpServiceConnection mServiceConnection; BackupTask(@NonNull SmsBackupService service) { final Context context = service.getApplicationContext(); @@ -68,8 +71,16 @@ class BackupTask extends AsyncTask { PersonLookup personLookup = new PersonLookup(service.getContentResolver()); + // bind to pgp service + if (preferences.isEncryptionAvailable()) { + this.mServiceConnection = new OpenPgpServiceConnection(context, preferences.getEncryptionProvider()); + this.mServiceConnection.bindToService(); + } else { + this.mServiceConnection = null; + } + this.contactAccessor = new ContactAccessor(); - this.converter = new MessageConverter(context, service.getPreferences(), authPreferences.getUserEmail(), personLookup, contactAccessor); + this.converter = new MessageConverter(context, service.getPreferences(), authPreferences.getUserEmail(), personLookup, contactAccessor, this.mServiceConnection); if (preferences.isCallLogCalendarSyncEnabled()) { calendarSyncer = new CalendarSyncer( @@ -92,7 +103,8 @@ class BackupTask extends AsyncTask { AuthPreferences authPreferences, Preferences preferences, ContactAccessor accessor, - TokenRefresher refresher) { + TokenRefresher refresher, + OpenPgpServiceConnection serviceConnection) { this.service = service; this.fetcher = fetcher; this.converter = messageConverter; @@ -101,6 +113,7 @@ class BackupTask extends AsyncTask { this.preferences = preferences; this.contactAccessor = accessor; this.tokenRefresher = refresher; + this.mServiceConnection = serviceConnection; } @Override @@ -236,6 +249,9 @@ protected void onProgressUpdate(BackupState... progress) { @Override protected void onPostExecute(BackupState result) { + if (this.mServiceConnection != null) { + this.mServiceConnection.unbindFromService(); + } if (result != null) { post(result); } @@ -244,6 +260,9 @@ protected void onPostExecute(BackupState result) { @Override protected void onCancelled() { + if (this.mServiceConnection != null) { + this.mServiceConnection.unbindFromService(); + } post(transition(CANCELED_BACKUP, null)); App.unregister(this); } diff --git a/app/src/main/java/com/zegoggles/smssync/service/SmsRestoreService.java b/app/src/main/java/com/zegoggles/smssync/service/SmsRestoreService.java index 0d4086321..926c54770 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/SmsRestoreService.java +++ b/app/src/main/java/com/zegoggles/smssync/service/SmsRestoreService.java @@ -83,7 +83,8 @@ protected void handleIntent(final Intent intent) { getPreferences(), getAuthPreferences().getUserEmail(), new PersonLookup(getContentResolver()), - new ContactAccessor() + new ContactAccessor(), + null ); RestoreConfig config = new RestoreConfig( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b20c3fa5c..996d16044 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,6 +326,15 @@ Could not get token from system. Do you want to try the browser authentication method? Select a Google account + + Encryption + Encrypt backed up messages. + Select OpenPGP Provider + No provider selected. + PGP provider:\u0020 + Your chosen PGP provider does not appear to be installed. + Select Keys + Donate Using secure Google Play Store in-app payment. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index a33894976..ff4f7cb38 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -224,6 +224,23 @@ + + + + + >>>>>> upstream/master ); } @@ -228,8 +232,12 @@ private void assertMessage(Message message) { false, groupIds, mmsSupport, +<<<<<<< HEAD + null +======= CallLogTypes.EVERYTHING, dataTypePreferences +>>>>>>> upstream/master ); PersonRecord record = new PersonRecord(1, "Test Testor", "test@test.com", "1234"); Map map = mockMessage("1234", record); diff --git a/app/src/test/java/com/zegoggles/smssync/service/BackupTaskTest.java b/app/src/test/java/com/zegoggles/smssync/service/BackupTaskTest.java index eeaee5be7..24eeb312c 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/BackupTaskTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/BackupTaskTest.java @@ -28,6 +28,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; +import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.robolectric.RuntimeEnvironment; import java.util.EnumSet; @@ -70,6 +71,7 @@ public class BackupTaskTest { @Mock Preferences preferences; @Mock ContactAccessor accessor; @Mock TokenRefresher tokenRefresher; + @Mock OpenPgpServiceConnection serviceConnection; @Before public void before() { initMocks(this); @@ -78,7 +80,7 @@ public class BackupTaskTest { when(service.getState()).thenReturn(state); when(preferences.getDataTypePreferences()).thenReturn(dataTypePreferences); - task = new BackupTask(service, fetcher, converter, syncer, authPreferences, preferences, accessor, tokenRefresher); + task = new BackupTask(service, fetcher, converter, syncer, authPreferences, preferences, accessor, tokenRefresher, serviceConnection); context = RuntimeEnvironment.application; } diff --git a/app/src/test/resources/robolectric.properties b/app/src/test/resources/robolectric.properties index 9daf69250..17a6482e2 100644 --- a/app/src/test/resources/robolectric.properties +++ b/app/src/test/resources/robolectric.properties @@ -1 +1,2 @@ manifest=src/main/AndroidManifest.xml +libraries=../../../target/unpacked-libs/os_openpgp-api_12.0