diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7ace9aa23..5b3d62cd1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,6 +79,11 @@ + + + + diff --git a/app/src/main/java/com/zegoggles/smssync/App.java b/app/src/main/java/com/zegoggles/smssync/App.java index 6f2fd6eaf..71ca9b3d6 100644 --- a/app/src/main/java/com/zegoggles/smssync/App.java +++ b/app/src/main/java/com/zegoggles/smssync/App.java @@ -44,6 +44,8 @@ import com.zegoggles.smssync.receiver.BootReceiver; import com.zegoggles.smssync.receiver.SmsBroadcastReceiver; import com.zegoggles.smssync.service.BackupJobs; +import com.zegoggles.smssync.utils.SimCard; +import com.zegoggles.smssync.utils.SimCardHelper; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; @@ -60,6 +62,8 @@ public class App extends Application { /** Google Play Services present on this device? */ public static boolean gcmAvailable; + public static SimCard[] SimCards; + private Preferences preferences; private BackupJobs backupJobs; @@ -68,6 +72,7 @@ public void onCreate() { super.onCreate(); setupStrictMode(); gcmAvailable = GooglePlayServices.isAvailable(this); + SimCards = SimCardHelper.getSimCards(getApplicationContext()); preferences = new Preferences(this); preferences.migrate(); 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 824fa4048..4a6cf7667 100644 --- a/app/src/main/java/com/zegoggles/smssync/activity/MainActivity.java +++ b/app/src/main/java/com/zegoggles/smssync/activity/MainActivity.java @@ -28,6 +28,7 @@ import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; +import android.Manifest; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -116,6 +117,8 @@ public class MainActivity extends ThemeActivity implements private static final int REQUEST_PERMISSIONS_BACKUP_MANUAL = 4; private static final int REQUEST_PERMISSIONS_BACKUP_MANUAL_SKIP = 5; private static final int REQUEST_PERMISSIONS_BACKUP_SERVICE = 6; + private static final int REQUEST_PERMISSIONS_PHONE = 7; + public static final String EXTRA_PERMISSIONS = "permissions"; private static final String SCREEN_TITLE_RES = "titleRes"; @@ -134,7 +137,8 @@ public void onCreate(Bundle bundle) { setSupportActionBar(toolbar); getSupportFragmentManager().addOnBackStackChangedListener(this); - authPreferences = new AuthPreferences(this); + //XOAuth2 is legacy and therefore not considered for multi-sim (authPreferences only used in this context here) + authPreferences = new AuthPreferences(this, 0); oauth2Client = new OAuth2Client(authPreferences.getOAuth2ClientId()); fallbackAuthIntent = new Intent(this, OAuth2WebAuthActivity.class).setData(oauth2Client.requestUrl()); preferenceTitles = new PreferenceTitles(getResources(), R.xml.preferences); @@ -147,6 +151,7 @@ public void onCreate(Bundle bundle) { } checkDefaultSmsApp(); requestPermissionsIfNeeded(); + requestPhoneStatePermission(); } @Override @@ -309,7 +314,9 @@ public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, Preferen @Override public void onBackStackChanged() { if (getSupportActionBar() == null) return; - getSupportActionBar().setSubtitle(getCurrentTitle()); + @StringRes Integer title = getCurrentTitle(); + if (title == null) return; + getSupportActionBar().setSubtitle(title); getSupportActionBar().setDisplayHomeAsUpEnabled(getSupportFragmentManager().getBackStackEntryCount() > 0); } @@ -318,10 +325,14 @@ public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, Preferen onBackStackChanged(); } - private @StringRes int getCurrentTitle() { + private @StringRes Integer getCurrentTitle() { final int entryCount = getSupportFragmentManager().getBackStackEntryCount(); + final List fragments = getSupportFragmentManager().getFragments(); if (entryCount == 0) { return 0; + } else if (fragments.size() > 0 && fragments.get(fragments.size() - 1) + instanceof com.zegoggles.smssync.activity.fragments.AdvancedSettings.Server) { + return null; } else { final FragmentManager.BackStackEntry entry = getSupportFragmentManager().getBackStackEntryAt(entryCount - 1); return entry.getBreadCrumbTitleRes(); @@ -329,10 +340,10 @@ public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, Preferen } @Subscribe public void performAction(PerformAction action) { - if (authPreferences.isLoginInformationSet()) { + if (atLeastOneLoginInformationSet(this)) { if (action.confirm) { showDialog(CONFIRM_ACTION, new BundleBuilder().putString(ACTION, action.action.name()).build()); - } else if (preferences.isFirstBackup() && action.action == Backup) { + } else if (preferences.isFirstBackup(0) && action.action == Backup) { showDialog(FIRST_SYNC); } else { doPerform(action.action); @@ -463,6 +474,18 @@ private void checkDefaultSmsApp() { } } + private void requestPhoneStatePermission() { + String phonePermission; + if (Build.VERSION.SDK_INT > 29) { + phonePermission=Manifest.permission.READ_PHONE_NUMBERS; + } + else + { + phonePermission=Manifest.permission.READ_PHONE_STATE; + } + ActivityCompat.requestPermissions(this, new String[]{phonePermission}, REQUEST_PERMISSIONS_PHONE); + } + private void requestPermissionsIfNeeded() { final Intent intent = getIntent(); if (intent != null && intent.hasExtra(EXTRA_PERMISSIONS)) { @@ -472,6 +495,13 @@ private void requestPermissionsIfNeeded() { } } + private boolean atLeastOneLoginInformationSet(Context context) { + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (new AuthPreferences(context, settingsId).isLoginInformationSet()) return true; + } + return false; + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); diff --git a/app/src/main/java/com/zegoggles/smssync/activity/StatusPreference.java b/app/src/main/java/com/zegoggles/smssync/activity/StatusPreference.java index ce49bf81d..787f1f376 100644 --- a/app/src/main/java/com/zegoggles/smssync/activity/StatusPreference.java +++ b/app/src/main/java/com/zegoggles/smssync/activity/StatusPreference.java @@ -230,7 +230,8 @@ private void onRestore() { private void onAuthFailed() { statusLabel.setText(R.string.status_auth_failure); - if (new AuthPreferences(getContext()).useXOAuth()) { + ////XOAuth2 is legacy and therefore not considered for multi-sim + if (new AuthPreferences(getContext(), 0).useXOAuth()) { syncDetailsLabel.setText(R.string.status_auth_failure_details_xoauth); } else { syncDetailsLabel.setText(R.string.status_auth_failure_details_plain); @@ -276,7 +277,12 @@ private void finishedRestore(RestoreState newState) { } private void idle() { - syncDetailsLabel.setText(getLastSyncText(preferences.getDataTypePreferences().getMostRecentSyncedDate())); + long mostRecentSyncedDate = 0; + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + long mostRecentSyncedDateForSettingsId = preferences.getDataTypePreferences().getMostRecentSyncedDate(settingsId); + if (mostRecentSyncedDateForSettingsId>mostRecentSyncedDate) mostRecentSyncedDate = mostRecentSyncedDateForSettingsId; + } + syncDetailsLabel.setText(getLastSyncText(mostRecentSyncedDate)); statusLabel.setText(R.string.status_idle); statusLabel.setTextColor(idleColor); statusIcon.setImageDrawable(idle); 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 8564b8e3e..809e18d95 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 @@ -14,6 +14,10 @@ import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.TwoStatePreference; +import androidx.preference.PreferenceScreen; +import androidx.preference.EditTextPreference; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.ActionBar; import com.squareup.otto.Subscribe; import com.zegoggles.smssync.App; @@ -31,12 +35,15 @@ import com.zegoggles.smssync.mail.DataType; import com.zegoggles.smssync.preferences.AuthPreferences; import com.zegoggles.smssync.preferences.DataTypePreferences; +import com.zegoggles.smssync.utils.SimCardHelper; import java.text.DateFormat; import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static android.Manifest.permission.READ_CONTACTS; import static android.Manifest.permission.WRITE_CALENDAR; @@ -83,7 +90,9 @@ public void onResume() { @Override public void onCreatePreferences(Bundle bundle, String rootKey) { super.onCreatePreferences(bundle, rootKey); - authPreferences = new AuthPreferences(getContext()); + + //XOAuth2 is legacy and therefore not considered for multi-sim (authPreferences only used in this context here) + authPreferences = new AuthPreferences(getContext(), 0); connected = findPreference(CONNECTED.key); assert connected != null; connected.setSummaryProvider(new Preference.SummaryProvider() { @@ -128,6 +137,30 @@ public static class Backup extends SMSBackupPreferenceFragment { private static final int REQUEST_CALL_LOG_PERMISSIONS = 0; private CheckBoxPreference callLogPreference; + public void onCreatePreferences(Bundle bundle, String rootKey) { + super.onCreatePreferences(bundle, rootKey); + + PreferenceScreen mainSettings = getPreferenceScreen(); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (settingsId>0) { + EditTextPreference imapFolder = findPreference(DataType.PreferenceKeys.IMAP_FOLDER); + EditTextPreference cloneImapFolder = new EditTextPreference(getContext()); + cloneImapFolder.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_imap_folder_label), settingsId)); + cloneImapFolder.setKey(SimCardHelper.addSettingsId(DataType.PreferenceKeys.IMAP_FOLDER, settingsId)); + cloneImapFolder.setDialogTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_imap_folder_label), settingsId)); + cloneImapFolder.setDialogMessage(getString(R.string.ui_imap_folder_label_dialog_msg)); + cloneImapFolder.setDefaultValue(getString(R.string.imap_folder_default)); + cloneImapFolder.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance()); + insertPreference(imapFolder.getOrder()+1, mainSettings, cloneImapFolder); + } + registerValidImapFolderCheck(settingsId); + } + + EditTextPreference imapFolder = findPreference(DataType.PreferenceKeys.IMAP_FOLDER); + imapFolder.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(imapFolder.getTitle().toString(), 0)); + imapFolder.setDialogTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_imap_folder_label), 0)); + } + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -142,7 +175,7 @@ public void onResume() { updateBackupContactGroupLabelFromPref(); updateLastBackupTimes(); initGroups(); - registerValidImapFolderCheck(); + findPreference(MAX_ITEMS_PER_SYNC.key) .setOnPreferenceChangeListener(new OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -204,9 +237,12 @@ private void updateMaxItemsPerSync(String newValue) { private void updateLastBackupTimes() { for (DataType type : DataType.values()) { - findPreference(type.backupEnabledPreference).setSummary( - getLastSyncText(preferences.getDataTypePreferences().getMaxSyncedDate(type)) - ); + long maxSyncedDate = 0; + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + long maxSyncedDateForSettingsId = preferences.getDataTypePreferences().getMaxSyncedDate(type, settingsId); + if (maxSyncedDateForSettingsId>maxSyncedDate) maxSyncedDate = maxSyncedDateForSettingsId; + } + findPreference(type.backupEnabledPreference).setSummary(getLastSyncText(maxSyncedDate)); } } @@ -224,8 +260,8 @@ private void updateBackupContactGroupLabelFromPref() { getString(R.string.ui_backup_contact_group_label)); } - private void registerValidImapFolderCheck() { - findPreference(SMS.folderPreference) + private void registerValidImapFolderCheck(Integer settingsId) { + findPreference(SimCardHelper.addSettingsId(SMS.folderPreference, settingsId)) .setOnPreferenceChangeListener(new OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, final Object newValue) { return checkValidImapFolder(getFragmentManager(), newValue.toString()); @@ -247,15 +283,51 @@ private void initGroups() { public static class CallLog extends SMSBackupPreferenceFragment { private static final int REQUEST_CALENDAR_ACCESS = 0; private CheckBoxPreference enabledPreference; - private ListPreference calendarPreference; - private Preference folderPreference; + + public void onCreatePreferences(Bundle bundle, String rootKey) { + super.onCreatePreferences(bundle, rootKey); + + PreferenceScreen mainSettings = getPreferenceScreen(); + + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (settingsId>0) { + addImapFolder(mainSettings, settingsId); + addCalendar(mainSettings, settingsId); + } + registerValidCallLogFolderCheck(settingsId); + } + + EditTextPreference imapFolder = findPreference(DataType.PreferenceKeys.IMAP_FOLDER_CALLLOG); + imapFolder.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(imapFolder.getTitle().toString(), 0)); + imapFolder.setDialogTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_imap_folder_calllog_label), 0)); + } + + private void addImapFolder(PreferenceScreen mainSettings, Integer settingsId) { + EditTextPreference imapFolder = findPreference(DataType.PreferenceKeys.IMAP_FOLDER_CALLLOG); + EditTextPreference cloneImapFolder = new EditTextPreference(getContext()); + cloneImapFolder.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_imap_folder_calllog_label), settingsId)); + cloneImapFolder.setKey(SimCardHelper.addSettingsId(DataType.PreferenceKeys.IMAP_FOLDER_CALLLOG, settingsId)); + cloneImapFolder.setDialogTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_imap_folder_calllog_label), settingsId)); + cloneImapFolder.setDialogMessage(getString(R.string.ui_imap_folder_calllog_label_dialog_msg)); + cloneImapFolder.setDefaultValue(getString(R.string.imap_folder_calllog_default)); + cloneImapFolder.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance()); + insertPreference(imapFolder.getOrder()+1, mainSettings, cloneImapFolder); + } + + private void addCalendar(PreferenceScreen mainSettings, Integer settingsId) { + ListPreference calendarPreference = findPreference(CALLLOG_SYNC_CALENDAR.key); + ListPreference cloneCalendarPreference = new ListPreference(getContext()); + cloneCalendarPreference.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_backup_calllog_sync_calendar_label), settingsId)); + cloneCalendarPreference.setSummary(calendarPreference.getSummary()); + cloneCalendarPreference.setKey(SimCardHelper.addSettingsId(CALLLOG_SYNC_CALENDAR.key, settingsId)); + cloneCalendarPreference.setDefaultValue("-1"); + insertPreference(calendarPreference.getOrder()+1, mainSettings, cloneCalendarPreference); + } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); enabledPreference = findPreference(CALLLOG_SYNC_CALENDAR_ENABLED.key); - calendarPreference = findPreference(CALLLOG_SYNC_CALENDAR.key); - folderPreference = findPreference(CALLLOG.folderPreference); } @Override @@ -264,7 +336,6 @@ public void onResume() { initCalendars(); updateCallLogCalendarLabelFromPref(); - registerValidCallLogFolderCheck(); registerCalendarSyncEnabledCallback(); addPreferenceListener(CALLLOG_BACKUP_AFTER_CALL.key); @@ -304,19 +375,25 @@ private boolean needCalendarPermission() { } private void updateCallLogCalendarLabelFromPref() { - calendarPreference.setTitle(calendarPreference.getEntry() != null ? calendarPreference.getEntry() : - getString(R.string.ui_backup_calllog_sync_calendar_label)); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + ListPreference calendarPreference = findPreference(SimCardHelper.addSettingsId(CALLLOG_SYNC_CALENDAR.key,settingsId)); + calendarPreference.setTitle(calendarPreference.getEntry() != null ? calendarPreference.getEntry() : SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.ui_backup_calllog_sync_calendar_label), settingsId)); + } } private void initCalendars() { if (needCalendarPermission()) return; CalendarAccessor calendars = CalendarAccessor.Get.instance(getContext().getContentResolver()); - initListPreference(calendarPreference, calendars.getCalendars(), false); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + ListPreference calendarPreference = findPreference(SimCardHelper.addSettingsId(CALLLOG_SYNC_CALENDAR.key,settingsId)); + initListPreference(calendarPreference, calendars.getCalendars(), false); + } } - private void registerValidCallLogFolderCheck() { - folderPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + private void registerValidCallLogFolderCheck(Integer settingsId) { + findPreference(SimCardHelper.addSettingsId(CALLLOG.folderPreference, settingsId)) + .setOnPreferenceChangeListener(new OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, final Object newValue) { return checkValidImapFolder(getFragmentManager(), newValue.toString()); } @@ -346,18 +423,77 @@ private void updateMaxItemsPerRestore(String newValue) { public static class Server extends SMSBackupPreferenceFragment { private AuthPreferences authPreferences; + private Integer settingsId; @Override public void onCreatePreferences(Bundle bundle, String rootKey) { + settingsId = 0; + Pattern p = Pattern.compile("^(.*)_(\\d*)$"); + Matcher m = p.matcher(rootKey); + if (m.find()) { + settingsId=Integer.parseInt(m.group(2)); + rootKey=m.group(1); + } + super.onCreatePreferences(bundle, rootKey); - authPreferences = new AuthPreferences(getContext()); + + authPreferences = new AuthPreferences(getContext(), settingsId); + + if (settingsId > 0) { + setPreferenceScreen(cloneSettings()); + insertTakeOverCheckBox(); + + setServerState(authPreferences.getTakeOver()); + + findPreference(SimCardHelper.addSettingsId(AuthPreferences.SERVER_TAKEOVER, settingsId)) + .setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + setServerState((Boolean)newValue); + return true; + } + }); + } + } + + private void setServerState(Boolean takeOver) { + Boolean state = !takeOver; + findPreference(SimCardHelper.addSettingsId(AuthPreferences.SERVER_ADDRESS, settingsId)).setEnabled(state); + findPreference(SimCardHelper.addSettingsId(AuthPreferences.IMAP_USER, settingsId)).setEnabled(state); + findPreference(SimCardHelper.addSettingsId(AuthPreferences.IMAP_PASSWORD, settingsId)).setEnabled(state); + findPreference(SimCardHelper.addSettingsId(AuthPreferences.SERVER_PROTOCOL, settingsId)).setEnabled(state); + findPreference(SimCardHelper.addSettingsId(AuthPreferences.SERVER_TRUST_ALL_CERTIFICATES, settingsId)).setEnabled(state); + } + + private void insertTakeOverCheckBox() { + PreferenceScreen preferenceScreen = getPreferenceScreen(); + + CheckBoxPreference checkbox = new CheckBoxPreference(getContext()); + checkbox.setTitle(R.string.ui_server_takeover); + checkbox.setSummary(R.string.ui_server_takeover_summary); + checkbox.setDefaultValue(true); + checkbox.setKey(SimCardHelper.addSettingsId(AuthPreferences.SERVER_TAKEOVER, settingsId)); + insertPreference(0, preferenceScreen, checkbox); + } + + private PreferenceScreen cloneSettings() { + PreferenceScreen originalScreen = getPreferenceScreen(); + PreferenceScreen copyScreen = getPreferenceManager().createPreferenceScreen(getContext()); + + while(originalScreen.getPreferenceCount() > 0) { + Preference pref = originalScreen.getPreference(0); + pref.setKey(pref.getKey()+"_"+settingsId.toString()); + + originalScreen.removePreference(pref); + copyScreen.addPreference(pref); + } + return copyScreen; } @Override public void onResume() { super.onResume(); - findPreference(AuthPreferences.IMAP_PASSWORD) + findPreference(SimCardHelper.addSettingsId(AuthPreferences.IMAP_PASSWORD, settingsId)) .setOnPreferenceChangeListener(new OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { authPreferences.setImapPassword(newValue.toString()); @@ -365,6 +501,15 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } }); } + + @Override public void onStart() { + super.onStart(); + + ActionBar actionBar = ((AppCompatActivity)getActivity()).getSupportActionBar(); + if ( actionBar == null) return; + actionBar.setSubtitle(SimCardHelper.addPhoneNumberIfMultiSim(getString(R.string.imap_settings), settingsId)); + actionBar.setDisplayHomeAsUpEnabled(true); + } } static void updateMaxItems(Preference preference, int currentValue, String newValue) { diff --git a/app/src/main/java/com/zegoggles/smssync/activity/fragments/MainSettings.java b/app/src/main/java/com/zegoggles/smssync/activity/fragments/MainSettings.java index c89b73dc2..151e8e9aa 100644 --- a/app/src/main/java/com/zegoggles/smssync/activity/fragments/MainSettings.java +++ b/app/src/main/java/com/zegoggles/smssync/activity/fragments/MainSettings.java @@ -7,6 +7,7 @@ import androidx.preference.CheckBoxPreference; import androidx.preference.ListPreference; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.squareup.otto.Subscribe; import com.zegoggles.smssync.App; @@ -19,6 +20,7 @@ import com.zegoggles.smssync.activity.events.SettingsResetEvent; import com.zegoggles.smssync.mail.DataType; import com.zegoggles.smssync.preferences.AuthPreferences; +import com.zegoggles.smssync.utils.SimCardHelper; import java.util.ArrayList; import java.util.List; @@ -37,10 +39,27 @@ public class MainSettings extends SMSBackupPreferenceFragment { @Override public void onCreatePreferences(Bundle bundle, String rootKey) { super.onCreatePreferences(bundle, rootKey); - authPreferences = new AuthPreferences(getContext()); - findPreference(AdvancedSettings.Server.class.getName()).setSummaryProvider(new Preference.SummaryProvider() { + + //XOAuth2 is legacy and therefore not considered for multi-sim (authPreferences only used in this context here) + authPreferences = new AuthPreferences(getContext(), 0); + + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (settingsId>0) { + addAdvancedSettingsForMultipleSimCards(settingsId); + } + + setSummaryForAdvancedServerSettings(settingsId); + } + + PreferenceScreen advancedSettings = findPreference(AdvancedSettings.Server.class.getName()); + advancedSettings.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(advancedSettings.getTitle().toString(), 0)); + } + + private void setSummaryForAdvancedServerSettings(final Integer settingsId) { + findPreference(SimCardHelper.addSettingsId(AdvancedSettings.Server.class.getName(), settingsId)).setSummaryProvider(new Preference.SummaryProvider() { @Override public CharSequence provideSummary(Preference preference) { + authPreferences = new AuthPreferences(getContext(), settingsId); if (authPreferences.usePlain() && authPreferences.isLoginInformationSet()) { return authPreferences.toString(); } else { @@ -54,7 +73,6 @@ public CharSequence provideSummary(Preference preference) { public void onStart() { super.onStart(); App.register(this); - } @Override public void onDestroy() { @@ -170,4 +188,27 @@ public void userDonationState(State state) { Log.w(TAG, e); } } + + private void addAdvancedSettingsForMultipleSimCards(Integer settingsId) { + PreferenceScreen advancedSettings = findPreference(AdvancedSettings.Server.class.getName()); + PreferenceScreen mainSettings = getPreferenceScreen(); + if (advancedSettings != null) { + try { + PreferenceScreen clone = clonePreferenceScreen(advancedSettings, settingsId); + clone.setTitle(SimCardHelper.addPhoneNumberIfMultiSim(clone.getTitle().toString(), settingsId)); + insertPreference(advancedSettings.getOrder()+1, mainSettings, clone); + } catch (Exception e) { + Log.w(TAG, "couldn't add advanced settings more than once"); + } + + } + } + + private PreferenceScreen clonePreferenceScreen(PreferenceScreen advancedSettings, Integer settingsId) throws Exception { + PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(getContext()); + screen.setTitle(advancedSettings.getTitle()); + screen.setKey(SimCardHelper.addSettingsId(advancedSettings.getKey(), settingsId)); + screen.setFragment(advancedSettings.getFragment()); + return screen; + } } diff --git a/app/src/main/java/com/zegoggles/smssync/activity/fragments/SMSBackupPreferenceFragment.java b/app/src/main/java/com/zegoggles/smssync/activity/fragments/SMSBackupPreferenceFragment.java index a22eb7eda..355297454 100644 --- a/app/src/main/java/com/zegoggles/smssync/activity/fragments/SMSBackupPreferenceFragment.java +++ b/app/src/main/java/com/zegoggles/smssync/activity/fragments/SMSBackupPreferenceFragment.java @@ -5,12 +5,15 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceGroup; import com.zegoggles.smssync.App; import com.zegoggles.smssync.R; import com.zegoggles.smssync.activity.events.AutoBackupSettingsChangedEvent; import com.zegoggles.smssync.preferences.Preferences; +import java.util.HashMap; + public abstract class SMSBackupPreferenceFragment extends PreferenceFragmentCompat { protected Preferences preferences; private Handler handler; @@ -42,4 +45,29 @@ public void run() { }); } } + + protected void insertPreference(int position, PreferenceGroup preferenceList, Preference item) { + HashMap listItems = new HashMap(); + int prefCount = preferenceList.getPreferenceCount(); + for(int i = 0; i < prefCount; i++) { + Preference preference = preferenceList.getPreference(i); + listItems.put(preference.getOrder(), preference); + } + + Integer cnt = 0; + for (Integer i : listItems.keySet()) { + Preference preference = listItems.get(i); + if (cnt checkPermissions(Context context) { } public static class PreferenceKeys { - static final String IMAP_FOLDER = "imap_folder"; - static final String IMAP_FOLDER_CALLLOG = "imap_folder_calllog"; + public static final String IMAP_FOLDER = "imap_folder"; + public static final String IMAP_FOLDER_CALLLOG = "imap_folder_calllog"; static final String BACKUP_SMS = "backup_sms"; static final String BACKUP_MMS = "backup_mms"; 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 678201f9a..e69a2a627 100644 --- a/app/src/main/java/com/zegoggles/smssync/mail/MessageConverter.java +++ b/app/src/main/java/com/zegoggles/smssync/mail/MessageConverter.java @@ -50,6 +50,7 @@ public class MessageConverter { private final Context context; + private final Preferences preferences; private final ThreadHelper threadHelper = new ThreadHelper(); private final MarkAsReadTypes markAsReadType; @@ -63,6 +64,7 @@ public MessageConverter(Context context, PersonLookup personLookup, ContactAccessor contactAccessor) { this.context = context; + this.preferences = preferences; markAsReadType = preferences.getMarkAsReadType(); this.personLookup = personLookup; markAsReadOnRestore = preferences.getMarkAsReadOnRestore(); @@ -108,11 +110,11 @@ private boolean markAsSeen(DataType dataType, Map msgMap) { } } - public @NonNull ConversionResult convertMessages(final Cursor cursor, DataType dataType) + public @NonNull ConversionResult convertMessages(final Cursor cursor, DataType dataType, Integer settingsId) throws MessagingException { final Map msgMap = getMessageMap(cursor); - final Message m = messageGenerator.messageForDataType(msgMap, dataType); + final Message m = messageGenerator.messageForDataType(msgMap, dataType, settingsId); final ConversionResult result = new ConversionResult(dataType); if (m != null) { m.setFlag(Flag.SEEN, markAsSeen(dataType, msgMap)); @@ -123,7 +125,7 @@ private boolean markAsSeen(DataType dataType, Map msgMap) { } - public @NonNull ContentValues messageToContentValues(final Message message) + public @NonNull ContentValues messageToContentValues(Integer settingsId, final Message message) throws IOException, MessagingException { if (message == null) throw new MessagingException("message is null"); @@ -148,6 +150,9 @@ private boolean markAsSeen(DataType dataType, Map msgMap) { values.put(Telephony.TextBasedSmsColumns.THREAD_ID, threadHelper.getThreadId(context, address)); values.put(Telephony.TextBasedSmsColumns.READ, markAsReadOnRestore ? "1" : Headers.get(message, Headers.READ)); + if (App.SimCards.length > 1) { + values.put(Telephony.TextBasedSmsColumns.SUBSCRIPTION_ID, settingsId); + } break; case CALLLOG: values.put(CallLog.Calls.NUMBER, Headers.get(message, Headers.ADDRESS)); @@ -155,6 +160,9 @@ private boolean markAsSeen(DataType dataType, Map msgMap) { values.put(CallLog.Calls.DATE, Headers.get(message, Headers.DATE)); values.put(CallLog.Calls.DURATION, Long.valueOf(Headers.get(message, Headers.DURATION))); values.put(CallLog.Calls.NEW, 0); + if (App.SimCards.length > 1) { + values.put(CallLog.Calls.PHONE_ACCOUNT_ID, getSimCardNumberOrIccId(settingsId)); + } PersonRecord record = personLookup.lookupPerson(Headers.get(message, Headers.ADDRESS)); if (!record.isUnknown()) { @@ -170,6 +178,15 @@ private boolean markAsSeen(DataType dataType, Map msgMap) { return values; } + private String getSimCardNumberOrIccId(Integer settingsId) { + if (preferences.getUseIccIdForRestore()) { + return App.SimCards[settingsId].IccId; + } else { + Integer simCardNumber = settingsId + 1; + return simCardNumber.toString(); + } + } + public DataType getDataType(Message message) throws MessagingException { final String dataTypeHeader = Headers.get(message, Headers.DATATYPE); if (dataTypeHeader == null) { 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 5250a03ab..01528d7c0 100644 --- a/app/src/main/java/com/zegoggles/smssync/mail/MessageGenerator.java +++ b/app/src/main/java/com/zegoggles/smssync/mail/MessageGenerator.java @@ -67,16 +67,16 @@ class MessageGenerator { this.callLogTypes = callLogTypes; } - public @Nullable Message messageForDataType(Map msgMap, DataType dataType) throws MessagingException { + public @Nullable Message messageForDataType(Map msgMap, DataType dataType, Integer settingsId) throws MessagingException { switch (dataType) { - case SMS: return messageFromMapSms(msgMap); - case MMS: return messageFromMapMms(msgMap); - case CALLLOG: return messageFromMapCallLog(msgMap); + case SMS: return messageFromMapSms(msgMap, settingsId); + case MMS: return messageFromMapMms(msgMap, settingsId); + case CALLLOG: return messageFromMapCallLog(msgMap, settingsId); default: return null; } } - private @Nullable Message messageFromMapSms(Map msgMap) throws MessagingException { + private @Nullable Message messageFromMapSms(Map msgMap, Integer settingsId) throws MessagingException { final String address = msgMap.get(Telephony.TextBasedSmsColumns.ADDRESS); if (TextUtils.isEmpty(address)) return null; @@ -84,7 +84,7 @@ class MessageGenerator { if (!includePersonInBackup(record, DataType.SMS)) return null; final Message msg = new MimeMessage(); - msg.setSubject(getSubject(DataType.SMS, record)); + msg.setSubject(getSubject(DataType.SMS, record, settingsId)); setBody(msg, new TextBody(msgMap.get(Telephony.TextBasedSmsColumns.BODY))); final int messageType = toInt(msgMap.get(Telephony.TextBasedSmsColumns.TYPE)); @@ -110,7 +110,7 @@ class MessageGenerator { return msg; } - private @Nullable Message messageFromMapMms(Map msgMap) throws MessagingException { + private @Nullable Message messageFromMapMms(Map msgMap, Integer settingsId) throws MessagingException { if (LOCAL_LOGV) Log.v(TAG, "messageFromMapMms(" + msgMap + ")"); final Uri mmsUri = Uri.withAppendedPath(Consts.MMS_PROVIDER, msgMap.get(Telephony.BaseMmsColumns._ID)); @@ -125,7 +125,7 @@ class MessageGenerator { } final Message msg = new MimeMessage(); - msg.setSubject(getSubject(DataType.MMS, details.getRecipient())); + msg.setSubject(getSubject(DataType.MMS, details.getRecipient(), settingsId)); if (details.inbound) { // msg_box == MmsConsts.MESSAGE_BOX_INBOX does not work @@ -155,7 +155,7 @@ class MessageGenerator { return msg; } - private @Nullable Message messageFromMapCallLog(Map msgMap) throws MessagingException { + private @Nullable Message messageFromMapCallLog(Map msgMap, Integer settingsId) throws MessagingException { final String address = msgMap.get(CallLog.Calls.NUMBER); final int callType = toInt(msgMap.get(CallLog.Calls.TYPE)); @@ -167,7 +167,7 @@ class MessageGenerator { if (!includePersonInBackup(record, DataType.CALLLOG)) return null; final Message msg = new MimeMessage(); - msg.setSubject(getSubject(DataType.CALLLOG, record)); + msg.setSubject(getSubject(DataType.CALLLOG, record, settingsId)); switch (callType) { case CallLog.Calls.OUTGOING_TYPE: @@ -204,9 +204,9 @@ class MessageGenerator { return msg; } - private String getSubject(@NonNull DataType type, @NonNull PersonRecord record) { + private String getSubject(@NonNull DataType type, @NonNull PersonRecord record, Integer settingsId) { return prefix ? - String.format(Locale.ENGLISH, "[%s] %s", dataTypePreferences.getFolder(type), record.getName()) : + String.format(Locale.ENGLISH, "[%s] %s", dataTypePreferences.getFolder(type, settingsId), record.getName()) : context.getString(type.withField, record.getName()); } diff --git a/app/src/main/java/com/zegoggles/smssync/preferences/AuthPreferences.java b/app/src/main/java/com/zegoggles/smssync/preferences/AuthPreferences.java index afb020228..09cd12083 100644 --- a/app/src/main/java/com/zegoggles/smssync/preferences/AuthPreferences.java +++ b/app/src/main/java/com/zegoggles/smssync/preferences/AuthPreferences.java @@ -12,6 +12,7 @@ import com.zegoggles.smssync.R; import com.zegoggles.smssync.auth.OAuth2Client; import com.zegoggles.smssync.auth.TokenRefresher; +import com.zegoggles.smssync.utils.SimCardHelper; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -25,8 +26,11 @@ public class AuthPreferences { private static final String UTF_8 = "UTF-8"; private final Context context; private final SharedPreferences preferences; + private final Integer settingsId; private SharedPreferences credentials; + public static final String SERVER_TAKEOVER = "server_takeover"; + public static final String SERVER_AUTHENTICATION = "server_authentication"; private static final String OAUTH2_USER = "oauth2_user"; @@ -43,9 +47,9 @@ public class AuthPreferences { /** * Preference key containing the server protocol */ - private static final String SERVER_PROTOCOL = "server_protocol"; + public static final String SERVER_PROTOCOL = "server_protocol"; - private static final String SERVER_TRUST_ALL_CERTIFICATES = "server_trust_all_certificates"; + public static final String SERVER_TRUST_ALL_CERTIFICATES = "server_trust_all_certificates"; /** * IMAP URI. @@ -64,9 +68,11 @@ public class AuthPreferences { private static final String DEFAULT_SERVER_ADDRESS = "imap.gmail.com:993"; private static final String DEFAULT_SERVER_PROTOCOL = "+ssl+"; - public AuthPreferences(Context context) { + public AuthPreferences(Context context, Integer settingsId) { this.context = context.getApplicationContext(); this.preferences = PreferenceManager.getDefaultSharedPreferences(context); + //settingsId has no effect on XOAuth2 (deprecated) + this.settingsId = settingsId; } public String getOauth2Token() { @@ -117,11 +123,11 @@ public String getOAuth2ClientId() { } public void setImapPassword(String s) { - getCredentials().edit().putString(IMAP_PASSWORD, s).commit(); + getCredentials().edit().putString(addSettingsId(IMAP_PASSWORD), s).commit(); } public void setImapUser(String s) { - preferences.edit().putString(IMAP_USER, s).commit(); + preferences.edit().putString(addSettingsId(IMAP_USER), s).commit(); } @SuppressWarnings("deprecation") @@ -185,16 +191,32 @@ public String getStoreUri() { } } + private String addSettingsId(String stringWithoutSettingsId) { + if (getTakeOver()) { + return SimCardHelper.addSettingsId(stringWithoutSettingsId, 0); + } else { + return SimCardHelper.addSettingsId(stringWithoutSettingsId, settingsId); + } + } + + public Boolean getTakeOver() { + if (settingsId == 0) { + return false; + } else { + return preferences.getBoolean(SimCardHelper.addSettingsId(SERVER_TAKEOVER, settingsId), true); + } + } + private String getServerAddress() { - return preferences.getString(SERVER_ADDRESS, DEFAULT_SERVER_ADDRESS); + return preferences.getString(addSettingsId(SERVER_ADDRESS), DEFAULT_SERVER_ADDRESS); } private String getServerProtocol() { - return preferences.getString(SERVER_PROTOCOL, DEFAULT_SERVER_PROTOCOL); + return preferences.getString(addSettingsId(SERVER_PROTOCOL), DEFAULT_SERVER_PROTOCOL); } public boolean isTrustAllCertificates() { - return preferences.getBoolean(SERVER_TRUST_ALL_CERTIFICATES, false); + return preferences.getBoolean(addSettingsId(SERVER_TRUST_ALL_CERTIFICATES), false); } private String formatUri(AuthType authType, String serverProtocol, String username, String password, String serverAddress) { @@ -226,15 +248,15 @@ private SharedPreferences getCredentials() { } public String getServername() { - return preferences.getString(SERVER_ADDRESS, null); + return preferences.getString(addSettingsId(SERVER_ADDRESS), null); } public String getImapUsername() { - return preferences.getString(IMAP_USER, null); + return preferences.getString(addSettingsId(IMAP_USER), null); } private String getImapPassword() { - return getCredentials().getString(IMAP_PASSWORD, null); + return getCredentials().getString(addSettingsId(IMAP_PASSWORD), null); } /** @@ -281,8 +303,8 @@ void migrate() { if ("+ssl".equals(getServerProtocol()) || "+tls".equals(getServerProtocol())) { preferences.edit() - .putBoolean(SERVER_TRUST_ALL_CERTIFICATES, true) - .putString(SERVER_PROTOCOL, getServerProtocol()+"+") + .putBoolean(addSettingsId(SERVER_TRUST_ALL_CERTIFICATES), true) + .putString(addSettingsId(SERVER_PROTOCOL), getServerProtocol()+"+") .commit(); } } diff --git a/app/src/main/java/com/zegoggles/smssync/preferences/DataTypePreferences.java b/app/src/main/java/com/zegoggles/smssync/preferences/DataTypePreferences.java index 67440bf54..e9da277fe 100644 --- a/app/src/main/java/com/zegoggles/smssync/preferences/DataTypePreferences.java +++ b/app/src/main/java/com/zegoggles/smssync/preferences/DataTypePreferences.java @@ -2,6 +2,8 @@ import android.content.SharedPreferences; import com.zegoggles.smssync.mail.DataType; +import com.zegoggles.smssync.utils.SimCardHelper; +import com.zegoggles.smssync.App; import java.util.ArrayList; import java.util.EnumSet; @@ -49,15 +51,15 @@ public EnumSet enabled() { return enabledTypes.isEmpty() ? EnumSet.noneOf(DataType.class) : EnumSet.copyOf(enabledTypes); } - public String getFolder(DataType dataType) { - return sharedPreferences.getString(dataType.folderPreference, dataType.defaultFolder); + public String getFolder(DataType dataType, Integer settingsId) { + return sharedPreferences.getString(SimCardHelper.addSettingsId(dataType.folderPreference, settingsId), dataType.defaultFolder); } /** * @return returns the last synced date in milliseconds (epoch) */ - public long getMaxSyncedDate(DataType dataType) { - final long maxSynced = sharedPreferences.getLong(dataType.maxSyncedPreference, MAX_SYNCED_DATE); + public long getMaxSyncedDate(DataType dataType, Integer settingsId) { + final long maxSynced = sharedPreferences.getLong(SimCardHelper.addSettingsId(dataType.maxSyncedPreference, settingsId), MAX_SYNCED_DATE); if (dataType == MMS && maxSynced > 0) { return maxSynced * 1000L; } else { @@ -65,21 +67,23 @@ public long getMaxSyncedDate(DataType dataType) { } } - public boolean setMaxSyncedDate(DataType dataType, long max) { - return sharedPreferences.edit().putLong(dataType.maxSyncedPreference, max).commit(); + public boolean setMaxSyncedDate(DataType dataType, long max, Integer settingsId) { + return sharedPreferences.edit().putLong(SimCardHelper.addSettingsId(dataType.maxSyncedPreference, settingsId), max).commit(); } - public long getMostRecentSyncedDate() { + public long getMostRecentSyncedDate(Integer settingsId) { return Math.max(Math.max( - getMaxSyncedDate(DataType.SMS), - getMaxSyncedDate(DataType.CALLLOG)), - getMaxSyncedDate(DataType.MMS)); + getMaxSyncedDate(DataType.SMS, settingsId), + getMaxSyncedDate(DataType.CALLLOG, settingsId)), + getMaxSyncedDate(DataType.MMS, settingsId)); } public void clearLastSyncData() { SharedPreferences.Editor editor = sharedPreferences.edit(); - for (DataType type : DataType.values()) { - editor.remove(type.maxSyncedPreference); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + for (DataType type : DataType.values()) { + editor.remove(SimCardHelper.addSettingsId(type.maxSyncedPreference, settingsId)); + } } editor.commit(); } diff --git a/app/src/main/java/com/zegoggles/smssync/preferences/Defaults.java b/app/src/main/java/com/zegoggles/smssync/preferences/Defaults.java index aab9b0046..4417a44e8 100644 --- a/app/src/main/java/com/zegoggles/smssync/preferences/Defaults.java +++ b/app/src/main/java/com/zegoggles/smssync/preferences/Defaults.java @@ -23,6 +23,7 @@ class Defaults { public static final int MAX_ITEMS_PER_SYNC = -1; public static final int MAX_ITEMS_PER_RESTORE = -1; public static final boolean MARK_AS_READ_ON_RESTORE = true; + public static final boolean USE_ICC_ID_FOR_RESTORE = true; private Defaults() {} } 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 a57518738..9bf650d5b 100644 --- a/app/src/main/java/com/zegoggles/smssync/preferences/Preferences.java +++ b/app/src/main/java/com/zegoggles/smssync/preferences/Preferences.java @@ -26,6 +26,7 @@ import com.zegoggles.smssync.R; import com.zegoggles.smssync.contacts.ContactGroup; import com.zegoggles.smssync.mail.DataType; +import com.zegoggles.smssync.utils.SimCardHelper; import java.util.Locale; @@ -44,6 +45,7 @@ import static com.zegoggles.smssync.preferences.Preferences.Keys.LAST_VERSION_CODE; import static com.zegoggles.smssync.preferences.Preferences.Keys.MAIL_SUBJECT_PREFIX; import static com.zegoggles.smssync.preferences.Preferences.Keys.MARK_AS_READ_ON_RESTORE; +import static com.zegoggles.smssync.preferences.Preferences.Keys.USE_ICC_ID_FOR_RESTORE; import static com.zegoggles.smssync.preferences.Preferences.Keys.MARK_AS_READ_TYPES; import static com.zegoggles.smssync.preferences.Preferences.Keys.MAX_ITEMS_PER_RESTORE; import static com.zegoggles.smssync.preferences.Preferences.Keys.MAX_ITEMS_PER_SYNC; @@ -90,6 +92,7 @@ public enum 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"), + USE_ICC_ID_FOR_RESTORE("use_icc_id_for_restore"), THIRD_PARTY_INTEGRATION("third_party_integration"), APP_LOG("app_log"), APP_LOG_DEBUG("app_log_debug"), @@ -128,8 +131,8 @@ public ContactGroup getBackupContactGroup() { return new ContactGroup(getStringAsInt(BACKUP_CONTACT_GROUP, -1)); } - public boolean isCallLogCalendarSyncEnabled() { - return getCallLogCalendarId() >= 0 && + public boolean isCallLogCalendarSyncEnabled(Integer settingsId) { + return getCallLogCalendarId(settingsId) >= 0 && preferences.getBoolean(CALLLOG_SYNC_CALENDAR_ENABLED.key, false); } @@ -137,8 +140,8 @@ public boolean isCallLogBackupAfterCallEnabled() { return preferences.getBoolean(CALLLOG_BACKUP_AFTER_CALL.key, false); } - public int getCallLogCalendarId() { - return getStringAsInt(CALLLOG_SYNC_CALENDAR, -1); + public int getCallLogCalendarId(Integer settingsId) { + return getStringAsInt(SimCardHelper.addSettingsId(CALLLOG_SYNC_CALENDAR.key, settingsId), -1); } public CallLogTypes getCallLogType() { @@ -214,13 +217,17 @@ public boolean getMarkAsReadOnRestore() { return preferences.getBoolean(MARK_AS_READ_ON_RESTORE.key, Defaults.MARK_AS_READ_ON_RESTORE); } + public boolean getUseIccIdForRestore() { + return preferences.getBoolean(USE_ICC_ID_FOR_RESTORE.key, Defaults.USE_ICC_ID_FOR_RESTORE); + } + public AddressStyle getEmailAddressStyle() { return getDefaultType(preferences, Keys.EMAIL_ADDRESS_STYLE.key, AddressStyle.class, AddressStyle.NAME); } - public boolean isFirstBackup() { + public boolean isFirstBackup(Integer settingsId) { for (DataType type : DataType.values()) { - if (preferences.contains(type.maxSyncedPreference)) { + if (preferences.contains(SimCardHelper.addSettingsId(type.maxSyncedPreference, settingsId))) { return false; } } @@ -228,7 +235,7 @@ public boolean isFirstBackup() { } public boolean isFirstUse() { - if (isFirstBackup() && !preferences.contains(FIRST_USE.key)) { + if (isFirstBackup(0) && !preferences.contains(FIRST_USE.key)) { preferences.edit().putBoolean(FIRST_USE.key, false).commit(); return true; } else { @@ -292,7 +299,9 @@ public void setUseOldScheduler(boolean enabled) { } public void migrate() { - new AuthPreferences(context).migrate(); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + new AuthPreferences(context, settingsId).migrate(); + } } static > T getDefaultType(SharedPreferences preferences, String pref, Class tClazz, T defaultType) { diff --git a/app/src/main/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiver.java b/app/src/main/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiver.java index 6a176a6b8..46fc97e58 100644 --- a/app/src/main/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiver.java +++ b/app/src/main/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiver.java @@ -26,6 +26,7 @@ import com.zegoggles.smssync.preferences.Preferences; import com.zegoggles.smssync.service.BackupJobs; import com.zegoggles.smssync.utils.AppLog; +import com.zegoggles.smssync.App; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; @@ -59,8 +60,8 @@ private boolean shouldSchedule(Context context) { final Preferences preferences = getPreferences(context); final boolean autoBackupEnabled = preferences.isAutoBackupEnabled(); - final boolean loginInformationSet = getAuthPreferences(context).isLoginInformationSet(); - final boolean firstBackup = preferences.isFirstBackup(); + final boolean loginInformationSet = atLeastOneLoginInformationSet(context); + final boolean firstBackup = preferences.isFirstBackup(0); final boolean schedule = (autoBackupEnabled && loginInformationSet && !firstBackup); @@ -90,7 +91,10 @@ protected Preferences getPreferences(Context context) { return new Preferences(context); } - protected AuthPreferences getAuthPreferences(Context context) { - return new AuthPreferences(context); + protected boolean atLeastOneLoginInformationSet(Context context) { + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (new AuthPreferences(context, settingsId).isLoginInformationSet()) return true; + } + return false; } } diff --git a/app/src/main/java/com/zegoggles/smssync/service/BackupConfig.java b/app/src/main/java/com/zegoggles/smssync/service/BackupConfig.java index 6e98e83a0..bba632e7a 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/BackupConfig.java +++ b/app/src/main/java/com/zegoggles/smssync/service/BackupConfig.java @@ -6,9 +6,10 @@ import com.zegoggles.smssync.mail.DataType; import java.util.EnumSet; +import java.util.List; public class BackupConfig { - public final BackupImapStore imapStore; + public final List imapStores; public final int currentTry; public final int maxItemsPerSync; public final ContactGroup groupToBackup; @@ -16,18 +17,18 @@ public class BackupConfig { public final boolean debug; public final EnumSet typesToBackup; - BackupConfig(@NonNull BackupImapStore imapStore, + BackupConfig(@NonNull List imapStores, int currentTry, int maxItemsPerSync, @NonNull ContactGroup groupToBackup, @NonNull BackupType backupType, @NonNull EnumSet typesToBackup, boolean debug) { - if (imapStore == null) throw new IllegalArgumentException("need imapstore"); + if (imapStores == null) throw new IllegalArgumentException("need imapstores"); if (typesToBackup == null || typesToBackup.isEmpty()) throw new IllegalArgumentException("need to specify types to backup"); if (currentTry < 0) throw new IllegalArgumentException("currentTry < 0"); - this.imapStore = imapStore; + this.imapStores = imapStores; this.currentTry = currentTry; this.maxItemsPerSync = maxItemsPerSync; this.groupToBackup = groupToBackup; @@ -36,8 +37,8 @@ public class BackupConfig { this.typesToBackup = typesToBackup; } - public BackupConfig retryWithStore(BackupImapStore store) { - return new BackupConfig(store, currentTry + 1, + public BackupConfig retryWithStore(List stores) { + return new BackupConfig(stores, currentTry + 1, maxItemsPerSync, groupToBackup, backupType, @@ -47,8 +48,8 @@ public BackupConfig retryWithStore(BackupImapStore store) { @Override public String toString() { return "BackupConfig{" + - "imap=" + imapStore + - ", currentTry=" + currentTry + + GetImapString() + + "currentTry=" + currentTry + ", maxItemsPerSync=" + maxItemsPerSync + ", groupToBackup=" + groupToBackup + ", backupType=" + backupType + @@ -56,4 +57,14 @@ public BackupConfig retryWithStore(BackupImapStore store) { ", typesToBackup=" + typesToBackup + '}'; } + + private String GetImapString() { + String imapStoreString = ""; + Integer cnt = 0; + for(BackupImapStore store: imapStores) { + imapStoreString += "imap" + cnt.toString() + "=" + store.toString() + ", "; + cnt++; + } + return imapStoreString; + } } diff --git a/app/src/main/java/com/zegoggles/smssync/service/BackupItemsFetcher.java b/app/src/main/java/com/zegoggles/smssync/service/BackupItemsFetcher.java index 8c1fc2cf1..7c99cdbe4 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/BackupItemsFetcher.java +++ b/app/src/main/java/com/zegoggles/smssync/service/BackupItemsFetcher.java @@ -27,9 +27,9 @@ public class BackupItemsFetcher { this.resolver = resolver; } - public @NonNull Cursor getItemsForDataType(DataType dataType, ContactGroupIds group, int max) { + public @NonNull Cursor getItemsForDataType(DataType dataType, ContactGroupIds group, Integer settingsId, int max) { if (LOCAL_LOGV) Log.v(TAG, "getItemsForDataType(type=" + dataType + ", max=" + max + ")"); - return performQuery(queryBuilder.buildQueryForDataType(dataType, group, max)); + return performQuery(queryBuilder.buildQueryForDataType(dataType, group, settingsId, max)); } /** diff --git a/app/src/main/java/com/zegoggles/smssync/service/BackupQueryBuilder.java b/app/src/main/java/com/zegoggles/smssync/service/BackupQueryBuilder.java index 88b31fb98..d4f2acfb3 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/BackupQueryBuilder.java +++ b/app/src/main/java/com/zegoggles/smssync/service/BackupQueryBuilder.java @@ -11,6 +11,7 @@ import com.zegoggles.smssync.contacts.ContactGroupIds; import com.zegoggles.smssync.mail.DataType; import com.zegoggles.smssync.preferences.DataTypePreferences; +import com.zegoggles.smssync.App; import java.util.Locale; import java.util.Set; @@ -60,15 +61,25 @@ static class Query { } } - public @Nullable Query buildQueryForDataType(DataType type, @Nullable ContactGroupIds groupIds, int max) { + public @Nullable Query buildQueryForDataType(DataType type, @Nullable ContactGroupIds groupIds, Integer settingsId, int max) { switch (type) { - case SMS: return getQueryForSMS(groupIds, max); - case MMS: return getQueryForMMS(groupIds, max); - case CALLLOG: return getQueryForCallLog(max); + case SMS: return getQueryForSMS(groupIds, settingsId, max); + case MMS: return getQueryForMMS(groupIds, settingsId, max); + case CALLLOG: return getQueryForCallLog(settingsId, max); default: return null; } } + private String getSimCardNumber(Integer settingsId) { + //simCardNumber starts with 1, whereas settingsId is 0-based + Integer simCardNumber = settingsId + 1; + return simCardNumber.toString(); + } + + private String getIccId(Integer settingsId) { + return App.SimCards[settingsId].IccId; + } + public @Nullable Query buildMostRecentQueryForDataType(DataType type) { switch (type) { case MMS: @@ -97,23 +108,26 @@ static class Query { } } - private Query getQueryForSMS(@Nullable ContactGroupIds groupIds, int max) { + private Query getQueryForSMS(@Nullable ContactGroupIds groupIds, Integer settingsId, int max) { return new Query(Consts.SMS_PROVIDER, null, String.format(Locale.ENGLISH, - "%s > ? AND %s <> ? %s", + "%s > ? AND %s <> ? %s AND %s = ?", Telephony.TextBasedSmsColumns.DATE, Telephony.TextBasedSmsColumns.TYPE, - groupSelection(SMS, groupIds)).trim(), + groupSelection(SMS, groupIds), + Telephony.TextBasedSmsColumns.SUBSCRIPTION_ID + ).trim(), new String[] { - String.valueOf(preferences.getMaxSyncedDate(SMS)), - String.valueOf(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT) + String.valueOf(preferences.getMaxSyncedDate(SMS, settingsId)), + String.valueOf(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT), + getSimCardNumber(settingsId) }, max); } - private Query getQueryForMMS(@Nullable ContactGroupIds group, int max) { - long maxSynced = preferences.getMaxSyncedDate(MMS); + private Query getQueryForMMS(@Nullable ContactGroupIds group, Integer settingsId, int max) { + long maxSynced = preferences.getMaxSyncedDate(MMS, settingsId); if (maxSynced > 0) { // NB: max synced date is stored in seconds since epoch in database maxSynced = (long) (maxSynced / 1000d); @@ -121,24 +135,29 @@ private Query getQueryForMMS(@Nullable ContactGroupIds group, int max) { return new Query( Consts.MMS_PROVIDER, null, - String.format(Locale.ENGLISH, "%s > ? AND %s <> ? %s", + String.format(Locale.ENGLISH, "%s > ? AND %s <> ? %s AND %s = ?", Telephony.BaseMmsColumns.DATE, Telephony.BaseMmsColumns.MESSAGE_TYPE, - groupSelection(DataType.MMS, group)).trim(), + groupSelection(DataType.MMS, group), + Telephony.BaseMmsColumns.SUBSCRIPTION_ID + ).trim(), new String[] { String.valueOf(maxSynced), - MmsConsts.DELIVERY_REPORT + MmsConsts.DELIVERY_REPORT, + getSimCardNumber(settingsId) }, max); } - private Query getQueryForCallLog(int max) { + private Query getQueryForCallLog(Integer settingsId, int max) { return new Query( Consts.CALLLOG_PROVIDER, CALLLOG_PROJECTION, - String.format(Locale.ENGLISH, "%s > ?", CallLog.Calls.DATE), + String.format(Locale.ENGLISH, "%s > ? AND (%s = ? OR %s = ?)", CallLog.Calls.DATE, CallLog.Calls.PHONE_ACCOUNT_ID, CallLog.Calls.PHONE_ACCOUNT_ID), new String[] { - String.valueOf(preferences.getMaxSyncedDate(CALLLOG)) + String.valueOf(preferences.getMaxSyncedDate(CALLLOG, settingsId)), + getSimCardNumber(settingsId), + getIccId(settingsId) }, max); } 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 72eb0dd4a..920d5847d 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/BackupTask.java +++ b/app/src/main/java/com/zegoggles/smssync/service/BackupTask.java @@ -14,6 +14,7 @@ import com.zegoggles.smssync.R; import com.zegoggles.smssync.auth.OAuth2Client; import com.zegoggles.smssync.auth.TokenRefreshException; +import com.zegoggles.smssync.service.exception.RequiresLoginException; import com.zegoggles.smssync.auth.TokenRefresher; import com.zegoggles.smssync.calendar.CalendarAccessor; import com.zegoggles.smssync.contacts.ContactAccessor; @@ -31,6 +32,8 @@ import java.util.List; import java.util.Locale; +import java.util.HashMap; + import static com.zegoggles.smssync.App.LOCAL_LOGV; import static com.zegoggles.smssync.App.TAG; @@ -52,7 +55,7 @@ class BackupTask extends AsyncTask { private final SmsBackupService service; private final BackupItemsFetcher fetcher; private final MessageConverter converter; - private final CalendarSyncer calendarSyncer; + private final HashMap calendarSyncerList = new HashMap(); private final AuthPreferences authPreferences; private final Preferences preferences; private final ContactAccessor contactAccessor; @@ -73,16 +76,15 @@ class BackupTask extends AsyncTask { this.contactAccessor = new ContactAccessor(); this.converter = new MessageConverter(context, service.getPreferences(), authPreferences.getUserEmail(), personLookup, contactAccessor); - if (preferences.isCallLogCalendarSyncEnabled()) { - calendarSyncer = new CalendarSyncer( - CalendarAccessor.Get.instance(service.getContentResolver()), - preferences.getCallLogCalendarId(), - personLookup, - new CallFormatter(context.getResources()) - ); - - } else { - calendarSyncer = null; + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (preferences.isCallLogCalendarSyncEnabled(settingsId)) { + calendarSyncerList.put(settingsId, new CalendarSyncer( + CalendarAccessor.Get.instance(service.getContentResolver()), + preferences.getCallLogCalendarId(settingsId), + personLookup, + new CallFormatter(context.getResources()) + )); + } } this.tokenRefresher = new TokenRefresher(service, new OAuth2Client(authPreferences.getOAuth2ClientId()), authPreferences); } @@ -98,7 +100,7 @@ class BackupTask extends AsyncTask { this.service = service; this.fetcher = fetcher; this.converter = messageConverter; - this.calendarSyncer = syncer; + this.calendarSyncerList.put(0, syncer); this.authPreferences = authPreferences; this.preferences = preferences; this.contactAccessor = accessor; @@ -139,48 +141,80 @@ private BackupState acquireLocksAndBackup(BackupConfig config) { } private BackupState fetchAndBackupItems(BackupConfig config) { - BackupCursors cursors = null; + HashMap cursorList = new HashMap(); + Exception lastException = null; try { final ContactGroupIds groupIds = contactAccessor.getGroupContactIds(service.getContentResolver(), config.groupToBackup); + final Context context = service.getApplicationContext(); - cursors = new BulkFetcher(fetcher).fetch(config.typesToBackup, groupIds, config.maxItemsPerSync); - final int itemsToSync = cursors.count(); - - if (itemsToSync > 0) { - appLog(R.string.app_log_backup_messages, cursors.count(SMS), cursors.count(MMS), cursors.count(CALLLOG)); - if (config.debug) { - appLog(R.string.app_log_backup_messages_with_config, config); + int itemsToSync = 0; + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + if (!(new AuthPreferences(context, settingsId).isLoginInformationSet())) { + lastException = new RequiresLoginException(); + continue; } - return backupCursors(cursors, config.imapStore, config.backupType, itemsToSync); - } else { - appLog(R.string.app_log_skip_backup_no_items); + BackupCursors cursors = new BulkFetcher(fetcher).fetch(config.typesToBackup, groupIds, settingsId, config.maxItemsPerSync); + itemsToSync += cursors.count(); + if (cursors.count() > 0) { + appLog(R.string.app_log_backup_messages, cursors.count(SMS), cursors.count(MMS), cursors.count(CALLLOG), App.SimCards[settingsId]); + if (config.debug) { + appLog(R.string.app_log_backup_messages_with_config, config); + } + cursorList.put(settingsId, cursors); + } + } - if (preferences.isFirstBackup()) { - // If this is the first backup we need to write something to MAX_SYNCED_DATE - // such that we know that we've performed a backup before. - preferences.getDataTypePreferences().setMaxSyncedDate(SMS, MAX_SYNCED_DATE); - preferences.getDataTypePreferences().setMaxSyncedDate(MMS, MAX_SYNCED_DATE); + int[] result = new int[2]; + result[0] = itemsToSync; + result[1] = 0; + for (int settingsId : cursorList.keySet()) { + BackupCursors cursors = cursorList.get(settingsId); + try { + if (cursors.count() > 0) { + result = backupCursors(settingsId, cursors, config.imapStores.get(settingsId), config.backupType, result[0], result[1]); + } else { + appLog(R.string.app_log_skip_backup_no_items); + + if (preferences.isFirstBackup(settingsId)) { + // If this is the first backup we need to write something to MAX_SYNCED_DATE + // such that we know that we've performed a backup before. + preferences.getDataTypePreferences().setMaxSyncedDate(SMS, MAX_SYNCED_DATE, settingsId); + preferences.getDataTypePreferences().setMaxSyncedDate(MMS, MAX_SYNCED_DATE, settingsId); + } + } + } catch (XOAuth2AuthenticationFailedException e) { + try { + return handleAuthError(config, e); + } catch (XOAuth2AuthenticationFailedException eInner) { + lastException = eInner; + } + } catch (AuthenticationFailedException e) { + lastException = e; + } catch (MessagingException e) { + lastException = e; + } catch (SecurityException e) { + lastException = e; } + } + if (lastException != null) return transition(ERROR, lastException); + + if (itemsToSync > 0) { + return new BackupState(FINISHED_BACKUP, result[1], result[0], config.backupType, null, null); + } else { Log.i(TAG, "Nothing to do."); return transition(FINISHED_BACKUP, null); } - } catch (XOAuth2AuthenticationFailedException e) { - return handleAuthError(config, e); - } catch (AuthenticationFailedException e) { - return transition(ERROR, e); - } catch (MessagingException e) { - return transition(ERROR, e); - } catch (SecurityException e) { - return transition(ERROR, e); } finally { - if (cursors != null) { - cursors.close(); + if (cursorList != null) { + for (BackupCursors cursors : cursorList.values()) { + cursors.close(); + } } } } - private BackupState handleAuthError(BackupConfig config, XOAuth2AuthenticationFailedException e) { + private BackupState handleAuthError(BackupConfig config, XOAuth2AuthenticationFailedException e) throws XOAuth2AuthenticationFailedException { if (e.getStatus() == 400) { appLogDebug("need to perform xoauth2 token refresh"); if (config.currentTry < 1) { @@ -189,7 +223,7 @@ private BackupState handleAuthError(BackupConfig config, XOAuth2AuthenticationFa // we got a new token, let's handleAuthError one more time - we need to pass in a new store object // since the auth params on it are immutable appLogDebug("token refreshed, retrying"); - return fetchAndBackupItems(config.retryWithStore(service.getBackupImapStore())); + return fetchAndBackupItems(config.retryWithStore(service.getBackupImapStores())); } catch (MessagingException ignored) { Log.w(TAG, ignored); } catch (TokenRefreshException refreshException) { @@ -201,14 +235,16 @@ private BackupState handleAuthError(BackupConfig config, XOAuth2AuthenticationFa } else { appLogDebug("unexpected xoauth status code " + e.getStatus()); } - return transition(ERROR, e); + throw e; } private BackupState skip(Iterable types) { appLog(R.string.app_log_skip_backup_skip_messages); for (DataType type : types) { try { - preferences.getDataTypePreferences().setMaxSyncedDate(type, fetcher.getMostRecentTimestamp(type)); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + preferences.getDataTypePreferences().setMaxSyncedDate(type, fetcher.getMostRecentTimestamp(type), settingsId); + } } catch (SecurityException e ) { return new BackupState(ERROR, 0, 0, MANUAL, type, e); } @@ -255,7 +291,7 @@ private void post(BackupState state) { App.post(state); } - private BackupState backupCursors(BackupCursors cursors, BackupImapStore store, BackupType backupType, int itemsToSync) + private int[] backupCursors(Integer settingsId, BackupCursors cursors, BackupImapStore store, BackupType backupType, int itemsToSync, int backedUpItems) throws MessagingException { Log.i(TAG, String.format(Locale.ENGLISH, "Starting backup (%d messages)", itemsToSync)); publish(LOGIN); @@ -263,12 +299,11 @@ private BackupState backupCursors(BackupCursors cursors, BackupImapStore store, try { publish(CALC); - int backedUpItems = 0; while (!isCancelled() && cursors.hasNext()) { BackupCursors.CursorAndType cursor = cursors.next(); if (LOCAL_LOGV) Log.v(TAG, "backing up: " + cursor); - ConversionResult result = converter.convertMessages(cursor.cursor, cursor.type); + ConversionResult result = converter.convertMessages(cursor.cursor, cursor.type, settingsId); if (!result.isEmpty()) { List messages = result.getMessages(); @@ -277,12 +312,13 @@ private BackupState backupCursors(BackupCursors cursors, BackupImapStore store, messages.size(), cursor.type)); } - store.getFolder(cursor.type, preferences.getDataTypePreferences()).appendMessages(messages); + store.getFolder(cursor.type, preferences.getDataTypePreferences(), settingsId).appendMessages(messages); - if (cursor.type == CALLLOG && calendarSyncer != null) { - calendarSyncer.syncCalendar(result); + if (cursor.type == CALLLOG && calendarSyncerList.containsKey(settingsId)) { + calendarSyncerList.get(settingsId).syncCalendar(result); } - preferences.getDataTypePreferences().setMaxSyncedDate(cursor.type, result.getMaxDate()); + + preferences.getDataTypePreferences().setMaxSyncedDate(cursor.type, result.getMaxDate(), settingsId); backedUpItems += messages.size(); } else { Log.w(TAG, "no messages converted"); @@ -292,10 +328,11 @@ private BackupState backupCursors(BackupCursors cursors, BackupImapStore store, publishProgress(new BackupState(BACKUP, backedUpItems, itemsToSync, backupType, cursor.type, null)); } - return new BackupState(FINISHED_BACKUP, - backedUpItems, - itemsToSync, - backupType, null, null); + int[] retVal = new int[2]; + + retVal[0] = itemsToSync; + retVal[1] = backedUpItems; + return retVal; } finally { store.closeFolders(); } diff --git a/app/src/main/java/com/zegoggles/smssync/service/BulkFetcher.java b/app/src/main/java/com/zegoggles/smssync/service/BulkFetcher.java index 4ab8cc23f..3d2e22f79 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/BulkFetcher.java +++ b/app/src/main/java/com/zegoggles/smssync/service/BulkFetcher.java @@ -18,12 +18,13 @@ public BulkFetcher(BackupItemsFetcher itemsFetcher) { public @NonNull BackupCursors fetch(final @NonNull EnumSet types, final @Nullable ContactGroupIds groups, + final Integer settingsId, final int maxItems) { int max = maxItems; BackupCursors cursors = new BackupCursors(); for (DataType type : types) { - Cursor cursor = itemsFetcher.getItemsForDataType(type, groups, max); + Cursor cursor = itemsFetcher.getItemsForDataType(type, groups, settingsId, max); cursors.add(type, cursor); if (max > 0) { diff --git a/app/src/main/java/com/zegoggles/smssync/service/RestoreConfig.java b/app/src/main/java/com/zegoggles/smssync/service/RestoreConfig.java index 8834741cb..79b4edeb9 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/RestoreConfig.java +++ b/app/src/main/java/com/zegoggles/smssync/service/RestoreConfig.java @@ -1,6 +1,7 @@ package com.zegoggles.smssync.service; import com.zegoggles.smssync.mail.BackupImapStore; +import java.util.List; public class RestoreConfig { final int tries; @@ -9,9 +10,9 @@ public class RestoreConfig { final boolean restoreOnlyStarred; final int maxRestore; final int currentRestoredItem; - final BackupImapStore imapStore; + final List imapStores; - public RestoreConfig(BackupImapStore imapStore, + public RestoreConfig(List imapStores, int tries, boolean restoreSms, boolean restoreCallLog, @@ -20,7 +21,7 @@ public RestoreConfig(BackupImapStore imapStore, int currentRestoredItem) { this.tries = tries; - this.imapStore = imapStore; + this.imapStores = imapStores; this.restoreSms = restoreSms; this.restoreCallLog = restoreCallLog; this.restoreOnlyStarred = restoreOnlyStarred; @@ -28,9 +29,9 @@ public RestoreConfig(BackupImapStore imapStore, this.currentRestoredItem = currentRestoredItem; } - public RestoreConfig retryWithStore(int currentItem, BackupImapStore backupImapStore) { + public RestoreConfig retryWithStore(int currentItem, List backupImapStores) { return new RestoreConfig( - backupImapStore, + backupImapStores, tries + 1, restoreSms, restoreCallLog, @@ -48,7 +49,17 @@ public RestoreConfig retryWithStore(int currentItem, BackupImapStore backupImapS ", restoreOnlyStarred=" + restoreOnlyStarred + ", maxRestore=" + maxRestore + ", currentRestoredItem=" + currentRestoredItem + - ", imapStore=" + imapStore + + GetImapString() + '}'; } + + private String GetImapString() { + String imapStoreString = ""; + Integer cnt = 0; + for(BackupImapStore store: imapStores) { + imapStoreString += ", imapStore" + cnt.toString() + "=" + store.toString(); + cnt++; + } + return imapStoreString; + } } diff --git a/app/src/main/java/com/zegoggles/smssync/service/RestoreTask.java b/app/src/main/java/com/zegoggles/smssync/service/RestoreTask.java index 37dc492c2..a395f5536 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/RestoreTask.java +++ b/app/src/main/java/com/zegoggles/smssync/service/RestoreTask.java @@ -20,17 +20,20 @@ import com.zegoggles.smssync.Consts; import com.zegoggles.smssync.auth.TokenRefreshException; import com.zegoggles.smssync.auth.TokenRefresher; +import com.zegoggles.smssync.auth.OAuth2Client; import com.zegoggles.smssync.mail.BackupImapStore; import com.zegoggles.smssync.mail.DataType; import com.zegoggles.smssync.mail.MessageConverter; import com.zegoggles.smssync.preferences.Preferences; import com.zegoggles.smssync.service.state.RestoreState; import com.zegoggles.smssync.service.state.SmsSyncState; +import com.zegoggles.smssync.preferences.AuthPreferences; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.HashMap; import java.util.List; import java.util.Set; @@ -57,16 +60,18 @@ class RestoreTask extends AsyncTask { private final MessageConverter converter; private final TokenRefresher tokenRefresher; private final Preferences preferences; + private final AuthPreferences authPreferences; RestoreTask(SmsRestoreService service, MessageConverter converter, - ContentResolver resolver, - TokenRefresher tokenRefresher) { + ContentResolver resolver) { this.service = service; + this.authPreferences = service.getAuthPreferences(); this.converter = converter; this.resolver = resolver; - this.tokenRefresher = tokenRefresher; this.preferences = service.getPreferences(); + + this.tokenRefresher = new TokenRefresher(service, new OAuth2Client(authPreferences.getOAuth2ClientId()), authPreferences); } @Override @@ -95,66 +100,93 @@ protected void onPreExecute() { } private RestoreState restore(RestoreConfig config) { - final BackupImapStore imapStore = config.imapStore; - + int itemsToRestoreCount = 0; + Exception lastException = null; + HashMap> msgList = new HashMap>(); int currentRestoredItem = config.currentRestoredItem; - try { - publishProgress(LOGIN); - imapStore.checkSettings(); - publishProgress(CALC); + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + final BackupImapStore imapStore = config.imapStores.get(settingsId); - final List msgs = new ArrayList(); - - if (config.restoreSms) { - msgs.addAll(imapStore.getFolder(SMS, preferences.getDataTypePreferences()).getMessages(config.maxRestore, config.restoreOnlyStarred, null)); - } - if (config.restoreCallLog) { - msgs.addAll(imapStore.getFolder(CALLLOG, preferences.getDataTypePreferences()).getMessages(config.maxRestore, config.restoreOnlyStarred, null)); - } + try { + publishProgress(LOGIN); + imapStore.checkSettings(); - final int itemsToRestoreCount = config.maxRestore <= 0 ? msgs.size() : Math.min(msgs.size(), config.maxRestore); + publishProgress(CALC); - if (itemsToRestoreCount > 0) { - for (; currentRestoredItem < itemsToRestoreCount && !isCancelled(); currentRestoredItem++) { - DataType dataType = importMessage(msgs.get(currentRestoredItem)); + if (config.restoreSms) { + List msgs = new ArrayList(); + msgs.addAll(imapStore.getFolder(SMS, preferences.getDataTypePreferences(), settingsId).getMessages(config.maxRestore, config.restoreOnlyStarred, null)); + msgList.put(settingsId, msgs); + } + if (config.restoreCallLog) { + List msgs = new ArrayList(); + msgs.addAll(imapStore.getFolder(CALLLOG, preferences.getDataTypePreferences(), settingsId).getMessages(config.maxRestore, config.restoreOnlyStarred, null)); + msgList.put(settingsId, msgs); + } - msgs.set(currentRestoredItem, null); // help gc - publishProgress(new RestoreState(RESTORE, currentRestoredItem, itemsToRestoreCount, 0, 0, dataType, null)); - if (currentRestoredItem % 50 == 0) { - //clear cache periodically otherwise SD card fills up - service.clearCache(); - } + itemsToRestoreCount += config.maxRestore <= 0 ? msgList.get(settingsId).size() : Math.min(msgList.get(settingsId).size(), config.maxRestore); + } catch (XOAuth2AuthenticationFailedException e) { + try { + return handleAuthError(config, currentRestoredItem, e); + } catch (XOAuth2AuthenticationFailedException eInner) { + lastException = eInner; } + } catch (AuthenticationFailedException e) { + lastException = e; + } catch (MessagingException e) { + Log.e(TAG, ERROR, e); updateAllThreadsIfAnySmsRestored(); - } else { - Log.d(TAG, "nothing to restore"); + lastException = e; + } catch (IllegalStateException e) { + // usually memory problems (Couldn't init cursor window) + lastException = e; + } finally { + imapStore.closeFolders(); } + } + + int restoredCount = 0; + for (int settingsId : msgList.keySet()) { + List msgs = msgList.get(settingsId); + try { + if (msgs.size() > 0) { + for (; currentRestoredItem < msgs.size() && !isCancelled(); currentRestoredItem++) { + DataType dataType = importMessage(settingsId, msgs.get(currentRestoredItem)); + + msgs.set(currentRestoredItem, null); // help gc + publishProgress(new RestoreState(RESTORE, restoredCount + currentRestoredItem, itemsToRestoreCount, 0, 0, dataType, null)); + if (currentRestoredItem % 50 == 0) { + //clear cache periodically otherwise SD card fills up + service.clearCache(); + } + } + updateAllThreadsIfAnySmsRestored(); + + restoredCount += smsIds.size() + callLogIds.size(); + } + } catch (IllegalStateException e) { + // usually memory problems (Couldn't init cursor window) + lastException = e; + } + } + + if (lastException != null) return transition(SmsSyncState.ERROR, lastException); - final int restoredCount = smsIds.size() + callLogIds.size(); + if (itemsToRestoreCount > 0) { return new RestoreState(isCancelled() ? CANCELED_RESTORE : FINISHED_RESTORE, - currentRestoredItem, + restoredCount, itemsToRestoreCount, restoredCount, Math.max(0, uids.size() - restoredCount), - null, null); - } catch (XOAuth2AuthenticationFailedException e) { - return handleAuthError(config, currentRestoredItem, e); - } catch (AuthenticationFailedException e) { - return transition(SmsSyncState.ERROR, e); - } catch (MessagingException e) { - Log.e(TAG, ERROR, e); - updateAllThreadsIfAnySmsRestored(); - return transition(SmsSyncState.ERROR, e); - } catch (IllegalStateException e) { - // usually memory problems (Couldn't init cursor window) - return transition(SmsSyncState.ERROR, e); - } finally { - imapStore.closeFolders(); + null, null); + } else { + Log.d(TAG, "nothing to restore"); + return new RestoreState(FINISHED_RESTORE, 0, 0, 0, 0, null, null); } } - private RestoreState handleAuthError(RestoreConfig config, int currentRestoredItem, XOAuth2AuthenticationFailedException e) { + private RestoreState handleAuthError(RestoreConfig config, int currentRestoredItem, XOAuth2AuthenticationFailedException e) throws XOAuth2AuthenticationFailedException { if (e.getStatus() == 400) { Log.d(TAG, "need to perform xoauth2 token refresh"); if (config.tries < 1) { @@ -162,7 +194,7 @@ private RestoreState handleAuthError(RestoreConfig config, int currentRestoredIt tokenRefresher.refreshOAuth2Token(); // we got a new token, let's retry one more time - we need to pass in a new store object // since the auth params on it are immutable - return restore(config.retryWithStore(currentRestoredItem, service.getBackupImapStore())); + return restore(config.retryWithStore(currentRestoredItem, service.getBackupImapStores())); } catch (MessagingException ignored) { Log.w(TAG, ignored); } catch (TokenRefreshException refreshException) { @@ -174,7 +206,7 @@ private RestoreState handleAuthError(RestoreConfig config, int currentRestoredIt } else { Log.w(TAG, "unexpected xoauth status code " + e.getStatus()); } - return transition(SmsSyncState.ERROR, e); + throw e; } private void publishProgress(SmsSyncState smsSyncState) { @@ -214,7 +246,7 @@ private void post(RestoreState changed) { } @SuppressWarnings("unchecked") - private DataType importMessage(Message message) { + private DataType importMessage(Integer settingsId, Message message) { uids.add(message.getUid()); FetchProfile fp = new FetchProfile(); @@ -227,10 +259,10 @@ private DataType importMessage(Message message) { //only restore sms+call log for now switch (dataType) { case CALLLOG: - importCallLog(message); + importCallLog(settingsId, message); break; case SMS: - importSms(message); + importSms(settingsId, message); break; default: if (LOCAL_LOGV) Log.d(TAG, "ignoring restore of type: " + dataType); @@ -247,9 +279,9 @@ private DataType importMessage(Message message) { return dataType; } - private void importSms(final Message message) throws IOException, MessagingException { + private void importSms(Integer settingsId, final Message message) throws IOException, MessagingException { if (LOCAL_LOGV) Log.v(TAG, "importSms(" + message + ")"); - final ContentValues values = converter.messageToContentValues(message); + final ContentValues values = converter.messageToContentValues(settingsId, message); final Integer type = values.getAsInteger(Telephony.TextBasedSmsColumns.TYPE); // only restore inbox messages and sent messages - otherwise sms might get sent on restore @@ -263,8 +295,8 @@ private void importSms(final Message message) throws IOException, MessagingExcep smsIds.add(uri.getLastPathSegment()); Long timestamp = values.getAsLong(Telephony.TextBasedSmsColumns.DATE); - if (timestamp != null && preferences.getDataTypePreferences().getMaxSyncedDate(SMS) < timestamp) { - preferences.getDataTypePreferences().setMaxSyncedDate(SMS, timestamp); + if (timestamp != null && preferences.getDataTypePreferences().getMaxSyncedDate(SMS, settingsId) < timestamp) { + preferences.getDataTypePreferences().setMaxSyncedDate(SMS, timestamp, settingsId); } if (LOCAL_LOGV) Log.v(TAG, "inserted " + uri); @@ -274,9 +306,9 @@ private void importSms(final Message message) throws IOException, MessagingExcep } } - private void importCallLog(final Message message) throws MessagingException, IOException { + private void importCallLog(Integer settingsId, final Message message) throws MessagingException, IOException { if (LOCAL_LOGV) Log.v(TAG, "importCallLog(" + message + ")"); - final ContentValues values = converter.messageToContentValues(message); + final ContentValues values = converter.messageToContentValues(settingsId, message); if (!callLogExists(values)) { final Uri uri = resolver.insert(Consts.CALLLOG_PROVIDER, values); if (uri != null) callLogIds.add(uri.getLastPathSegment()); diff --git a/app/src/main/java/com/zegoggles/smssync/service/ServiceBase.java b/app/src/main/java/com/zegoggles/smssync/service/ServiceBase.java index 2ea3da9d8..8cf8f4a6e 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/ServiceBase.java +++ b/app/src/main/java/com/zegoggles/smssync/service/ServiceBase.java @@ -49,6 +49,8 @@ import static com.zegoggles.smssync.App.LOCAL_LOGV; import static com.zegoggles.smssync.App.TAG; import static java.util.Locale.ENGLISH; +import java.util.ArrayList; +import java.util.List; public abstract class ServiceBase extends Service { @Nullable private PowerManager.WakeLock wakeLock; @@ -96,16 +98,25 @@ public boolean isWorking() { return getState().isRunning(); } - protected BackupImapStore getBackupImapStore() throws MessagingException { - final String uri = getAuthPreferences().getStoreUri(); - if (!BackupImapStore.isValidUri(uri)) { - throw new MessagingException("No valid IMAP URI: "+uri); + protected List getBackupImapStores() throws MessagingException { + List backupImapStores = new ArrayList(); + + for (Integer settingsId = 0; settingsId < App.SimCards.length; settingsId++) { + final String uri = getAuthPreferences(settingsId).getStoreUri(); + if (!BackupImapStore.isValidUri(uri)) { + throw new MessagingException("No valid IMAP URI: "+uri); + } + backupImapStores.add(new BackupImapStore(getApplicationContext(), uri, getAuthPreferences(settingsId).isTrustAllCertificates())); } - return new BackupImapStore(getApplicationContext(), uri, getAuthPreferences().isTrustAllCertificates()); + return backupImapStores; } protected AuthPreferences getAuthPreferences() { - return new AuthPreferences(this); + return getAuthPreferences(0); + } + + protected AuthPreferences getAuthPreferences(Integer settingsId) { + return new AuthPreferences(this, settingsId); } protected Preferences getPreferences() { diff --git a/app/src/main/java/com/zegoggles/smssync/service/SmsBackupService.java b/app/src/main/java/com/zegoggles/smssync/service/SmsBackupService.java index dc9a6b1e9..8381a002a 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/SmsBackupService.java +++ b/app/src/main/java/com/zegoggles/smssync/service/SmsBackupService.java @@ -37,7 +37,6 @@ import com.zegoggles.smssync.service.exception.ConnectivityException; import com.zegoggles.smssync.service.exception.MissingPermissionException; import com.zegoggles.smssync.service.exception.NoConnectionException; -import com.zegoggles.smssync.service.exception.RequiresLoginException; import com.zegoggles.smssync.service.exception.RequiresWifiException; import com.zegoggles.smssync.service.state.BackupState; import com.zegoggles.smssync.service.state.SmsSyncState; @@ -46,6 +45,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.Set; +import java.util.List; import static android.R.drawable.stat_sys_warning; import static com.zegoggles.smssync.App.CHANNEL_ID; @@ -114,21 +114,17 @@ private void backup(BackupType backupType) { EnumSet enabledTypes = getEnabledBackupTypes(); checkPermissions(enabledTypes); if (backupType != SKIP) { - checkCredentials(); if (getPreferences().isUseOldScheduler()) { legacyCheckConnectivity(); } } appLog(R.string.app_log_start_backup, backupType); - getBackupTask().execute(getBackupConfig(backupType, enabledTypes, getBackupImapStore())); + getBackupTask().execute(getBackupConfig(backupType, enabledTypes, getBackupImapStores())); } catch (MessagingException e) { Log.w(TAG, e); moveToState(state.transition(ERROR, e)); } catch (ConnectivityException e) { moveToState(state.transition(ERROR, e)); - } catch (RequiresLoginException e) { - appLog(R.string.app_log_missing_credentials); - moveToState(state.transition(ERROR, e)); } catch (BackupDisabledException e) { moveToState(state.transition(FINISHED_BACKUP, e)); } catch (MissingPermissionException e) { @@ -148,9 +144,9 @@ private void checkPermissions(EnumSet enabledTypes) throws MissingPerm private BackupConfig getBackupConfig(BackupType backupType, EnumSet enabledTypes, - BackupImapStore imapStore) { + List imapStores) { return new BackupConfig( - imapStore, + imapStores, 0, getPreferences().getMaxItemsPerSync(), getPreferences().getBackupContactGroup(), @@ -168,12 +164,6 @@ private EnumSet getEnabledBackupTypes() throws BackupDisabledException return dataTypes; } - private void checkCredentials() throws RequiresLoginException { - if (!getAuthPreferences().isLoginInformationSet()) { - throw new RequiresLoginException(); - } - } - @SuppressWarnings("deprecation") private void legacyCheckConnectivity() throws ConnectivityException { android.net.NetworkInfo active = getConnectivityManager().getActiveNetworkInfo(); 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 f90130c90..179d91576 100644 --- a/app/src/main/java/com/zegoggles/smssync/service/SmsRestoreService.java +++ b/app/src/main/java/com/zegoggles/smssync/service/SmsRestoreService.java @@ -12,12 +12,10 @@ import com.squareup.otto.Subscribe; import com.zegoggles.smssync.App; import com.zegoggles.smssync.R; -import com.zegoggles.smssync.auth.OAuth2Client; import com.zegoggles.smssync.auth.TokenRefresher; import com.zegoggles.smssync.contacts.ContactAccessor; import com.zegoggles.smssync.mail.MessageConverter; import com.zegoggles.smssync.mail.PersonLookup; -import com.zegoggles.smssync.preferences.AuthPreferences; import com.zegoggles.smssync.service.exception.SmsProviderNotWritableException; import com.zegoggles.smssync.service.state.RestoreState; @@ -89,7 +87,7 @@ protected void handleIntent(final Intent intent) { ); RestoreConfig config = new RestoreConfig( - getBackupImapStore(), + getBackupImapStores(), 0, restoreSms, restoreCallLog, @@ -98,9 +96,7 @@ protected void handleIntent(final Intent intent) { 0 ); - final AuthPreferences authPreferences = new AuthPreferences(this); - new RestoreTask(this, converter, getContentResolver(), - new TokenRefresher(service, new OAuth2Client(authPreferences.getOAuth2ClientId()), authPreferences)).execute(config); + new RestoreTask(this, converter, getContentResolver()).execute(config); } catch (MessagingException e) { postError(e); diff --git a/app/src/main/java/com/zegoggles/smssync/utils/SimCard.java b/app/src/main/java/com/zegoggles/smssync/utils/SimCard.java new file mode 100644 index 000000000..7b3bcb616 --- /dev/null +++ b/app/src/main/java/com/zegoggles/smssync/utils/SimCard.java @@ -0,0 +1,12 @@ +package com.zegoggles.smssync.utils; + +public class SimCard +{ + public SimCard(String phoneNumber, String iccId) { + PhoneNumber = phoneNumber; + IccId = iccId; + } + + public String PhoneNumber; + public String IccId; +} \ No newline at end of file diff --git a/app/src/main/java/com/zegoggles/smssync/utils/SimCardHelper.java b/app/src/main/java/com/zegoggles/smssync/utils/SimCardHelper.java new file mode 100644 index 000000000..a2d585199 --- /dev/null +++ b/app/src/main/java/com/zegoggles/smssync/utils/SimCardHelper.java @@ -0,0 +1,87 @@ +package com.zegoggles.smssync.utils; + +import android.content.Context; +import android.os.Build; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import androidx.annotation.RequiresApi; + +import com.zegoggles.smssync.App; + +public class SimCardHelper { + public static SimCard[] getSimCards(Context context) { + if (Build.VERSION.SDK_INT >= 29) return getSimCardsInternal(context); else { + SimCard[] simCards = new SimCard[1]; + simCards[0] = new SimCard("1", "1"); + return simCards; + } + } + + @RequiresApi(29) + private static SimCard[] getSimCardsInternal(Context context) { + int simMaxCount = 1; + try { + SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE + ); + simMaxCount = subscriptionManager.getActiveSubscriptionInfoCountMax(); + } catch (Exception e) { + simMaxCount = 1; + } + + SimCard[] simCards = new SimCard[simMaxCount]; + + try { + SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE + ); + + int simCount = 0; + for (SubscriptionInfo subscriptionInfo : subscriptionManager.getActiveSubscriptionInfoList()) { + String phoneNumber = transformPhoneNumber(subscriptionInfo.getNumber()); + String iccId = subscriptionInfo.getIccId(); + simCards[simCount] = new SimCard(phoneNumber, iccId); + simCount++; + } + + simCards = fillWithFallBack(simCards, simMaxCount); + } catch (Exception e) { + simCards = fillWithFallBack(simCards, simMaxCount); + if (simCards.length == 0) { + simCards[0] = new SimCard("1", "1"); + } + } + + return simCards; + } + + private static String transformPhoneNumber(String number) { + return number.replaceAll("[^\\d]", ""); + } + + private static SimCard[] fillWithFallBack(SimCard[] simCards, int simMaxCount) { + int simCount = 0; + for (int i = 0; i < simMaxCount; i++) { + if (simCards[simCount] == null) { + String cnt = String.valueOf(i + 1); + simCards[simCount] = new SimCard(cnt, cnt); + } + simCount++; + } + return simCards; + } + + public static String addSettingsId(String stringWithoutSettingsId, Integer settingsId) { + if (settingsId == 0) { + return stringWithoutSettingsId; + } else { + return stringWithoutSettingsId + "_" + settingsId; + } + } + + public static String addPhoneNumberIfMultiSim(String plain, Integer settingsId) { + SimCard[] simCards = App.SimCards; + if (simCards.length < 2) return plain; + return plain + " ("+simCards[settingsId].PhoneNumber+")"; + } +} diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 14b370cf3..d1c5067c1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -114,6 +114,9 @@ Gelesen markieren (SMS) Ob alle wiederhergestellten SMS als gelesen markiert werden sollen oder nicht. + IccId für MultiSim verwenden + Ob IccId für das Anrufprotokoll verwendet werden soll, um zwischen Sim-Karten zu unterscheiden (geräteabhängig und nur verwendet auf MultiSim-Geräten) + E-Mail Adress-Stil Format der E-Mail-Adressen @@ -252,7 +255,7 @@ Übersprungen (Anmeldedaten fehlen) Sicherung angefordert (%1$s) Starte Sicherung (%1$s) - Sichere (%1$d SMS, %2$d MMS, %3$d call log) + Sichere (%1$d SMS, %2$d MMS, %3$d call log) von Telefonnummer %4$s Sicherung abgebrochen Sicherung fertiggestellt @@ -317,6 +320,8 @@ Allen Zertifikaten trauen Benuzte Konfiguration: %1$s + Einstellungen von SIM 1 + Einstellungen von SIM 1 übernehmen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 714b69c4d..651f7cf6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -138,6 +138,9 @@ Mark as read (SMS) Whether to mark all restored SMS as read or not. + Use IccId for multisim + Whether to use IccId for Calllog to differentiate between multiple sim-cards (device dependant and only used on mulitsim devices) + Email address style Format of email addresses @@ -279,7 +282,7 @@ Skipped (missing credentials) Backup requested (%1$s) Starting backup (%1$s) - Backing up (%1$d SMS, %2$d MMS, %3$d call log) + Backing up (%1$d SMS, %2$d MMS, %3$d call log) from number %4$s Using config: %1$s Backup canceled Backup finished @@ -349,4 +352,6 @@ Dark theme Legacy settings XOAuth2 is no longer supported. + Settings from SIM 1 + Takeover settings from SIM 1 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6232305e4..829f66e7c 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -258,6 +258,12 @@ android:summary="@string/ui_mark_as_read_restore_desc" android:defaultValue="true"/> + + map = new HashMap(); - Message msg = generator.messageForDataType(map, DataType.SMS); + Message msg = generator.messageForDataType(map, DataType.SMS, 0); assertThat(msg).isNull(); } @Test public void testShouldGenerateSubjectWithNameForSMS() throws Exception { PersonRecord record = new PersonRecord(1, "Test Testor", null, null); - Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS); + Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getSubject()).isEqualTo("SMS with Test Testor"); } @Test public void testShouldGenerateSMSMessageWithCorrectEncoding() throws Exception { PersonRecord record = new PersonRecord(1, "Test Testor", null, null); - Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS); + Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS, 0); assertThat(msg.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)).isEqualTo(new String[] { MimeUtil.ENC_QUOTED_PRINTABLE }); @@ -91,7 +91,7 @@ public class MessageGeneratorTest { new Address("foo@bar.com")); when(mmsSupport.getDetails(any(Uri.class), any(AddressStyle.class))).thenReturn(details); - Message msg = generator.messageForDataType(mockMessage("1234", personRecord), DataType.MMS); + Message msg = generator.messageForDataType(mockMessage("1234", personRecord), DataType.MMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getSubject()).isEqualTo("SMS with Foo Bar"); @@ -104,7 +104,7 @@ public class MessageGeneratorTest { new Address("foo@bar.com")); when(mmsSupport.getDetails(any(Uri.class), any(AddressStyle.class))).thenReturn(details); - Message msg = generator.messageForDataType(mockMessage("1234", personRecord), DataType.MMS); + Message msg = generator.messageForDataType(mockMessage("1234", personRecord), DataType.MMS, 0); assertThat(msg.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)).isEqualTo(new String[] { MimeUtil.ENC_7BIT }); @@ -112,7 +112,7 @@ public class MessageGeneratorTest { @Test public void testShouldGenerateMessageForCallLogOutgoing() throws Exception { PersonRecord record = new PersonRecord(-1, "Test Testor", null, null); - Message msg = generator.messageForDataType(mockCalllogMessage("1234", OUTGOING_TYPE, record), CALLLOG); + Message msg = generator.messageForDataType(mockCalllogMessage("1234", OUTGOING_TYPE, record), CALLLOG, 0); assertThat(msg).isNotNull(); assertThat(msg.getSubject()).isEqualTo("Call with Test Testor"); assertThat(msg.getFrom()[0]).isEqualTo(me); @@ -121,13 +121,13 @@ public class MessageGeneratorTest { @Test public void testShouldGenerateMessageForCallLogIncoming() throws Exception { PersonRecord record = new PersonRecord(-1, "Test Testor", null, null); - Message message = generator.messageForDataType(mockCalllogMessage("1234", INCOMING_TYPE, record), CALLLOG); + Message message = generator.messageForDataType(mockCalllogMessage("1234", INCOMING_TYPE, record), CALLLOG, 0); assertMessage(message); } @Test public void testShouldGenerateMessageForCallLogMissed() throws Exception { PersonRecord record = new PersonRecord(-1, "Test Testor", null, null); - Message message = generator.messageForDataType(mockCalllogMessage("1234", MISSED_TYPE, record), CALLLOG); + Message message = generator.messageForDataType(mockCalllogMessage("1234", MISSED_TYPE, record), CALLLOG, 0); assertMessage(message); } @@ -140,7 +140,7 @@ private void assertMessage(Message message) { @Test public void testShouldGenerateMessageForCallLogIncomingUnknown() throws Exception { PersonRecord record = new PersonRecord(0, null, null, "-1"); - Message msg = generator.messageForDataType(mockCalllogMessage("", INCOMING_TYPE, record), CALLLOG); + Message msg = generator.messageForDataType(mockCalllogMessage("", INCOMING_TYPE, record), CALLLOG, 0); assertThat(msg).isNotNull(); assertThat(msg.getSubject()).isEqualTo("Call with Unknown"); assertThat(msg.getFrom()[0].toString()).isEqualTo("Unknown "); @@ -149,7 +149,7 @@ private void assertMessage(Message message) { @Test public void testShouldGenerateCallLogMessageWithCorrectEncoding() throws Exception { PersonRecord record = new PersonRecord(-1, "Test Testor", null, null); - Message msg = generator.messageForDataType(mockCalllogMessage("1234", OUTGOING_TYPE, record), CALLLOG); + Message msg = generator.messageForDataType(mockCalllogMessage("1234", OUTGOING_TYPE, record), CALLLOG, 0); assertThat(msg.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)).isEqualTo(new String[] { MimeUtil.ENC_QUOTED_PRINTABLE }); @@ -157,21 +157,21 @@ private void assertMessage(Message message) { @Test public void testShouldGenerateSubjectWithNameAndNumberForSMS() throws Exception { PersonRecord record = new PersonRecord(1, "Test Testor", "test@test.com", "1234"); - Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS); + Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getSubject()).isEqualTo("SMS with Test Testor"); } @Test public void shouldGenerateCorrectFromHeaderWithUsersEmailAddress() throws Exception { PersonRecord record = new PersonRecord(1, "Test Testor", "test@test.com", "1234"); - Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS); + Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getFrom()[0]).isEqualTo(me); } @Test public void shouldGenerateCorrectToHeader() throws Exception { PersonRecord record = new PersonRecord(1, "Test Testor", "test@test.com", "1234"); - Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS); + Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getRecipients(Message.RecipientType.TO)[0].toString()) @@ -186,7 +186,7 @@ private void assertMessage(Message message) { map.put(Telephony.TextBasedSmsColumns.DATE, String.valueOf(date.getTime())); map.put(Telephony.TextBasedSmsColumns.TYPE, "0"); - Message msg = generator.messageForDataType(map, DataType.SMS); + Message msg = generator.messageForDataType(map, DataType.SMS, 0); assertThat(msg).isNotNull(); verify(headerGenerator).setHeaders(any(Message.class), @@ -203,7 +203,7 @@ private void assertMessage(Message message) { Map map = mockMessage("1234", record); map.put(Telephony.TextBasedSmsColumns.TYPE, "1"); - Message msg = generator.messageForDataType(map, DataType.SMS); + Message msg = generator.messageForDataType(map, DataType.SMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getFrom()[0].toString()) @@ -214,7 +214,7 @@ private void assertMessage(Message message) { @Test public void testShouldUseNumberIfNameIsUnknown() throws Exception { PersonRecord record = new PersonRecord(-1, null, null, "1234"); - Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS); + Message msg = generator.messageForDataType(mockMessage("1234", record), DataType.SMS, 0); assertThat(msg).isNotNull(); assertThat(msg.getSubject()).isEqualTo("SMS with 1234"); } @@ -236,9 +236,9 @@ private void assertMessage(Message message) { map.put(Telephony.TextBasedSmsColumns.TYPE, "1"); when(groupIds.contains(record)).thenReturn(false); - assertThat(generator.messageForDataType(map, DataType.SMS)).isNull(); + assertThat(generator.messageForDataType(map, DataType.SMS, 0)).isNull(); when(groupIds.contains(record)).thenReturn(true); - assertThat(generator.messageForDataType(map, DataType.SMS)).isNotNull(); + assertThat(generator.messageForDataType(map, DataType.SMS, 0)).isNotNull(); } private Map mockMessage(String address, PersonRecord record) { diff --git a/app/src/test/java/com/zegoggles/smssync/preferences/AuthPreferencesTest.java b/app/src/test/java/com/zegoggles/smssync/preferences/AuthPreferencesTest.java index 2713f78a9..349accc34 100644 --- a/app/src/test/java/com/zegoggles/smssync/preferences/AuthPreferencesTest.java +++ b/app/src/test/java/com/zegoggles/smssync/preferences/AuthPreferencesTest.java @@ -17,7 +17,7 @@ public class AuthPreferencesTest { @Before public void before() { initMocks(this); - authPreferences = new AuthPreferences(RuntimeEnvironment.application); + authPreferences = new AuthPreferences(RuntimeEnvironment.application, 0); } @Test public void testStoreUri() throws Exception { diff --git a/app/src/test/java/com/zegoggles/smssync/preferences/PreferencesTest.java b/app/src/test/java/com/zegoggles/smssync/preferences/PreferencesTest.java index 9f25b0ee3..695ac0419 100644 --- a/app/src/test/java/com/zegoggles/smssync/preferences/PreferencesTest.java +++ b/app/src/test/java/com/zegoggles/smssync/preferences/PreferencesTest.java @@ -24,21 +24,26 @@ public class PreferencesTest { assertThat(preferences.isFirstUse()).isFalse(); } @Test public void shouldTestForFirstBackup() throws Exception { - assertThat(preferences.isFirstBackup()).isTrue(); + assertThat(preferences.isFirstBackup(0)).isTrue(); } @Test public void shouldTestForFirstBackupSMS() throws Exception { - preferences.getDataTypePreferences().setMaxSyncedDate(SMS, 1234); - assertThat(preferences.isFirstBackup()).isFalse(); + preferences.getDataTypePreferences().setMaxSyncedDate(SMS, 1234, 0); + assertThat(preferences.isFirstBackup(0)).isFalse(); + } + + @Test public void shouldTestForFirstBackupSMSForSecondSIM() throws Exception { + preferences.getDataTypePreferences().setMaxSyncedDate(SMS, 1234, 1); + assertThat(preferences.isFirstBackup(1)).isFalse(); } @Test public void shouldTestForFirstBackupMMS() throws Exception { - preferences.getDataTypePreferences().setMaxSyncedDate(MMS, 1234); - assertThat(preferences.isFirstBackup()).isFalse(); + preferences.getDataTypePreferences().setMaxSyncedDate(MMS, 1234, 0); + assertThat(preferences.isFirstBackup(0)).isFalse(); } @Test public void shouldTestForFirstBackupCallLog() throws Exception { - preferences.getDataTypePreferences().setMaxSyncedDate(CALLLOG, 1234); - assertThat(preferences.isFirstBackup()).isFalse(); + preferences.getDataTypePreferences().setMaxSyncedDate(CALLLOG, 1234, 0); + assertThat(preferences.isFirstBackup(0)).isFalse(); } } diff --git a/app/src/test/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiverTest.java b/app/src/test/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiverTest.java index 7e7d67ae6..0d567491f 100644 --- a/app/src/test/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiverTest.java +++ b/app/src/test/java/com/zegoggles/smssync/receiver/SmsBroadcastReceiverTest.java @@ -17,28 +17,36 @@ @RunWith(RobolectricTestRunner.class) public class SmsBroadcastReceiverTest { + + public class TestSmsBroadcastReceiver extends SmsBroadcastReceiver { + private boolean atLeastOneLoginInformationSet; + + public void SetAtLeastOneLoginInformationSet(boolean value) { + atLeastOneLoginInformationSet = value; + } + + @Override protected BackupJobs getBackupJobs(Context context) { + return backupJobs; + } + + @Override protected Preferences getPreferences(Context context) { + return preferences; + } + + @Override protected boolean atLeastOneLoginInformationSet(Context context) { + return atLeastOneLoginInformationSet; + } + } + Context context; @Mock BackupJobs backupJobs; @Mock Preferences preferences; - @Mock AuthPreferences authPreferences; - SmsBroadcastReceiver receiver; + TestSmsBroadcastReceiver receiver; @Before public void before() { initMocks(this); context = RuntimeEnvironment.application; - receiver = new SmsBroadcastReceiver() { - @Override protected BackupJobs getBackupJobs(Context context) { - return backupJobs; - } - - @Override protected Preferences getPreferences(Context context) { - return preferences; - } - - @Override protected AuthPreferences getAuthPreferences(Context context) { - return authPreferences; - } - }; + receiver = new TestSmsBroadcastReceiver(); } @Test public void shouldScheduleIncomingBackupAfterIncomingMessage() throws Exception { @@ -56,22 +64,22 @@ public class SmsBroadcastReceiverTest { @Test public void shouldNotScheduleIfLoginInformationIsNotSet() throws Exception { mockScheduled(); - when(authPreferences.isLoginInformationSet()).thenReturn(false); + receiver.SetAtLeastOneLoginInformationSet(false); receiver.onReceive(context, new Intent().setAction("android.provider.Telephony.SMS_RECEIVED")); verifyZeroInteractions(backupJobs); } @Test public void shouldNotScheduleIfFirstBackupHasNotBeenRun() throws Exception { mockScheduled(); - when(preferences.isFirstBackup()).thenReturn(true); + when(preferences.isFirstBackup(0)).thenReturn(true); receiver.onReceive(context, new Intent().setAction("android.provider.Telephony.SMS_RECEIVED")); verifyZeroInteractions(backupJobs); } private void mockScheduled() { - when(authPreferences.isLoginInformationSet()).thenReturn(true); + receiver.SetAtLeastOneLoginInformationSet(true); when(preferences.isAutoBackupEnabled()).thenReturn(true); - when(preferences.isFirstBackup()).thenReturn(false); + when(preferences.isFirstBackup(0)).thenReturn(false); when(preferences.isUseOldScheduler()).thenReturn(true); } } diff --git a/app/src/test/java/com/zegoggles/smssync/service/BackupConfigTest.java b/app/src/test/java/com/zegoggles/smssync/service/BackupConfigTest.java index da29cef37..019eddd9f 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/BackupConfigTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/BackupConfigTest.java @@ -6,6 +6,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import java.util.ArrayList; +import java.util.List; import java.util.EnumSet; @@ -16,7 +18,9 @@ public class BackupConfigTest { @Test(expected = IllegalArgumentException.class) public void shouldCheckForDataTypesEmpty() throws Exception { - new BackupConfig(mock(BackupImapStore.class), + List imapStores = new ArrayList(); + imapStores.add(mock(BackupImapStore.class)); + new BackupConfig(imapStores, 0, -1, ContactGroup.EVERYBODY, @@ -29,7 +33,9 @@ public void shouldCheckForDataTypesEmpty() throws Exception { @SuppressWarnings("ConstantConditions") @Test(expected = IllegalArgumentException.class) public void shouldCheckForDataTypesNull() throws Exception { - new BackupConfig(mock(BackupImapStore.class), + List imapStores = new ArrayList(); + imapStores.add(mock(BackupImapStore.class)); + new BackupConfig(imapStores, 0, -1, ContactGroup.EVERYBODY, @@ -41,7 +47,9 @@ public void shouldCheckForDataTypesNull() throws Exception { @Test(expected = IllegalArgumentException.class) public void shouldCheckForPositiveTry() throws Exception { - new BackupConfig(mock(BackupImapStore.class), + List imapStores = new ArrayList(); + imapStores.add(mock(BackupImapStore.class)); + new BackupConfig(imapStores, -1, -1, ContactGroup.EVERYBODY, diff --git a/app/src/test/java/com/zegoggles/smssync/service/BackupItemsFetcherTest.java b/app/src/test/java/com/zegoggles/smssync/service/BackupItemsFetcherTest.java index 604db13af..588af7bdd 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/BackupItemsFetcherTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/BackupItemsFetcherTest.java @@ -44,7 +44,7 @@ public class BackupItemsFetcherTest { @Test public void shouldGetItemsForDataType() throws Exception { preferences.getDataTypePreferences().setBackupEnabled(true, SMS); - assertThat(fetcher.getItemsForDataType(SMS, null, -1).getCount()).isEqualTo(0); + assertThat(fetcher.getItemsForDataType(SMS, null, 0, -1).getCount()).isEqualTo(0); verifyZeroInteractions(resolver); } @@ -55,7 +55,7 @@ public class BackupItemsFetcherTest { mockEmptyQuery(); - assertThat(fetcher.getItemsForDataType(SMS, null, -1).getCount()).isEqualTo(0); + assertThat(fetcher.getItemsForDataType(SMS, null, 0, -1).getCount()).isEqualTo(0); } @Test public void shouldCatchNullPointerExceptions() throws Exception { @@ -65,7 +65,7 @@ public class BackupItemsFetcherTest { mockEmptyQuery(); - assertThat(fetcher.getItemsForDataType(SMS, null, -1).getCount()).isEqualTo(0); + assertThat(fetcher.getItemsForDataType(SMS, null, 0, -1).getCount()).isEqualTo(0); } @Test public void shouldReturnDefaultIfDataTypeCannotBeRead() throws Exception { @@ -105,6 +105,6 @@ private void mockMostRecentTimestampForType(DataType type, long max) { private void mockEmptyQuery() { BackupQueryBuilder.Query query = mock(BackupQueryBuilder.Query.class); - when(queryBuilder.buildQueryForDataType(SMS, null, -1)).thenReturn(query); + when(queryBuilder.buildQueryForDataType(SMS, null, 0, -1)).thenReturn(query); } } diff --git a/app/src/test/java/com/zegoggles/smssync/service/BackupQueryBuilderTest.java b/app/src/test/java/com/zegoggles/smssync/service/BackupQueryBuilderTest.java index 7cffc7588..22009df97 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/BackupQueryBuilderTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/BackupQueryBuilderTest.java @@ -15,6 +15,7 @@ import static com.zegoggles.smssync.mail.DataType.MMS; import static com.zegoggles.smssync.mail.DataType.SMS; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -26,17 +27,17 @@ public class BackupQueryBuilderTest { @Before public void before() { initMocks(this); - when(dataTypePreferences.getMaxSyncedDate(any(DataType.class))).thenReturn(-1L); + when(dataTypePreferences.getMaxSyncedDate(any(DataType.class), anyInt())).thenReturn(-1L); builder = new BackupQueryBuilder(dataTypePreferences); } @Test public void shouldBuildQueryForSMS() throws Exception { - BackupQueryBuilder.Query query = builder.buildQueryForDataType(SMS, null, 200); + BackupQueryBuilder.Query query = builder.buildQueryForDataType(SMS, null, 0, 200); assertThat(query.uri).isEqualTo(Uri.parse("content://sms")); assertThat(query.projection).isNull(); - assertThat(query.selection).isEqualTo("date > ? AND type <> ?"); - assertThat(query.selectionArgs).asList().containsExactly("-1", "3"); + assertThat(query.selection).isEqualTo("date > ? AND type <> ? AND sub_id = ?"); + assertThat(query.selectionArgs).asList().containsExactly("-1", "3", "1"); assertThat(query.sortOrder).isEqualTo("date LIMIT 200"); } @@ -44,45 +45,45 @@ public class BackupQueryBuilderTest { ContactGroupIds ids = new ContactGroupIds(); ids.add(1L, 20L); - BackupQueryBuilder.Query query = builder.buildQueryForDataType(SMS, ids, 200); + BackupQueryBuilder.Query query = builder.buildQueryForDataType(SMS, ids, 0, 200); assertThat(query.uri).isEqualTo(Uri.parse("content://sms")); assertThat(query.projection).isNull(); - assertThat(query.selection).isEqualTo("date > ? AND type <> ? AND (type = 2 OR person IN (20))"); - assertThat(query.selectionArgs).asList().containsExactly("-1", "3"); + assertThat(query.selection).isEqualTo("date > ? AND type <> ? AND (type = 2 OR person IN (20)) AND sub_id = ?"); + assertThat(query.selectionArgs).asList().containsExactly("-1", "3", "1"); assertThat(query.sortOrder).isEqualTo("date LIMIT 200"); } @Test public void shouldBuildQueryForMMS() throws Exception { - BackupQueryBuilder.Query query = builder.buildQueryForDataType(MMS, null, 200); + BackupQueryBuilder.Query query = builder.buildQueryForDataType(MMS, null, 0, 200); assertThat(query.uri).isEqualTo(Uri.parse("content://mms")); assertThat(query.projection).isNull(); - assertThat(query.selection).isEqualTo("date > ? AND m_type <> ?"); - assertThat(query.selectionArgs).asList().containsExactly("-1", "134"); + assertThat(query.selection).isEqualTo("date > ? AND m_type <> ? AND sub_id = ?"); + assertThat(query.selectionArgs).asList().containsExactly("-1", "134", "1"); assertThat(query.sortOrder).isEqualTo("date LIMIT 200"); } @Test public void shouldBuildQueryForMMSWithSyncedDate() throws Exception { long nowInSecs = System.currentTimeMillis(); - when(dataTypePreferences.getMaxSyncedDate(MMS)).thenReturn(nowInSecs); - BackupQueryBuilder.Query query = builder.buildQueryForDataType(MMS, null, 200); + when(dataTypePreferences.getMaxSyncedDate(MMS, 0)).thenReturn(nowInSecs); + BackupQueryBuilder.Query query = builder.buildQueryForDataType(MMS, null, 0, 200); assertThat(query.uri).isEqualTo(Uri.parse("content://mms")); assertThat(query.projection).isNull(); - assertThat(query.selection).isEqualTo("date > ? AND m_type <> ?"); - assertThat(query.selectionArgs).asList().containsExactly(String.valueOf(nowInSecs / 1000L), "134"); + assertThat(query.selection).isEqualTo("date > ? AND m_type <> ? AND sub_id = ?"); + assertThat(query.selectionArgs).asList().containsExactly(String.valueOf(nowInSecs / 1000L), "134", "1"); assertThat(query.sortOrder).isEqualTo("date LIMIT 200"); } @Test public void shouldBuildQueryForCallLog() throws Exception { - BackupQueryBuilder.Query query = builder.buildQueryForDataType(CALLLOG, null, 200); + BackupQueryBuilder.Query query = builder.buildQueryForDataType(CALLLOG, null, 0, 200); assertThat(query.uri).isEqualTo(Uri.parse("content://call_log/calls")); assertThat(query.projection).asList().containsExactly("_id", "number", "duration", "date", "type"); - assertThat(query.selection).isEqualTo("date > ?"); - assertThat(query.selectionArgs).asList().containsExactly("-1"); + assertThat(query.selection).isEqualTo("date > ? AND (subscription_id = ? OR subscription_id = ?)"); + assertThat(query.selectionArgs).asList().containsExactly("-1", "1", "1"); assertThat(query.sortOrder).isEqualTo("date LIMIT 200"); } 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..69f05f9cd 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/BackupTaskTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/BackupTaskTest.java @@ -7,6 +7,7 @@ import com.fsck.k9.mail.Message; import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.store.imap.XOAuth2AuthenticationFailedException; +import com.zegoggles.smssync.service.exception.RequiresLoginException; import com.zegoggles.smssync.auth.TokenRefreshException; import com.zegoggles.smssync.auth.TokenRefresher; import com.zegoggles.smssync.contacts.ContactAccessor; @@ -53,6 +54,9 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; +import java.util.ArrayList; +import java.util.List; + @RunWith(RobolectricTestRunner.class) public class BackupTaskTest { BackupTask task; @@ -80,10 +84,15 @@ public class BackupTaskTest { task = new BackupTask(service, fetcher, converter, syncer, authPreferences, preferences, accessor, tokenRefresher); context = RuntimeEnvironment.application; + + new AuthPreferences(context, 0).setImapPassword("a"); + new AuthPreferences(context, 0).setImapUser("a"); } private BackupConfig getBackupConfig(EnumSet types) { - return new BackupConfig(store, 0, 100, new ContactGroup(-1), BackupType.MANUAL, types, + List imapStores = new ArrayList(); + imapStores.add(store); + return new BackupConfig(imapStores, 0, 100, new ContactGroup(-1), BackupType.MANUAL, types, false ); } @@ -100,8 +109,8 @@ private BackupConfig getBackupConfig(EnumSet types) { @Test public void shouldVerifyStoreSettings() throws Exception { mockFetch(SMS, 1); - when(converter.convertMessages(any(Cursor.class), eq(SMS))).thenReturn(result(SMS, 1)); - when(store.getFolder(SMS, dataTypePreferences)).thenReturn(folder); + when(converter.convertMessages(any(Cursor.class), eq(SMS), anyInt())).thenReturn(result(SMS, 1)); + when(store.getFolder(SMS, dataTypePreferences, 0)).thenReturn(folder); task.doInBackground(config); verify(store).checkSettings(); } @@ -109,8 +118,8 @@ private BackupConfig getBackupConfig(EnumSet types) { @Test public void shouldBackupItems() throws Exception { mockFetch(SMS, 1); - when(converter.convertMessages(any(Cursor.class), eq(SMS))).thenReturn(result(SMS, 1)); - when(store.getFolder(notNull(DataType.class), same(dataTypePreferences))).thenReturn(folder); + when(converter.convertMessages(any(Cursor.class), eq(SMS), anyInt())).thenReturn(result(SMS, 1)); + when(store.getFolder(notNull(DataType.class), same(dataTypePreferences), anyInt())).thenReturn(folder); BackupState finalState = task.doInBackground(config); @@ -130,8 +139,8 @@ private BackupConfig getBackupConfig(EnumSet types) { public void shouldBackupMultipleTypes() throws Exception { mockFetch(SMS, 1); mockFetch(MMS, 2); - when(store.getFolder(notNull(DataType.class), same(dataTypePreferences))).thenReturn(folder); - when(converter.convertMessages(any(Cursor.class), any(DataType.class))).thenReturn(result(SMS, 1)); + when(store.getFolder(notNull(DataType.class), same(dataTypePreferences), anyInt())).thenReturn(folder); + when(converter.convertMessages(any(Cursor.class), any(DataType.class), anyInt())).thenReturn(result(SMS, 1)); BackupState finalState = task.doInBackground(getBackupConfig(EnumSet.of(SMS, MMS))); @@ -143,20 +152,20 @@ public void shouldBackupMultipleTypes() throws Exception { @Test public void shouldCreateFoldersLazilyOnlyForNeededTypes() throws Exception { mockFetch(SMS, 1); - when(converter.convertMessages(any(Cursor.class), eq(SMS))).thenReturn(result(SMS, 1)); - when(store.getFolder(notNull(DataType.class), same(dataTypePreferences))).thenReturn(folder); + when(converter.convertMessages(any(Cursor.class), eq(SMS), anyInt())).thenReturn(result(SMS, 1)); + when(store.getFolder(notNull(DataType.class), same(dataTypePreferences), anyInt())).thenReturn(folder); task.doInBackground(config); - verify(store).getFolder(SMS, dataTypePreferences); - verify(store, never()).getFolder(MMS, dataTypePreferences); - verify(store, never()).getFolder(CALLLOG, dataTypePreferences); + verify(store).getFolder(SMS, dataTypePreferences, 0); + verify(store, never()).getFolder(MMS, dataTypePreferences, 0); + verify(store, never()).getFolder(CALLLOG, dataTypePreferences, 0); } @Test public void shouldCloseImapFolderAfterBackup() throws Exception { mockFetch(SMS, 1); - when(converter.convertMessages(any(Cursor.class), eq(SMS))).thenReturn(result(SMS, 1)); - when(store.getFolder(notNull(DataType.class), same(dataTypePreferences))).thenReturn(folder); + when(converter.convertMessages(any(Cursor.class), eq(SMS), anyInt())).thenReturn(result(SMS, 1)); + when(store.getFolder(notNull(DataType.class), same(dataTypePreferences), anyInt())).thenReturn(folder); task.doInBackground(config); @@ -172,11 +181,13 @@ public void shouldBackupMultipleTypes() throws Exception { @Test public void shouldSkipItems() throws Exception { when(fetcher.getMostRecentTimestamp(any(DataType.class))).thenReturn(-23L); + List imapStores = new ArrayList(); + imapStores.add(store); BackupState finalState = task.doInBackground(new BackupConfig( - store, 0, 100, new ContactGroup(-1), BackupType.SKIP, EnumSet.of(SMS), false + imapStores, 0, 100, new ContactGroup(-1), BackupType.SKIP, EnumSet.of(SMS), false ) ); - verify(dataTypePreferences).setMaxSyncedDate(DataType.SMS, -23); + verify(dataTypePreferences).setMaxSyncedDate(DataType.SMS, -23, 0); verifyZeroInteractions(dataTypePreferences); assertThat(finalState).isNotNull(); @@ -185,12 +196,12 @@ public void shouldBackupMultipleTypes() throws Exception { @Test public void shouldHandleAuthErrorAndTokenCannotBeRefreshed() throws Exception { mockFetch(SMS, 1); - when(converter.convertMessages(any(Cursor.class), notNull(DataType.class))).thenReturn(result(SMS, 1)); + when(converter.convertMessages(any(Cursor.class), notNull(DataType.class), anyInt())).thenReturn(result(SMS, 1)); XOAuth2AuthenticationFailedException exception = mock(XOAuth2AuthenticationFailedException.class); when(exception.getStatus()).thenReturn(400); - when(store.getFolder(notNull(DataType.class), same(dataTypePreferences))).thenThrow(exception); + when(store.getFolder(notNull(DataType.class), same(dataTypePreferences), anyInt())).thenThrow(exception); doThrow(new TokenRefreshException("failed")).when(tokenRefresher).refreshOAuth2Token(); @@ -206,13 +217,15 @@ public void shouldBackupMultipleTypes() throws Exception { @Test public void shouldHandleAuthErrorAndTokenCouldBeRefreshed() throws Exception { mockFetch(SMS, 1); - when(converter.convertMessages(any(Cursor.class), notNull(DataType.class))).thenReturn(result(SMS, 1)); + when(converter.convertMessages(any(Cursor.class), notNull(DataType.class), anyInt())).thenReturn(result(SMS, 1)); XOAuth2AuthenticationFailedException exception = mock(XOAuth2AuthenticationFailedException.class); when(exception.getStatus()).thenReturn(400); - when(store.getFolder(notNull(DataType.class), same(dataTypePreferences))).thenThrow(exception); - when(service.getBackupImapStore()).thenReturn(store); + when(store.getFolder(notNull(DataType.class), same(dataTypePreferences), anyInt())).thenThrow(exception); + List imapStores = new ArrayList(); + imapStores.add(store); + when(service.getBackupImapStores()).thenReturn(imapStores); task.doInBackground(config); @@ -227,6 +240,17 @@ public void shouldBackupMultipleTypes() throws Exception { verify(service).releaseLocks(); } + @Test public void shouldCheckForLoginCredentials() throws Exception { + mockFetch(SMS, 1); + when(converter.convertMessages(any(Cursor.class), eq(SMS), anyInt())).thenReturn(result(SMS, 1)); + when(store.getFolder(SMS, dataTypePreferences, 0)).thenReturn(folder); + new AuthPreferences(context, 0).setImapPassword(""); + + task.doInBackground(config); + + verify(service).transition(eq(SmsSyncState.ERROR), any(RequiresLoginException.class)); + } + private ConversionResult result(DataType type, int n) { ConversionResult result = new ConversionResult(type); @@ -237,7 +261,7 @@ private ConversionResult result(DataType type, int n) { } private void mockFetch(DataType type, final int n) { - when(fetcher.getItemsForDataType(eq(type), any(ContactGroupIds.class), anyInt())).then(new Answer() { + when(fetcher.getItemsForDataType(eq(type), any(ContactGroupIds.class), anyInt(), anyInt())).then(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { return testMessages(n); } @@ -256,6 +280,6 @@ private Cursor testMessages(int n) { private void mockAllFetchEmpty() { - when(fetcher.getItemsForDataType(any(DataType.class), any(ContactGroupIds.class), anyInt())).thenReturn(emptyCursor()); + when(fetcher.getItemsForDataType(any(DataType.class), any(ContactGroupIds.class), anyInt(), anyInt())).thenReturn(emptyCursor()); } } diff --git a/app/src/test/java/com/zegoggles/smssync/service/BulkFetcherTest.java b/app/src/test/java/com/zegoggles/smssync/service/BulkFetcherTest.java index ca38fee2c..19f3e4a83 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/BulkFetcherTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/BulkFetcherTest.java @@ -34,10 +34,10 @@ public class BulkFetcherTest { @Test public void shouldFetchAllItems() throws Exception { - when(fetcher.getItemsForDataType(SMS, null, 50)).thenReturn(cursor(3)); - when(fetcher.getItemsForDataType(MMS, null, 47)).thenReturn(cursor(5)); + when(fetcher.getItemsForDataType(SMS, null, 0, 50)).thenReturn(cursor(3)); + when(fetcher.getItemsForDataType(MMS, null, 0, 47)).thenReturn(cursor(5)); - BackupCursors cursors = bulkFetcher.fetch(EnumSet.of(SMS, MMS), null, 50); + BackupCursors cursors = bulkFetcher.fetch(EnumSet.of(SMS, MMS), null, 0, 50); assertThat(cursors.count()).isEqualTo(8); assertThat(cursors.count(SMS)).isEqualTo(3); @@ -45,19 +45,19 @@ public class BulkFetcherTest { } @Test public void shouldFetchAllItemsRespectingMaxItems() throws Exception { - when(fetcher.getItemsForDataType(SMS, null, 5)).thenReturn(cursor(5)); + when(fetcher.getItemsForDataType(SMS, null, 0, 5)).thenReturn(cursor(5)); - BackupCursors cursors = bulkFetcher.fetch(EnumSet.of(SMS, MMS), null, 5); + BackupCursors cursors = bulkFetcher.fetch(EnumSet.of(SMS, MMS), null, 0, 5); assertThat(cursors.count()).isEqualTo(5); assertThat(cursors.count(SMS)).isEqualTo(5); - verify(fetcher, never()).getItemsForDataType(eq(DataType.MMS), any(ContactGroupIds.class), anyInt()); + verify(fetcher, never()).getItemsForDataType(eq(DataType.MMS), any(ContactGroupIds.class), anyInt(), anyInt()); } @Test public void shouldFetchAllItemsEmptyList() throws Exception { - BackupCursors cursors = bulkFetcher.fetch(EnumSet.noneOf(DataType.class), null, 50); + BackupCursors cursors = bulkFetcher.fetch(EnumSet.noneOf(DataType.class), null, 0, 50); assertThat(cursors.count()).isEqualTo(0); } diff --git a/app/src/test/java/com/zegoggles/smssync/service/RestoreTaskTest.java b/app/src/test/java/com/zegoggles/smssync/service/RestoreTaskTest.java index acfbacde1..45ab62451 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/RestoreTaskTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/RestoreTaskTest.java @@ -8,12 +8,12 @@ import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.store.imap.ImapMessage; import com.zegoggles.smssync.Consts; -import com.zegoggles.smssync.auth.TokenRefresher; import com.zegoggles.smssync.mail.BackupImapStore; import com.zegoggles.smssync.mail.DataType; import com.zegoggles.smssync.mail.MessageConverter; import com.zegoggles.smssync.preferences.DataTypePreferences; import com.zegoggles.smssync.preferences.Preferences; +import com.zegoggles.smssync.preferences.AuthPreferences; import com.zegoggles.smssync.service.state.RestoreState; import org.junit.Before; import org.junit.Test; @@ -45,19 +45,23 @@ public class RestoreTaskTest { @Mock RestoreState state; @Mock MessageConverter converter; @Mock ContentResolver resolver; - @Mock TokenRefresher tokenRefresher; + @Mock AuthPreferences authPreferences; @Before public void before() throws MessagingException { initMocks(this); - config = new RestoreConfig(store, 0, true, false, false, -1, 0); + List imapStores = new ArrayList(); + imapStores.add(store); + config = new RestoreConfig(imapStores, 0, true, false, false, -1, 0); when(service.getApplicationContext()).thenReturn(RuntimeEnvironment.application); when(service.getState()).thenReturn(state); + when(service.getAuthPreferences()).thenReturn(authPreferences); + when(authPreferences.getOAuth2ClientId()).thenReturn("123"); when(service.getPreferences()).thenReturn(new Preferences(RuntimeEnvironment.application)); - when(store.getFolder(any(DataType.class), any(DataTypePreferences.class))).thenReturn(folder); + when(store.getFolder(any(DataType.class), any(DataTypePreferences.class), anyInt())).thenReturn(folder); - task = new RestoreTask(service, converter, resolver, tokenRefresher); + task = new RestoreTask(service, converter, resolver); } @Test public void shouldAcquireAndReleaseLocksDuringRestore() throws Exception { @@ -87,7 +91,7 @@ public void shouldRestoreItems() throws Exception { ImapMessage mockMessage = mock(ImapMessage.class); when(mockMessage.getFolder()).thenReturn(folder); when(converter.getDataType(mockMessage)).thenReturn(DataType.SMS); - when(converter.messageToContentValues(mockMessage)).thenReturn(values); + when(converter.messageToContentValues(0, mockMessage)).thenReturn(values); messages.add(mockMessage); @@ -98,7 +102,7 @@ public void shouldRestoreItems() throws Exception { verify(resolver).insert(Consts.SMS_PROVIDER, values); verify(resolver).delete(Uri.parse("content://sms/conversations/-1"), null, null); - assertThat(service.getPreferences().getDataTypePreferences().getMaxSyncedDate(DataType.SMS)).isEqualTo(now.getTime()); + assertThat(service.getPreferences().getDataTypePreferences().getMaxSyncedDate(DataType.SMS, 0)).isEqualTo(now.getTime()); assertThat(task.getSmsIds()).containsExactly("123"); verify(store).closeFolders(); diff --git a/app/src/test/java/com/zegoggles/smssync/service/SmsBackupServiceTest.java b/app/src/test/java/com/zegoggles/smssync/service/SmsBackupServiceTest.java index c99e6f8bf..69851147f 100644 --- a/app/src/test/java/com/zegoggles/smssync/service/SmsBackupServiceTest.java +++ b/app/src/test/java/com/zegoggles/smssync/service/SmsBackupServiceTest.java @@ -15,7 +15,6 @@ import com.zegoggles.smssync.preferences.Preferences; import com.zegoggles.smssync.service.exception.BackupDisabledException; import com.zegoggles.smssync.service.exception.NoConnectionException; -import com.zegoggles.smssync.service.exception.RequiresLoginException; import com.zegoggles.smssync.service.exception.RequiresWifiException; import com.zegoggles.smssync.service.state.SmsSyncState; import org.junit.After; @@ -68,7 +67,7 @@ public class SmsBackupServiceTest { @Override protected BackupJobs getBackupJobs() { return backupJobs; } @Override protected Preferences getPreferences() { return preferences; } @Override public int checkPermission(String permission, int pid, int uid) { return PERMISSION_GRANTED; } - @Override protected AuthPreferences getAuthPreferences() { return authPreferences; } + @Override protected AuthPreferences getAuthPreferences(Integer settingsId) { return authPreferences; } @Override protected void notifyUser(int icon, NotificationCompat.Builder builder) { sentNotifications.add(builder); } @@ -137,15 +136,6 @@ public class SmsBackupServiceTest { assertThat(service.getState().exception).isInstanceOf(RequiresWifiException.class); } - @Test public void shouldCheckForLoginCredentials() throws Exception { - Intent intent = new Intent(); - when(authPreferences.isLoginInformationSet()).thenReturn(false); - shadowConnectivityManager.setBackgroundDataSetting(true); - service.handleIntent(intent); - verifyZeroInteractions(backupTask); - assertThat(service.getState().exception).isInstanceOf(RequiresLoginException.class); - } - @Test public void shouldCheckForEnabledDataTypes() throws Exception { when(dataTypePreferences.enabled()).thenReturn(EnumSet.noneOf(DataType.class));