diff --git a/ImapNote2/build.gradle b/ImapNote2/build.gradle index 647de409..d5110a81 100644 --- a/ImapNote2/build.gradle +++ b/ImapNote2/build.gradle @@ -2,12 +2,13 @@ apply plugin: 'com.android.application' android { compileSdkVersion 21 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" + //noinspection GroovyMissingReturnStatement defaultConfig { applicationId "com.Pau.ImapNotes2" - minSdkVersion 16 - targetSdkVersion 16 + minSdkVersion 19 + targetSdkVersion 19 } buildTypes { @@ -17,6 +18,7 @@ android { } } + //noinspection GroovyMissingReturnStatement packagingOptions { exclude 'META-INF/LICENSE.txt' } @@ -29,4 +31,7 @@ dependencies { compile files('libs/commons-io-2.4.jar') compile files('libs/javamaildir-0.6.jar') compile files('libs/mail.jar') + compile 'jdiff:jdiff:1.0.9' + compile 'jp.wasabeef:richeditor-android:1.2.1' + } diff --git a/ImapNote2/src/main/AndroidManifest.xml b/ImapNote2/src/main/AndroidManifest.xml index 9680deb1..ade0dba0 100644 --- a/ImapNote2/src/main/AndroidManifest.xml +++ b/ImapNote2/src/main/AndroidManifest.xml @@ -1,75 +1,106 @@ - - - - - - - - - - - - + - - - - - - - - - - + + + + + + + + + - - - + - - - + + + + + + + + - - - + + + - - - - - - + + + + - + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/AccontConfigurationActivity.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/AccontConfigurationActivity.java deleted file mode 100644 index fcd54633..00000000 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/AccontConfigurationActivity.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.Pau.ImapNotes2; - -import java.util.ArrayList; -import java.util.List; - -import com.Pau.ImapNotes2.Data.ConfigurationFile; -import com.Pau.ImapNotes2.Data.ImapNotes2Account; -import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; -import com.Pau.ImapNotes2.Miscs.Imaper; - -import android.accounts.Account; -import android.accounts.AccountAuthenticatorActivity; -import android.accounts.AccountManager; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.CountDownTimer; -import android.support.v4.app.NavUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.WindowManager; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; - -public class AccontConfigurationActivity extends AccountAuthenticatorActivity implements OnItemSelectedListener{ - public static final int TO_REFRESH = 999; - public static final String AUTHORITY = "com.Pau.ImapNotes2.provider"; - private static final String TAG = "AccontConfigurationActivity"; - - private Imaper imapFolder; - - private TextView accountnameTextView; - private TextView usernameTextView; - private TextView passwordTextView; - private TextView serverTextView; - private TextView portnumTextView; - private TextView syncintervalTextView; - private TextView folderTextView; - private CheckBox stickyCheckBox; - private Spinner securitySpinner; - private ImapNotes2Account imapNotes2Account; - private String security; - private int security_i; - private String action; - private String accountname; - private ConfigurationFile settings; - private static Account myAccount = null; - private static AccountManager accountManager; - - private OnClickListener clickListenerLogin = new View.OnClickListener() { - @Override - public void onClick(View v) { - // Click on Login Button - if (((String) accountnameTextView.getText().toString()).contains("'")) { - // Single quotation marks are not allowed in accountname - Toast.makeText(getApplicationContext(), "Quotation marks are not allowed in accountname", - Toast.LENGTH_LONG).show(); - } else { - DoLogin(v); - } - } - }; - - private OnClickListener clickListenerEdit = new View.OnClickListener() { - @Override - public void onClick(View v) { - // Click on Edit Button - if (((String) accountnameTextView.getText().toString()).contains("'")) { - // Single quotation marks are not allowed in accountname - Toast.makeText(getApplicationContext(), "Quotation marks are not allowed in accountname", - Toast.LENGTH_LONG).show(); - } else { - DoLogin(v); - } - } - }; - - private OnClickListener clickListenerRemove = new View.OnClickListener() { - @Override - public void onClick(View v) { - // Clic on Remove Button - accountManager.removeAccount(myAccount, null, null); - Toast.makeText(getApplicationContext(), "Account has been removed", - Toast.LENGTH_LONG).show(); - finish();//finishing activity - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.account_selection); - getActionBar().setDisplayHomeAsUpEnabled(true); - this.accountnameTextView = (TextView)(findViewById(R.id.accountnameEdit)); - this.usernameTextView = (TextView)findViewById(R.id.usernameEdit); - this.passwordTextView = (TextView)findViewById(R.id.passwordEdit); - this.serverTextView = (TextView)findViewById(R.id.serverEdit); - this.portnumTextView = (TextView)findViewById(R.id.portnumEdit); - this.syncintervalTextView = (TextView)findViewById(R.id.syncintervalEdit); - this.folderTextView = (TextView)findViewById(R.id.folderEdit); - this.stickyCheckBox = (CheckBox)findViewById(R.id.stickyCheckBox); - - securitySpinner = (Spinner) findViewById(R.id.securitySpinner); - List list = new ArrayList(); - list.add("None"); - list.add("SSL/TLS"); - list.add("SSL/TLS (accept all certificates)"); - list.add("STARTTLS"); - list.add("STARTTLS (accept all certificates)"); - ArrayAdapter dataAdapter = new ArrayAdapter - (this, android.R.layout.simple_spinner_item,list); - dataAdapter.setDropDownViewResource - (android.R.layout.simple_spinner_dropdown_item); - securitySpinner.setAdapter(dataAdapter); - // Spinner item selection Listener - securitySpinner.setOnItemSelectedListener(this); - - imapNotes2Account = new ImapNotes2Account(); - this.imapFolder = ((ImapNotes2)getApplicationContext()).GetImaper(); - this.settings = new ConfigurationFile(this.getApplicationContext()); - - Bundle extras = getIntent().getExtras(); - if (extras != null) { - if (extras.containsKey("action")) { - action = extras.getString("action"); - } - if (extras.containsKey("accountname")) { - accountname = extras.getString("accountname"); - } - } - - if (this.settings != null) { - this.accountnameTextView.setText(this.settings.GetAccountname()); - this.usernameTextView.setText(this.settings.GetUsername()); - this.passwordTextView.setText(this.settings.GetPassword()); - this.serverTextView.setText(this.settings.GetServer()); - this.portnumTextView.setText(this.settings.GetPortnum()); - this.security = this.settings.GetSecurity(); - if (this.security == null) this.security = "0"; - this.security_i = Integer.parseInt(this.security); - this.securitySpinner.setSelection(this.security_i); - this.stickyCheckBox.setChecked(Boolean.parseBoolean(this.settings.GetUsesticky())); - this.syncintervalTextView.setText("15"); - this.folderTextView.setText(this.settings.GetFoldername()); - } - - LinearLayout layout = (LinearLayout) findViewById(R.id.bttonsLayout); - accountManager = AccountManager.get(getApplicationContext()); - Account[] accounts = accountManager.getAccountsByType("com.Pau.ImapNotes2"); - for (Account account : accounts) { - if (account.name.equals(accountname)) { - myAccount = account; - break; - } - } - - if ((this.action == null) || (this.myAccount == null)) { this.action = "CREATE_ACCOUNT"; } - - if (this.action.equals("EDIT_ACCOUNT")) { - // Here we have to edit an existing account - this.accountnameTextView.setText(this.accountname); - this.usernameTextView.setText(this.accountManager.getUserData (myAccount, "username")); - this.passwordTextView.setText(this.accountManager.getPassword(myAccount)); - this.serverTextView.setText(this.accountManager.getUserData(myAccount, "server")); - this.portnumTextView.setText(this.accountManager.getUserData(myAccount, "portnum")); - this.security = this.accountManager.getUserData (myAccount, "security"); - this.stickyCheckBox.setChecked(Boolean.parseBoolean(this.accountManager.getUserData(myAccount,"usesticky"))); - this.syncintervalTextView.setText(this.accountManager.getUserData(myAccount, "syncinterval")); - this.folderTextView.setText(this.accountManager.getUserData (myAccount, "imapfolder")); - if (this.security == null) this.security = "0"; - this.security_i = Integer.parseInt(this.security); - this.securitySpinner.setSelection(this.security_i); - Button buttonEdit = new Button(this); - buttonEdit.setText("Save"); - buttonEdit.setOnClickListener(clickListenerEdit); - layout.addView(buttonEdit); - Button buttonRemove = new Button(this); - buttonRemove.setText("Remove"); - buttonRemove.setOnClickListener(clickListenerRemove); - layout.addView(buttonRemove); - } else { - // Here we have to create a new account - Button buttonView = new Button(this); - buttonView.setText("Check & Create Account"); - buttonView.setOnClickListener(clickListenerLogin); - layout.addView(buttonView); - } - - // Don't display keyboard when on note detail, only if user touches the screen - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN - ); - } - - // DoLogin method is defined in account_selection.xml (account_selection layout) - public void DoLogin(View v) { - ProgressDialog loadingDialog = ProgressDialog.show(this, "ImapNotes2" , "Logging into your account... ", true); - this.imapNotes2Account.SetAccountname(this.accountnameTextView.getText().toString().trim()); - this.imapNotes2Account.SetUsername(this.usernameTextView.getText().toString().trim()); - this.imapNotes2Account.SetPassword(this.passwordTextView.getText().toString().trim()); - this.imapNotes2Account.SetServer(this.serverTextView.getText().toString().trim()); - this.imapNotes2Account.SetPortnum(this.portnumTextView.getText().toString()); - this.imapNotes2Account.SetSecurity(this.security); - this.imapNotes2Account.SetUsesticky(String.valueOf(this.stickyCheckBox.isChecked())); - this.imapNotes2Account.SetSyncinterval(this.syncintervalTextView.getText().toString()); - this.imapNotes2Account.SetFoldername(this.folderTextView.getText().toString()); - long SYNC_FREQUENCY = Long.parseLong(syncintervalTextView.getText().toString(), 10) * 60; - new LoginThread().execute(this.imapFolder, this.imapNotes2Account, loadingDialog, this, this.action, SYNC_FREQUENCY); - - } - - class LoginThread extends AsyncTask { - - private AccontConfigurationActivity accontConfigurationActivity; - private ImapNotes2Result res = new ImapNotes2Result(); - String action; - - protected Boolean doInBackground(Object... stuffs) { - this.action = (String)stuffs[4]; - try { - this.res=((Imaper)stuffs[0]).ConnectToProvider( - ((ImapNotes2Account)stuffs[1]).GetUsername(), - ((ImapNotes2Account)stuffs[1]).GetPassword(), - ((ImapNotes2Account)stuffs[1]).GetServer(), - ((ImapNotes2Account)stuffs[1]).GetPortnum(), - ((ImapNotes2Account)stuffs[1]).GetSecurity(), - ((ImapNotes2Account)stuffs[1]).GetUsesticky(), - ((ImapNotes2Account)stuffs[1]).GetFoldername()); - accontConfigurationActivity = (AccontConfigurationActivity)stuffs[3]; - if (this.res.returnCode==0) { - Account account = new Account(((ImapNotes2Account)stuffs[1]).GetAccountname(), "com.Pau.ImapNotes2"); - long SYNC_FREQUENCY = (long)stuffs[5]; - AccountManager am = AccountManager.get(((AccontConfigurationActivity)stuffs[3])); - accontConfigurationActivity.setResult(AccontConfigurationActivity.TO_REFRESH); - Bundle result = null; - if (this.action.equals("EDIT_ACCOUNT")) { - result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); - setAccountAuthenticatorResult(result); - am.setUserData(account, "username", ((ImapNotes2Account)stuffs[1]).GetUsername()); - am.setUserData(account, "server", ((ImapNotes2Account)stuffs[1]).GetServer()); - am.setUserData(account, "portnum", ((ImapNotes2Account)stuffs[1]).GetPortnum()); - am.setUserData(account, "syncinterval", ((ImapNotes2Account)stuffs[1]).GetSyncinterval()); - am.setUserData(account, "security", ((ImapNotes2Account)stuffs[1]).GetSecurity()); - am.setUserData(account, "usesticky", ((ImapNotes2Account)stuffs[1]).GetUsesticky()); - am.setUserData(account, "imapfolder", ((ImapNotes2Account)stuffs[1]).GetFoldername()); - // Run the Sync Adapter Periodically - ContentResolver.setIsSyncable(account, AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, AUTHORITY, true); - ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY); - this.res.errorMessage = "Account has been modified"; - return true; - } else { - if (am.addAccountExplicitly(account, ((ImapNotes2Account)stuffs[1]).GetPassword(), null)) { - result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); - setAccountAuthenticatorResult(result); - am.setUserData(account, "username", ((ImapNotes2Account)stuffs[1]).GetUsername()); - am.setUserData(account, "server", ((ImapNotes2Account)stuffs[1]).GetServer()); - am.setUserData(account, "portnum", ((ImapNotes2Account)stuffs[1]).GetPortnum()); - am.setUserData(account, "syncinterval", ((ImapNotes2Account)stuffs[1]).GetSyncinterval()); - am.setUserData(account, "security", ((ImapNotes2Account)stuffs[1]).GetSecurity()); - am.setUserData(account, "usesticky", ((ImapNotes2Account)stuffs[1]).GetUsesticky()); - am.setUserData(account, "imapfolder", ((ImapNotes2Account)stuffs[1]).GetFoldername()); - // Run the Sync Adapter Periodically - ContentResolver.setIsSyncable(account, AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, AUTHORITY, true); - ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY); - this.res.errorMessage = "Account has been added"; - return true; - } else { - this.res.errorMessage = "Account already exists or is null"; - return false; - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - ((ProgressDialog)stuffs[2]).dismiss(); - } - return false; - } - - protected void onPostExecute(Boolean result){ - if(result){ - accontConfigurationActivity.settings.Clear(); - this.accontConfigurationActivity.accountnameTextView.setText(""); - this.accontConfigurationActivity.usernameTextView.setText(""); - this.accontConfigurationActivity.passwordTextView.setText(""); - this.accontConfigurationActivity.serverTextView.setText(""); - this.accontConfigurationActivity.portnumTextView.setText(""); - this.accontConfigurationActivity.syncintervalTextView.setText("15"); - this.accontConfigurationActivity.securitySpinner.setSelection(0); - this.accontConfigurationActivity.folderTextView.setText(""); - this.accontConfigurationActivity.stickyCheckBox.setChecked(false); - } - final Toast tag = Toast.makeText(getApplicationContext(), this.res.errorMessage,Toast.LENGTH_LONG); - tag.show(); - new CountDownTimer(5000, 1000) { - public void onTick(long millisUntilFinished) {tag.show();} - public void onFinish() {tag.show();} - }.start(); - if (this.action.equals("EDIT_ACCOUNT")) finish(); - } - } - - public boolean onCreateOptionsMenu(Menu menu) { - return true; - } - - public boolean onOptionsItemSelected (MenuItem item){ - switch (item.getItemId()){ - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - this.security = Integer.toString(position); - if ((position == 0) || (position == 3) || (position == 4)) - this.portnumTextView.setText("143"); - if ((position == 1) || (position == 2)) - this.portnumTextView.setText("993"); - } - - @Override - public void onNothingSelected(AdapterView parent) { - // TODO Auto-generated method stub - } - -} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/AccountConfigurationActivity.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/AccountConfigurationActivity.java new file mode 100644 index 00000000..f08adac4 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/AccountConfigurationActivity.java @@ -0,0 +1,512 @@ +package com.Pau.ImapNotes2; + +import android.accounts.Account; +import android.accounts.AccountAuthenticatorActivity; +import android.accounts.AccountManager; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.NavUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.NumberPicker; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.Pau.ImapNotes2.Data.ConfigurationFieldNames; +import com.Pau.ImapNotes2.Data.ImapNotes2Account; +import com.Pau.ImapNotes2.Data.Security; +import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; +import com.Pau.ImapNotes2.Miscs.Imaper; +import com.Pau.ImapNotes2.Miscs.Result; + +import java.util.List; + + +public class AccountConfigurationActivity extends AccountAuthenticatorActivity implements OnItemSelectedListener { + private static final int TO_REFRESH = 999; + private static final String AUTHORITY = "com.Pau.ImapNotes2.provider"; + private static final String TAG = "IN_AccountConfActivity"; + + private Imaper imapFolder; + + + private TextView accountnameTextView; + private TextView devicenameTextView; + private TextView usernameTextView; + private TextView passwordTextView; + private TextView serverTextView; + private TextView portnumTextView; + private NumberPicker syncIntervalNumberPicker; + private TextView folderTextView; + private CheckBox stickyCheckBox; + private CheckBox automaticMergeCheckBox; + private Spinner securitySpinner; + @NonNull + private Security security = Security.None; + //private int security_i; + @Nullable + private Actions action; + @Nullable + private String accountname; + @Nullable + private static Account myAccount = null; + + private static AccountManager accountManager; + + /** + * Cannot be final or NonNull because it needs the application context which is not available + * until onCreate. + */ + //private ConfigurationFile settings; + + //region Intent item names and values. + public static final String ACTION = "ACTION"; + public static final String ACCOUNTNAME = "ACCOUNTNAME"; +// public static final String EDIT_ACCOUNT = "EDIT_ACCOUNT"; +// public static final String CREATE_ACCOUNT = "CREATE_ACCOUNT"; + //endregion + + + /** + * + */ + enum Actions { + CREATE_ACCOUNT, + EDIT_ACCOUNT + } + + + private final OnClickListener clickListenerLogin = new View.OnClickListener() { + @Override + public void onClick(View v) { + // Click on Login Button + Log.d(TAG, "clickListenerLogin onClick"); + CheckNameAndLogIn(); + } + }; +/* + private final TextWatcher textWatcher = new TextWatcher(){ + + public void beforeTextChanged(CharSequence chars, int start, int count, int after){} + public void afterTextChanged(Editable editable){} + public void onTextChanged(CharSequence chars, int start, int before, int count) { + + } + + };*/ + + private final OnClickListener clickListenerEdit = new View.OnClickListener() { + @Override + public void onClick(View v) { + // Click on Edit Button + Log.d(TAG, "clickListenerEdit onClick"); + CheckNameAndLogIn(); + } + }; + + + private void Toast(int message) { + Toast.makeText(getApplicationContext(), message, + Toast.LENGTH_LONG).show(); + + } + + private void CheckNameAndLogIn() { + if (accountnameTextView.getText().toString().contains("'")) { + // Single quotation marks are not allowed in accountname + //Toast.makeText(getApplicationContext(), R.string.quotation_marks_not_allowed, + // Toast.LENGTH_LONG).show(); + Toast(R.string.quotation_marks_not_allowed); + } else { + DoLogin(); + } + } + + private final OnClickListener clickListenerRemove = new View.OnClickListener() { + @Override + public void onClick(View v) { + // Click on Remove Button + accountManager.removeAccount(myAccount, null, null); +// Toast.makeText(getApplicationContext(), R.string.account_removed, + // Toast.LENGTH_LONG).show(); + Toast(R.string.account_removed); + finish();//finishing activity + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //settings = new ConfigurationFile(getApplicationContext()); + setContentView(R.layout.account_selection); + //noinspection ConstantConditions + getActionBar().setDisplayHomeAsUpEnabled(true); + TextView headingTextView = findTextViewById(R.id.heading); + accountnameTextView = findTextViewById(R.id.accountnameEdit); + devicenameTextView = findTextViewById(R.id.devicenameEdit); + usernameTextView = findTextViewById(R.id.usernameEdit); + passwordTextView = findTextViewById(R.id.passwordEdit); + serverTextView = findTextViewById(R.id.serverEdit); + portnumTextView = findTextViewById(R.id.portnumEdit); + //syncintervalTextView = findTextViewById(R.id.syncintervalEdit); + //syncintervalTextView.addTextChangedListener(textWatcher); + syncIntervalNumberPicker = (NumberPicker) findViewById(R.id.syncintervalMinutes); + syncIntervalNumberPicker.setMaxValue(24 * 60); + syncIntervalNumberPicker.setMinValue(1); + syncIntervalNumberPicker.setValue(15); + + folderTextView = findTextViewById(R.id.folderEdit); + stickyCheckBox = (CheckBox) findViewById(R.id.stickyCheckBox); + automaticMergeCheckBox = (CheckBox) findViewById(R.id.automaticMergeCheckBox); + + securitySpinner = (Spinner) findViewById(R.id.securitySpinner); + /*List list = new ArrayList(); + list.add("None"); + list.add("SSL/TLS"); + list.add("SSL/TLS (accept all certificates)"); + list.add("STARTTLS"); + list.add("STARTTLS (accept all certificates)"); + */ + List list = Security.Printables(); + ArrayAdapter dataAdapter = new ArrayAdapter<> + (this, android.R.layout.simple_spinner_item, list); + dataAdapter.setDropDownViewResource + (android.R.layout.simple_spinner_dropdown_item); + securitySpinner.setAdapter(dataAdapter); + // Spinner item selection Listener + securitySpinner.setOnItemSelectedListener(this); + + //imapNotes2Account = new ImapNotes2Account(); + imapFolder = ((ImapNotes2k) getApplicationContext()).GetImaper(); + //settings = new ConfigurationFile(); + + Bundle extras = getIntent().getExtras(); + // TODO: find out if extras can be null. + if (extras != null) { + if (extras.containsKey(ACTION)) { + action = (Actions) (extras.getSerializable(ACTION)); + } + if (extras.containsKey(ACCOUNTNAME)) { + accountname = extras.getString(ACCOUNTNAME); + } + } + + + // Settings can never be null so there is no need to guard it + //if (settings != null) { +/* + accountnameTextView.setText(settings.GetAccountname()); + usernameTextView.setText(settings.GetUsername()); + passwordTextView.setText(settings.GetPassword()); + serverTextView.setText(settings.GetServer()); + portnumTextView.setText(settings.GetPortnum()); + security = settings.GetSecurity(); + // Can never be null. if (security == null) security = "0"; + //int security_i = security.ordinal(); + securitySpinner.setSelection(security.ordinal()); + stickyCheckBox.setChecked(settings.GetUsesticky()); + automaticMergeCheckBox.setChecked(settings.GetUseAutomaticMerge()); + folderTextView.setText(settings.GetFoldername()); +*/ + //syncintervalTextView.setText(R.string.default_sync_interval); + //} + + LinearLayout layout = (LinearLayout) findViewById(R.id.buttonsLayout); + accountManager = AccountManager.get(getApplicationContext()); + Account[] accounts = accountManager.getAccountsByType("com.Pau.ImapNotes2"); + for (Account account : accounts) { + if (account.name.equals(accountname)) { + myAccount = account; + break; + } + } + + // action can never be null + if (myAccount == null) { + action = Actions.CREATE_ACCOUNT; + } + + if (action == Actions.EDIT_ACCOUNT) { + // Here we have to edit an existing account + headingTextView.setText(R.string.editAccount); + accountnameTextView.setText(accountname); + devicenameTextView.setText(GetConfigValue(ConfigurationFieldNames.DeviceId)); + usernameTextView.setText(GetConfigValue(ConfigurationFieldNames.UserName)); + passwordTextView.setText(accountManager.getPassword(myAccount)); + serverTextView.setText(GetConfigValue(ConfigurationFieldNames.Server)); + portnumTextView.setText(GetConfigValue(ConfigurationFieldNames.PortNumber)); + Log.d(TAG, "Security: " + GetConfigValue(ConfigurationFieldNames.Security)); + security = Security.from(GetConfigValue(ConfigurationFieldNames.Security)); + stickyCheckBox.setChecked(Boolean.parseBoolean(GetConfigValue(ConfigurationFieldNames.UseSticky))); + automaticMergeCheckBox.setChecked(Boolean.parseBoolean(GetConfigValue(ConfigurationFieldNames.UseAutomaticMerge))); + //syncintervalTextView.setText(GetConfigValue(ConfigurationFieldNames.SyncInterval)); + syncIntervalNumberPicker.setValue(Integer.parseInt(GetConfigValue(ConfigurationFieldNames.SyncInterval))); + folderTextView.setText(GetConfigValue(ConfigurationFieldNames.ImapFolder)); + //if (security == null) security = "0"; + //security_i = security.ordinal(); + securitySpinner.setSelection(security.ordinal()); + Button buttonEdit = new Button(this); + buttonEdit.setText(R.string.save); + Log.d(TAG, "Set onclick listener edit"); + buttonEdit.setOnClickListener(clickListenerEdit); + layout.addView(buttonEdit); + Button buttonRemove = new Button(this); + buttonRemove.setText(R.string.remove); + buttonRemove.setOnClickListener(clickListenerRemove); + layout.addView(buttonRemove); + } else { + // Here we have to create a new account + Button buttonView = new Button(this); + buttonView.setText(R.string.check_and_create_account); + Log.d(TAG, "Set onclick listener login"); + buttonView.setOnClickListener(clickListenerLogin); + layout.addView(buttonView); + } + + // Don't display keyboard when on note detail, only if user touches the screen + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN + ); + } + + private TextView findTextViewById(int id) { + return (TextView) (findViewById(id)); + } + + private String GetConfigValue(@NonNull String name) { + return accountManager.getUserData(myAccount, name); + } + + private String GetTextViewText(@NonNull TextView textView) { + return textView.getText().toString().trim(); + } + + // DoLogin method is defined in account_selection.xml (account_selection layout) + private void DoLogin() { + Log.d(TAG, "DoLogin"); + final ImapNotes2Account imapNotes2Account = new ImapNotes2Account( + GetTextViewText(accountnameTextView), + GetTextViewText(usernameTextView), + GetTextViewText(passwordTextView), + GetTextViewText(serverTextView), + GetTextViewText(portnumTextView), + GetTextViewText(devicenameTextView), + security, + stickyCheckBox.isChecked(), + automaticMergeCheckBox.isChecked(), + syncIntervalNumberPicker.getValue(), + GetTextViewText(folderTextView)); + // No need to check for valid numbers because the field only allows digits. But it is + // possible to remove all characters which causes the program to crash. The easiest fix is + // to add a zero at the beginning so that we are guaranteed to be able to parse it but that + // leaves us with a zero sync. interval. + /* Result synchronizationInterval = GetSynchronizationInterval(); + if (synchronizationInterval.succeeded) { +*/ + new LoginThread( + imapNotes2Account, + this, + action).execute(); + // } + } +/* + + Result GetSynchronizationInterval() { + final String syncInterval = GetTextViewText(syncintervalTextView).trim(); + if (TextUtils.isDigitsOnly(syncInterval)){ + Log.d(TAG, "GetSynchronizationInterval: " + syncInterval); + final int syncIntervalInt = Integer.parseInt(GetTextViewText(syncintervalTextView), 10) * 60; + if (syncIntervalInt > 0) { + return new Result(syncIntervalInt, true); + } + Toast.makeText(this, "Synchronization interval must be greater than zero: <" + syncInterval + ">.", Toast.LENGTH_LONG).show(); + } + Toast.makeText(this, "Synchronization interval is invalid: <" + syncInterval + ">.", Toast.LENGTH_LONG).show(); + return new Result(0, false); + } +*/ + + class LoginThread extends AsyncTask> { + + private final ImapNotes2Account imapNotes2Account; + private final ProgressDialog progressDialog; + //private final int synchronizationInterval; + + private final AccountConfigurationActivity accountConfigurationActivity; + + private final Actions action; + + LoginThread(ImapNotes2Account imapNotes2Account, + AccountConfigurationActivity accountConfigurationActivity, + Actions action) { + this.imapNotes2Account = imapNotes2Account; + //this.progressDialog = loadingDialog; + this.accountConfigurationActivity = accountConfigurationActivity; + this.action = action; + //this.synchronizationInterval = synchronizationInterval; + this.progressDialog = ProgressDialog.show(accountConfigurationActivity, + getString(R.string.app_name), + getString(R.string.logging_in), + true); + + } + + /* + + class Result{ + final String message; + final boolean succeeded; + + Result(String message, + boolean succeeded) { + this.message = message; + this.succeeded = succeeded; + } + } + */ + @NonNull + protected Result doInBackground(Void... none) { + try { + ImapNotes2Result res = imapFolder.ConnectToProvider( + imapNotes2Account.username, + imapNotes2Account.password, + imapNotes2Account.server, + imapNotes2Account.portnum, + imapNotes2Account.security + ); + //accountConfigurationActivity = acountConfigurationActivity; + if (res.returnCode != Imaper.ResultCodeSuccess) { + return new Result<>("IMAP operation failed: " + res.errorMessage, false); + } + // TODO: Find out if "com.Pau.ImapNotes2" is the same as getApplicationContext().getPackageName(). + final Account account = new Account(imapNotes2Account.accountName, "com.Pau.ImapNotes2"); + final AccountManager am = AccountManager.get(accountConfigurationActivity); + accountConfigurationActivity.setResult(AccountConfigurationActivity.TO_REFRESH); + if (action == Actions.EDIT_ACCOUNT) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + setAccountAuthenticatorResult(result); + setUserData(am, account); + // Run the Sync Adapter Periodically + ContentResolver.setIsSyncable(account, AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, AUTHORITY, true); + ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), imapNotes2Account.syncInterval); + return new Result<>("Account has been modified", true); + } else { + if (!am.addAccountExplicitly(account, imapNotes2Account.password, null)) { + return new Result<>(getString(R.string.account_already_exists_or_is_null), false); + } + // TODO: make function for these repeated lines. + Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + setAccountAuthenticatorResult(result); + setUserData(am, account); + // Run the Sync Adapter Periodically + ContentResolver.setIsSyncable(account, AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, AUTHORITY, true); + ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), imapNotes2Account.syncInterval); + return new Result<>(getString(R.string.account_added), true); + } + } catch (Exception e) { + e.printStackTrace(); + return new Result<>("Unexpected exception: " + e.getMessage(), false); + } finally { + progressDialog.dismiss(); + } + } + + + private void setUserData(@NonNull AccountManager am, + @NonNull Account account) { + am.setUserData(account, ConfigurationFieldNames.UserName, imapNotes2Account.username); + am.setUserData(account, ConfigurationFieldNames.DeviceId, imapNotes2Account.deviceId); + am.setUserData(account, ConfigurationFieldNames.Server, imapNotes2Account.server); + am.setUserData(account, ConfigurationFieldNames.PortNumber, imapNotes2Account.portnum); + am.setUserData(account, ConfigurationFieldNames.SyncInterval, Integer.toString(imapNotes2Account.syncInterval)); + am.setUserData(account, ConfigurationFieldNames.Security, imapNotes2Account.security.name()); + am.setUserData(account, ConfigurationFieldNames.UseSticky, String.valueOf(imapNotes2Account.usesticky)); + am.setUserData(account, ConfigurationFieldNames.ImapFolder, imapNotes2Account.imapfolder); + } + + protected void onPostExecute(@NonNull Result result) { + if (result.succeeded) { + accountConfigurationActivity.Clear(); + } + ShowToast(result.result, 5); + if (action == Actions.EDIT_ACCOUNT) { + finish(); + } + } + + void ShowToast(String message, + int durationSeconds) { + final Toast tag = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG); + tag.show(); + new CountDownTimer(durationSeconds * 1000, 1000) { + public void onTick(long millisUntilFinished) { + tag.show(); + } + + public void onFinish() { + tag.show(); + } + }.start(); + } + } + + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + security = Security.from(position); + portnumTextView.setText(security.defaultPort); + } + + @Override + public void onNothingSelected(AdapterView parent) { + // TODO Auto-generated method stub + } + + void Clear() { + + accountnameTextView.setText(""); + usernameTextView.setText(""); + passwordTextView.setText(""); + serverTextView.setText(""); + portnumTextView.setText(""); + syncIntervalNumberPicker.setValue(15); + securitySpinner.setSelection(0); + folderTextView.setText(""); + stickyCheckBox.setChecked(false); + } +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFieldNames.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFieldNames.java new file mode 100644 index 00000000..61319423 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFieldNames.java @@ -0,0 +1,14 @@ +package com.Pau.ImapNotes2.Data; + +public final class ConfigurationFieldNames { + public static final String UserName = "username"; + static final String Password = "password"; + public static final String UseSticky = "usesticky"; + public static final String UseAutomaticMerge = "UseAutomaticMerge"; + public static final String ImapFolder = "imapfolder"; + public static final String Server = "server"; + public static final String PortNumber = "portnum"; + public static final String SyncInterval = "syncinterval"; + public static final String Security = "security"; + public static final String DeviceId = "deviceId"; +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFile.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFile.java index b4ec7b09..10b977b2 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFile.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ConfigurationFile.java @@ -1,177 +1,245 @@ package com.Pau.ImapNotes2.Data; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; -import javax.xml.parsers.DocumentBuilderFactory; +import com.Pau.ImapNotes2.ImapNotes2k; import org.w3c.dom.Document; import org.w3c.dom.NodeList; -import org.xmlpull.v1.XmlSerializer; -import android.content.Context; -import android.util.Log; -import android.util.Xml; +import java.io.File; + +import javax.xml.parsers.DocumentBuilderFactory; + +//import android.util.Log; public class ConfigurationFile { - private Context applicationContext; + // For logging. private static final String TAG = "IN_ConfigurationFile"; - + + // TODO: make all fields final. + // The account name is the concatenation of the username and server. + @Nullable private String accountname; + // User name on the IMAP server. + @Nullable private String username; + @Nullable private String password; + // Address of the IMAP server + @Nullable private String server; + // Port number. + @Nullable private String portnum; - private String security; - private String usesticky; + // TLS, etc. + @NonNull + private Security security = Security.None; + // ? + private boolean usesticky; + + private boolean useAutomaticMerge; + + // The name of the IMAP folder to be used. + @Nullable private String imapfolder; - - - public ConfigurationFile(Context myContext){ - this.applicationContext = myContext; - + + @NonNull + private final Context applicationContext; + + + public ConfigurationFile(@NonNull Context applicationContext) { + this.applicationContext = applicationContext; try { Document fileToLoad = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( - new File(this.applicationContext.getFilesDir()+"/ImapNotes2.conf")); - this.username = this.LoadItemFromXML(fileToLoad, "username").item(0).getChildNodes().item(0).getNodeValue(); - this.password = this.LoadItemFromXML(fileToLoad, "password").item(0).getChildNodes().item(0).getNodeValue(); - this.server = this.LoadItemFromXML(fileToLoad, "server").item(0).getChildNodes().item(0).getNodeValue(); - this.imapfolder = this.LoadItemFromXML(fileToLoad, "imapfolder").item(0).getChildNodes().item(0).getNodeValue(); - this.accountname = this.username + "@" + this.server; - if (this.LoadItemFromXML(fileToLoad, "portnum").getLength() == 0) + new File(ImapNotes2k.ConfigurationFilePath(applicationContext))); + username = NodeValueFromXML(fileToLoad, ConfigurationFieldNames.UserName); + password = NodeValueFromXML(fileToLoad, ConfigurationFieldNames.Password); + server = NodeValueFromXML(fileToLoad, ConfigurationFieldNames.Server); + imapfolder = NodeValueFromXML(fileToLoad, ConfigurationFieldNames.ImapFolder); + accountname = username + "@" + server; + // All of these can be simplified by initializing the fields to the default values and + // only setting when the value exists in the file. + if (LoadItemFromXML(fileToLoad, ConfigurationFieldNames.Security).getLength() != 0) { + Log.d(TAG, "nfx: " + NodeValueFromXML(fileToLoad, ConfigurationFieldNames.Security)); + security = Security.from(NodeValueFromXML(fileToLoad, ConfigurationFieldNames.Security)); + } + if (LoadItemFromXML(fileToLoad, ConfigurationFieldNames.PortNumber).getLength() == 0) { // portnum option doesn't exist - this.portnum = ""; - else - this.portnum = this.LoadItemFromXML(fileToLoad, "portnum").item(0).getChildNodes().item(0).getNodeValue(); - if (this.LoadItemFromXML(fileToLoad, "security").getLength() == 0) - // security option doesn't exist, say "0" - this.security = "0"; - else - this.security = this.LoadItemFromXML(fileToLoad, "security").item(0).getChildNodes().item(0).getNodeValue(); - if (this.LoadItemFromXML(fileToLoad, "usesticky").getLength() == 0) - // usesticky option doesn't exist, say no - this.usesticky = "false"; - else - this.usesticky = this.LoadItemFromXML(fileToLoad, "usesticky").item(0).getChildNodes().item(0).getNodeValue(); + portnum = security.defaultPort; + } else { + portnum = NodeValueFromXML(fileToLoad, ConfigurationFieldNames.PortNumber); + } + + usesticky = LoadItemFromXML(fileToLoad, ConfigurationFieldNames.UseSticky).getLength() != 0 && + Boolean.parseBoolean(NodeValueFromXML(fileToLoad, ConfigurationFieldNames.UseSticky)); + + // Automatic merge allows you to edit the same note on different devices at the same + // time an have the changes merged automatically. + useAutomaticMerge = LoadItemFromXML(fileToLoad, ConfigurationFieldNames.UseAutomaticMerge).getLength() != 0 && + Boolean.parseBoolean(NodeValueFromXML(fileToLoad, ConfigurationFieldNames.UseAutomaticMerge)); //Log.d(TAG, "conf file present, we read data"); } catch (Exception e) { + // TODO: This catch should be turned into a simple if then and the catch + // reserved for conditions that cannot be checked for. //Log.d(TAG, "Conf file absent, go to the exception that initializes variables"); - this.accountname = ""; - this.username = ""; - this.password = ""; - this.server = ""; - this.portnum = ""; - this.security = "0"; - this.usesticky = "false"; - this.imapfolder = ""; + accountname = ""; + username = ""; + password = ""; + server = ""; + security = Security.None; + portnum = security.defaultPort; + usesticky = false; + useAutomaticMerge = false; + imapfolder = ""; } } - - public String GetAccountname(){ - return this.accountname; - } - - public String GetUsername(){ - return this.username; - } - - public void SetUsername(String Username){ - this.username = Username; - } - - public String GetPassword(){ - return this.password; - } - - public void SetPassword(String Password){ - this.password = Password; + + @Nullable + public String GetAccountname() { + return accountname; } - - public String GetServer(){ - return this.server; + + @Nullable + public String GetUsername() { + return username; } - - public void SetServer(String Server){ - this.server = Server; + +// --Commented out by Inspection START (11/26/16 11:42 PM): +// public void SetUsername(String Username) { +// username = Username; +// } +// --Commented out by Inspection STOP (11/26/16 11:42 PM) + + @Nullable + public String GetPassword() { + return password; } - - public String GetPortnum(){ - return this.portnum; + +// --Commented out by Inspection START (11/26/16 11:42 PM): +// public void SetPassword(String Password) { +// password = Password; +// } +// --Commented out by Inspection STOP (11/26/16 11:42 PM) + + @Nullable + public String GetServer() { + return server; } - - public void SetPortnum(String Portnum){ - this.portnum = Portnum; + +// --Commented out by Inspection START (11/26/16 11:42 PM): +// public void SetServer(String Server) { +// server = Server; +// } +// --Commented out by Inspection STOP (11/26/16 11:42 PM) + + @Nullable + public String GetPortnum() { + return portnum; } - - public String GetSecurity(){ - return this.security; + +// --Commented out by Inspection START (11/26/16 11:42 PM): +// public void SetPortnum(String Portnum) { +// portnum = Portnum; +// } +// --Commented out by Inspection STOP (11/26/16 11:42 PM) + + @NonNull + public Security GetSecurity() { + return security; } - - public void SetSecurity(String Security){ - this.security = Security; +/* + + public void SetSecurity(Security security) { + security = security; } - - public String GetUsesticky(){ - return this.usesticky; +*/ + + public boolean GetUsesticky() { + return usesticky; } - - public void SetUsesticky(String Usesticky){ - this.usesticky = Usesticky; + + public boolean GetUseAutomaticMerge() { + return useAutomaticMerge; } +/* - public String GetFoldername(){ - return this.imapfolder; + public void SetUsesticky(boolean usesticky) { + this.usesticky = usesticky; } - - public void Clear(){ - new File(this.applicationContext.getFilesDir()+"/ImapNotes2.conf").delete(); - this.username=null; - this.password=null; - this.server=null; - this.portnum=null; - this.security=null; - this.usesticky=null; - this.imapfolder = null; +*/ + + @Nullable + public String GetFoldername() { + return imapfolder; } - - public void SaveConfigurationToXML() throws IllegalArgumentException, IllegalStateException, IOException{ - FileOutputStream configurationFile = this.applicationContext.openFileOutput("ImapNotes2.conf", Context.MODE_PRIVATE); - XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(configurationFile, "UTF-8"); - serializer.startDocument(null, Boolean.valueOf(true)); - serializer.startTag(null, "Configuration"); - serializer.startTag(null, "username"); - serializer.text(this.username); - serializer.endTag(null, "username"); - serializer.startTag(null, "password"); - serializer.text(this.password); - serializer.endTag(null, "password"); - serializer.startTag(null, "server"); - serializer.text(this.server); - serializer.endTag(null, "server"); - serializer.startTag(null, "portnum"); - serializer.text(this.portnum); - serializer.endTag(null, "portnum"); - serializer.startTag(null, "security"); - serializer.text(this.security); - serializer.endTag(null, "security"); - serializer.startTag(null,"imapfolder"); - serializer.text(this.imapfolder); - serializer.endTag(null, "imapfolder"); - serializer.startTag(null, "usesticky"); - serializer.text(this.usesticky); - serializer.endTag(null, "usesticky"); - serializer.endTag(null, "Configuration"); - serializer.endDocument(); - serializer.flush(); - configurationFile.close(); + + + public void Clear() { + //noinspection ResultOfMethodCallIgnored + new File(ImapNotes2k.ConfigurationFilePath(applicationContext)).delete(); + username = null; + password = null; + server = null; + portnum = null; + security = Security.None; + usesticky = false; + useAutomaticMerge= false; + imapfolder = null; } - - private NodeList LoadItemFromXML(Document fileLoaded, String tag){ + +// --Commented out by Inspection START (11/26/16 11:42 PM): +// // This function could take the context as an argument. +// // In addition the name of the file should be a named constant +// // because it is used elewhere. +// public void SaveConfigurationToXML() +// throws IllegalArgumentException, IllegalStateException, IOException { +// FileOutputStream configurationFile +// = ImapNotes2k.getAppContext().openFileOutput(ImapNotes2k.ConfigurationFilePath(), +// Context.MODE_PRIVATE); +// XmlSerializer serializer = Xml.newSerializer(); +// serializer.setOutput(configurationFile, "UTF-8"); +// serializer.startDocument(null, true); +// serializer.startTag(null, "Configuration"); +// SerializeText(serializer, ConfigurationFieldNames.UserName, username); +// SerializeText(serializer, ConfigurationFieldNames.Password, password); +// SerializeText(serializer, ConfigurationFieldNames.Server, server); +// SerializeText(serializer, ConfigurationFieldNames.PortNumber, portnum); +// SerializeText(serializer, ConfigurationFieldNames.Security, security.name()); +// SerializeText(serializer, ConfigurationFieldNames.ImapFolder, imapfolder); +// SerializeText(serializer, ConfigurationFieldNames.UseSticky, String.valueOf(usesticky)); +// serializer.endTag(null, "Configuration"); +// serializer.endDocument(); +// serializer.flush(); +// configurationFile.close(); +// } +// --Commented out by Inspection STOP (11/26/16 11:42 PM) +/* + // Avoid repeated literal tag names. + private void SerializeText(@NonNull XmlSerializer serializer, + String tag, + String text) + throws IOException { + serializer.startTag(null, tag); + serializer.text(text); + serializer.endTag(null, tag); + }*/ + + private NodeList LoadItemFromXML(@NonNull Document fileLoaded, + String tag) { return fileLoaded.getElementsByTagName(tag); - } + + // Reduce clutter and improve maintainability. + private String NodeValueFromXML(@NonNull Document fileLoaded, + String tag) { + return LoadItemFromXML(fileLoaded, tag).item(0).getChildNodes().item(0).getNodeValue(); + } + + } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Db.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Db.java new file mode 100644 index 00000000..15992e98 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Db.java @@ -0,0 +1,113 @@ +package com.Pau.ImapNotes2.Data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; + +/** + * Created by kj on 2017-01-15 14:29. + */ + +public class Db { + + private static final String TAG = "IN_Db"; + + @NonNull + SQLiteDatabase notesDb; + @NonNull + private final NotesDbHelper defaultHelper; + @NonNull + public final NotesDb notes; + @NonNull + public final VectorDb vectors; + + public Db(@NonNull Context applicationContext) { + this.defaultHelper = new NotesDbHelper(applicationContext); + notes = new NotesDb(this); + vectors = new VectorDb(this); + } + + + /** + * TODO: can we make this implement closeable? + */ + public void OpenDb() { + this.notesDb = this.defaultHelper.getWritableDatabase(); + + } + + public void CloseDb() { + this.notesDb.close(); + + } + + public void insert(@NonNull String table, + String nullColumnHack, + @NonNull ContentValues values) { + notesDb.insert(table, nullColumnHack, values); + } + + + /** + * Database helper that creates and maintains the SQLite database. + */ + + private class NotesDbHelper extends SQLiteOpenHelper { + + private static final int NOTES_VERSION = 3; + + private static final String DATABASE_NAME = "NotesDb"; + + + NotesDbHelper(@NonNull Context currentApplicationContext) { + super(currentApplicationContext, DATABASE_NAME, null, NOTES_VERSION); + } + + @Override + public void onCreate(@NonNull SQLiteDatabase _db) { + CreateNotesDb(_db); + } + + private void CreateNotesDb(@NonNull SQLiteDatabase _db) { + _db.execSQL(NotesDb.CREATE_NOTES_DB); + vectors.CreateTables(_db); + } + + @Override + public void onUpgrade(@NonNull SQLiteDatabase _db, + int oldVersion, + int newVersion) { + //Log.d(TAG,"onUpgrade from:"+oldVersion+" to:"+newVersion); + for (int i = oldVersion; i < newVersion; i++) { + PATCHES[i - 2].apply(_db); + } + } + + private class Patch { + public void apply(SQLiteDatabase _db) { + } + } + + private final Patch[] PATCHES = new Patch[]{ + new Patch() { + public void apply(@NonNull SQLiteDatabase _db) { + //Log.d(TAG,"upgrade: v2 to v3"); + _db.execSQL("Drop table notesTable;"); + CreateNotesDb(_db); + } + } +/* + ,new Patch() { + public void apply(SQLiteDatabase _db) { + Log.d(TAG,"upgrade: v3 to v4"); + _db.execSQL("Drop table notesTable;"); + _db.execSQL(NotesDb.CREATE_NOTES_DB); + } + } +*/ + }; + } + +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ImapNotes2Account.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ImapNotes2Account.java index 31b637a4..ca1a290a 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ImapNotes2Account.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/ImapNotes2Account.java @@ -1,132 +1,303 @@ package com.Pau.ImapNotes2.Data; import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Date; public class ImapNotes2Account { - private String accountname = ""; - private String username = ""; - private String password = ""; - private String server = ""; - private String portnum = ""; - private String security = ""; - private String usesticky = ""; - private String syncinterval = "15"; - private String imapfolder = ""; - private Boolean accountHasChanged = false; - private Account account = null; - - - public ImapNotes2Account() { - } - - public String toString() { - return this.accountname + ":" + this.username + ":" + this.password + ":" - + this.server + ":" + this.portnum + ":" + this.security + ":" - + this.usesticky + ":" + this.imapfolder + ":" + this.accountHasChanged.toString(); - } + private static final String TAG = "IN_ImapNotes2Account"; + @NonNull + public final String accountName; + @NonNull + public final String username; + @NonNull + public final String password; + @NonNull + public final String server; + @NonNull + public final String portnum; + @NonNull + public final String deviceId; + @NonNull + public final Security security; + public final boolean usesticky; + public final int syncInterval; + @NonNull + public final String imapfolder; + @Nullable + private final Account account; + public boolean usesAutomaticMerge = false; - public String GetAccountname() { - return this.accountname; + + public ImapNotes2Account(@NonNull String accountName, + @NonNull String username, + @NonNull String password, + @NonNull String server, + @NonNull String portNumber, + @NonNull String deviceId, + @NonNull Security security, + boolean useSticky, + boolean usesAutomaticMerge, + int syncInterval, + @NonNull String folderName) { + account = null; + this.accountName = accountName; + this.username = username; + this.password = password; + this.server = server; + this.security = security; + this.portnum = portNumber; + this.deviceId = deviceId; + this.usesticky = useSticky; + this.usesAutomaticMerge = usesAutomaticMerge; + this.imapfolder = folderName; + this.syncInterval = syncInterval; } - - public void SetAccount(Account account) { + + private File dirForNewFiles; + + private File dirForDeletedFiles; + + private File rootDir; + + public ImapNotes2Account(@NonNull Account account, + @NonNull Context applicationContext) { + this.accountName = account.name; + rootDir = new File(applicationContext.getFilesDir(), accountName); + dirForNewFiles = new File(rootDir, "new"); + dirForDeletedFiles = new File(rootDir, "deleted"); + this.account = account; + AccountManager am = AccountManager.get(applicationContext); + syncInterval = Integer.parseInt(am.getUserData(account, ConfigurationFieldNames.SyncInterval)); + username = am.getUserData(account, ConfigurationFieldNames.UserName); + password = am.getPassword(account); + server = am.getUserData(account, ConfigurationFieldNames.Server); + portnum = am.getUserData(account, ConfigurationFieldNames.PortNumber); + deviceId = EnsureNonEmptyDeviceId(am.getUserData(account, ConfigurationFieldNames.DeviceId)); + security = Security.from(am.getUserData(account, ConfigurationFieldNames.Security)); + usesticky = "true".equals(am.getUserData(account, ConfigurationFieldNames.UseSticky)); + imapfolder = am.getUserData(account, ConfigurationFieldNames.ImapFolder); + } + + + /** + * If the given id is null or empty then generate a new one based on the current time, otherwise + * return the incoming value stripped of leading and trailing whitespace. + * * @param deviceId + * + * @return + */ + String EnsureNonEmptyDeviceId(String deviceId) { + final String id = deviceId == null ? "" : deviceId.trim(); + return id != "" ? id : GenerateDeviceId(); + } + + + /** + * Generate a human readable id from the current time: + * + * @return + */ + private String GenerateDeviceId() { + final int now = (int) (new Date().getTime() / 1000); + final int jan_1_2017 = 1483228800; + int id = now - jan_1_2017; + return HumanReadable(id); + + } + + + /** + * Create a human readable string from the given number. Use an alphabet that avoids hard to + * distinguish characters. + * + * @param id + * @return + */ + private String HumanReadable(int id) { + final String digits = "0123456789abcdefghjkmnpqrstuvwxyz"; + final int base = digits.length(); + Log.d(TAG, "base: " + Integer.toString(base)); + int remainingId = id; + String result = ""; + Log.d(TAG, "id: " + Integer.toString(id)); + while (true) { + Log.d(TAG, "result: /" + result + "/"); + Log.d(TAG, "remainingId : " + Integer.toString(remainingId)); + if (remainingId < base) { + result += digits.charAt((int) remainingId); + return result; + } + final int remainder = id % base; + Log.d(TAG, "remainder: " + Integer.toString(remainder)); + result += digits.charAt(remainder); + remainingId /= base; + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public void CreateLocalDirectories() { + Log.d(TAG, "CreateLocalDirs(String: " + accountName); + dirForNewFiles.mkdirs(); + dirForDeletedFiles.mkdirs(); + } + + + public void ClearHomeDir() { + try { + FileUtils.deleteDirectory(rootDir); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + + /* + @NonNull + public String toString() { + return this.accountName + ":" + this.username + ":" + this.password + ":" + + this.server + ":" + this.portnum + ":" + this.security + ":" + + this.usesticky + ":" + this.imapfolder + ":" + Boolean.toString(this.accountHasChanged); + }*/ +/* + + public String GetAccountName() { + return accountName; } - + +*/ + @Nullable public Account GetAccount() { return this.account; } - - public void SetAccountname(String Accountname) { - if (this.accountname.equals(Accountname)) this.accountHasChanged = true; - this.accountname = Accountname; - } - + + //public void SetAccountname(String accountName) { + // this.accountName = accountName; + //} +/* + + @NonNull public String GetUsername() { return this.username; } - - public void SetUsername(String Username) { + + public void SetUsername(@NonNull String Username) { this.username = Username; } - +*/ + + /* @NonNull public String GetPassword() { return this.password; } - - public void SetPassword(String Password) { + + public void SetPassword(@NonNull String Password) { + this.password = Password; } - + + @NonNull public String GetServer() { return this.server; } - - public void SetServer(String Server) { + + public void SetServer(@NonNull String Server) { this.server = Server; } - +*/ + /* @NonNull public String GetPortnum() { return this.portnum; } - - public void SetPortnum(String Portnum) { + + public void SetPortnum(@NonNull String Portnum) { + this.portnum = Portnum; } - - public String GetSecurity() { - return this.security; - } - - public void SetSecurity(String Security) { - this.security = Security; - } - - public String GetUsesticky() { - return this.usesticky; + + @NonNull + public Security GetSecurity() { + return security; } - - public void SetUsesticky(String Usesticky) { - this.usesticky = Usesticky; + + public void SetSecurity(@NonNull Security security) { + + this.security = security; } - - public String GetSyncinterval() { - return this.syncinterval; + + public void SetSecurity(String security) { + Log.d(TAG, "Set: " + security); + SetSecurity(Security.from(security)); } - - public void SetSyncinterval(String Syncinterval) { - this.syncinterval = Syncinterval; + + public boolean GetUsesticky() { + return this.usesticky; } - - public void SetaccountHasChanged() { - this.accountHasChanged = true; +*/ + //public void SetUsesticky(boolean Usesticky) { + // this.usesticky = Usesticky; + //} + + /* public String GetSyncinterval() { + return this.syncInterval; } - +*/ + //public void SetSyncinterval(String Syncinterval) { + // this.syncInterval = Syncinterval; + //} + + /* public void SetaccountHasNotChanged() { this.accountHasChanged = false; } - + */ +/* + + @NonNull public Boolean GetaccountHasChanged() { return this.accountHasChanged; } +*/ - public String GetFoldername(){ +/* + @Nullable + public String GetFoldername() { return this.imapfolder; } - public void SetFoldername(String folder) { + private void SetFolderName(@NonNull String folder) { this.imapfolder = folder; } - - public void Clear() { - this.username=null; - this.password=null; - this.server=null; - this.portnum=null; - this.security=null; - this.usesticky=null; - this.imapfolder=null; - this.accountHasChanged=false; + + public boolean GetUsesAutomaticMerge() { + return this.usesAutomaticMerge; + } + public void SetUsesAutomaticMerge(boolean usesAutomaticMerge) { + this.usesAutomaticMerge = usesAutomaticMerge; } +*/ + +/* + public void Clear() { + this.username = null; + this.password = null; + this.server = null; + this.portnum = null; + this.security = Security.None; + this.usesticky = false; + this.imapfolder = null; + this.accountHasChanged = false; + }*/ } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/MergeMessages.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/MergeMessages.java new file mode 100644 index 00000000..ca658949 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/MergeMessages.java @@ -0,0 +1,21 @@ +package com.Pau.ImapNotes2.Data; + +/** + * Created by kj on 12/25/16. + + + This class contains methods to fetch ancestors, current versions, etc., and do a three way merge using jdiff. + + */ +public class MergeMessages { + + + /** + * Figure out if there is a conflict, if so fetch the current and ancestor and merge with the local file. Replace + * the local file with the merged version. + * @param id This is the subject line of the message. + */ + MergeMessages(String id) { + + } +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NoteVector.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NoteVector.java new file mode 100644 index 00000000..6bc7475f --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NoteVector.java @@ -0,0 +1,76 @@ +package com.Pau.ImapNotes2.Data; + +import android.support.annotation.NonNull; + +import java.util.HashMap; + +/** + * Created by kj on 2017-01-09 11:13. + */ + +public class NoteVector extends HashMap { + + enum ComparisonResult { + Later, + Equal, + Earlier, + Conflict; + } + + NoteVector() { + + } + + + /** + * @param deviceID + * @return Clock or zero if the deviceID is not found. + */ + int get(String deviceID) { + return containsKey(deviceID) + ? get(deviceID) + : 0; + } + + + // Returns EARLIER if this object is unambiguously earlier than other. + public ComparisonResult compareTo(@NonNull NoteVector other) { + ComparisonResult result = ComparisonResult.Equal; + for (Entry entry : this.entrySet()) { + Integer otherClock = other.get(entry.getKey()); + ComparisonResult singleResult = compareEntries(entry.getValue(), otherClock); + switch (result) { + case Equal: + result = singleResult; + break; + case Earlier: + if (singleResult == ComparisonResult.Later) { + return ComparisonResult.Conflict; + } + break; + case Later: + if (singleResult == ComparisonResult.Earlier) { + return ComparisonResult.Conflict; + } + case Conflict: + // cannot occur + break; + default: { + // Nothing to do, cannot occur. + } + } + } + return result; + } + + // Returns EARLIER if a is earlier than b. + public ComparisonResult compareEntries(@NonNull Integer aClock, + @NonNull Integer bClock) { + return (aClock < bClock) + ? ComparisonResult.Earlier + : ((bClock < aClock) + ? ComparisonResult.Later + : ComparisonResult.Equal); + } +} + diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NotesDb.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NotesDb.java index 5da9495e..be08bb89 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NotesDb.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/NotesDb.java @@ -1,175 +1,129 @@ package com.Pau.ImapNotes2.Data; +import android.content.ContentValues; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.util.Log; + import java.text.DateFormat; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import com.Pau.ImapNotes2.Miscs.OneNote; +public class NotesDb { + + private static final String TAG = "IN_NotesDb"; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; -public class NotesDb { + private static final String COL_TITLE = "title"; + private static final String COL_DATE = "date"; + private static final String COL_NUMBER = "number"; + private static final String COL_ACCOUNT_NAME = "accountname"; + private static final String TABLE_NAME = "notesTable"; - private static final int NOTES_VERSION = 3; - private static final String TAG = "IN_NotesDb"; - private Context ctx; - - private static final String CREATE_NOTES_DB = "CREATE TABLE IF NOT EXISTS " - + "notesTable (" - + "pk integer primary key autoincrement, " - + "title text not null, " - + "date text not null, " - + "number text not null, " - + "accountname text not null);"; - - private SQLiteDatabase notesDb; - private NotesDbHelper defaultHelper; - - public NotesDb(Context applicationContext){ - this.defaultHelper = new NotesDbHelper(applicationContext, "NotesDb", NOTES_VERSION); - this.ctx = applicationContext; - - } - - public void OpenDb(){ - this.notesDb = this.defaultHelper.getWritableDatabase(); - - } - - public void CloseDb(){ - this.notesDb.close(); - - } - - public void InsertANoteInDb(OneNote noteElement, String accountname){ + public static final String CREATE_NOTES_DB = "CREATE TABLE IF NOT EXISTS " + + TABLE_NAME + + " (pk integer primary key autoincrement, " + + COL_TITLE + " text not null, " + + COL_DATE + " text not null, " + + COL_NUMBER + " text not null, " + + COL_ACCOUNT_NAME + " text not null);"; + + + private final Db db; + + public NotesDb(@NonNull Db db) { + this.db = db; + + } + + public void InsertANoteInDb(@NonNull OneNote noteElement, + @NonNull String accountname) { ContentValues tableRow = new ContentValues(); - tableRow.put("title", (noteElement.GetTitle() != null) ? noteElement.GetTitle() : ""); - tableRow.put("date", noteElement.GetDate()); - tableRow.put("number", noteElement.GetUid()); - tableRow.put("accountname", accountname); - this.notesDb.insert("notesTable", null, tableRow); - //Log.d(TAG, "note inserted"); + tableRow.put(COL_TITLE, noteElement.GetTitle()); + tableRow.put(COL_DATE, noteElement.GetDate()); + tableRow.put(COL_NUMBER, noteElement.GetUid()); + tableRow.put(COL_ACCOUNT_NAME, accountname); + db.insert(TABLE_NAME, null, tableRow); + //Log.d(TAG, "note inserted"); } - public void DeleteANote(String number, String accountname){ - this.notesDb.execSQL("delete from notesTable where number = '" + number + - "' and accountname = '" + accountname + "'"); + public void DeleteANote(@NonNull String number, + @NonNull String accountname) { + db.notesDb.execSQL("delete from notesTable where number = '" + number + + "' and accountname = '" + accountname + "'"); } - public void UpdateANote(String olduid, String newuid, String accountname){ + public void UpdateANote(@NonNull String olduid, + @NonNull String newuid, + @NonNull String accountname) { + /* TODO: use sql template and placeholders instead of string concatenation. + */ String req = "update notesTable set number='" + newuid + "' where number='-" + olduid + "' and accountname='" + accountname + "'"; - this.notesDb.execSQL(req); + db.notesDb.execSQL(req); } - public String GetDate(String uid, String accountname){ - String selectQuery = "select date from notesTable where number = '" + uid + "' and accountname='"+accountname+"'"; - Cursor c = this.notesDb.rawQuery(selectQuery, null); - if (c.moveToFirst()) { + public String GetDate(@NonNull String uid, + @NonNull String accountname) { + /* Returns a string representing the modification time of the note. + TODO: use date class. + */ + String selectQuery = "select date from notesTable where number = '" + uid + "' and accountname='" + accountname + "'"; + try (Cursor c = db.notesDb.rawQuery(selectQuery, null)) { + if (c.moveToFirst()) { return c.getString(0); + } } return ""; } - public String GetTempNumber(String accountname) { - String selectQuery = "select case when cast(max(abs(number)+1) as int) > 0 then cast(max(abs(number)+1) as int)*-1 else '-1' end from notesTable where number < '0' and accountname='"+accountname+"'"; - Cursor c = this.notesDb.rawQuery(selectQuery, null); - if (c.moveToFirst()) { + public String GetTempNumber(@NonNull String accountname) { + String selectQuery = "select case when cast(max(abs(number)+1) as int) > 0 then cast(max(abs(number)+1) as int)*-1 else '-1' end from notesTable where number < '0' and accountname='" + accountname + "'"; + try (Cursor c = db.notesDb.rawQuery(selectQuery, null)) { + if (c.moveToFirst()) { return c.getString(0); + } } return "-1"; } - public void GetStoredNotes(ArrayList noteList, String accountname){ - noteList.clear(); - Date date=null; - Cursor resultPointer = this.notesDb.query("notesTable", null,"accountname = ?", new String[]{accountname},null,null,"date DESC"); - - if(resultPointer.moveToFirst()){ - int titleIndex = resultPointer.getColumnIndex("title"); - int bodyIndex = resultPointer.getColumnIndex("body"); - int dateIndex = resultPointer.getColumnIndex("date"); - int numberIndex = resultPointer.getColumnIndex("number"); - int positionIndex = resultPointer.getColumnIndex("position"); - int colorIndex = resultPointer.getColumnIndex("color"); - do { - String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); - try { - date = sdf.parse(resultPointer.getString(dateIndex)); - } catch(ParseException e){ - //Exception handling - } catch(Exception e){ - //handle exception - } - DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(this.ctx); - //String sdate = dateFormat.format(date); - String sdate = DateFormat.getDateTimeInstance().format(date); - - noteList.add(new OneNote(resultPointer.getString(titleIndex), - sdate, - resultPointer.getString(numberIndex))); - } while (resultPointer.moveToNext()); + public void GetStoredNotes(@NonNull ArrayList noteList, + @NonNull String accountName) { + noteList.clear(); + Date date = null; + try (Cursor resultPointer = db.notesDb.query(TABLE_NAME, null, "accountname = ?", + new String[]{accountName}, null, null, "date DESC")) { + + if (resultPointer.moveToFirst()) { + int titleIndex = resultPointer.getColumnIndex(COL_TITLE); + //int bodyIndex = resultPointer.getColumnIndex("body"); + int dateIndex = resultPointer.getColumnIndex(COL_DATE); + int numberIndex = resultPointer.getColumnIndex(COL_NUMBER); + //int positionIndex = resultPointer.getColumnIndex("position"); + //int colorIndex = resultPointer.getColumnIndex("color"); + do { + //String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + //SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.ROOT); + try { + date = Utilities.internalDateFormat.parse(resultPointer.getString(dateIndex)); + } catch (ParseException e) { + Log.d(TAG, "Parsing data from database failed: " + e.getMessage()); + } + //DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(this.ctx); + //String sdate = dateFormat.format(date); + String sdate = DateFormat.getDateTimeInstance().format(date); + + noteList.add(new OneNote(resultPointer.getString(titleIndex), + sdate, + resultPointer.getString(numberIndex))); + } while (resultPointer.moveToNext()); + } } - - } - - public void ClearDb(String accountname){ - this.notesDb.execSQL("delete from notesTable where accountname = '" + accountname+"'"); - + } - - /** - * Database helper that creates and maintains the SQLite database. - */ - - private static class NotesDbHelper extends SQLiteOpenHelper { - - public NotesDbHelper(Context currentApplicationContext, String dbName, int dbVersion) { - super(currentApplicationContext, dbName, null, dbVersion); - } - - @Override - public void onCreate(SQLiteDatabase _db) { - _db.execSQL(NotesDb.CREATE_NOTES_DB); - } - - @Override - public void onUpgrade(SQLiteDatabase _db, int oldVersion, int newVersion) { - //Log.d(TAG,"onUpgrade from:"+oldVersion+" to:"+newVersion); - for (int i=oldVersion; i { + public static final String TITLE = "title"; + public static final String DATE = "date"; + private static final String UID = "uid"; + + + /** + * + */ + private static final long serialVersionUID = 1L; + + + public OneNote(String title, String date, String uid) { + super(); + put(TITLE, title); + put(DATE, date); + put(UID, uid); + } + + @NonNull + public String GetTitle() { + return this.get(TITLE); + } + + @NonNull + String GetDate() { + return this.get(DATE); + } + + @NonNull + public String GetUid() { + return this.get(UID); + } + + + public void SetDate(String date) { + this.put(DATE, date); + } + + public void SetUid(String uid) { + this.put(UID, uid); + } + + @NonNull + @Override + public String toString() { + return ("Title:" + this.GetTitle() + + " Date: " + this.GetDate() + + " Uid: " + this.GetUid()); + } +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Security.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Security.java new file mode 100644 index 00000000..2b6d06dd --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Security.java @@ -0,0 +1,89 @@ +package com.Pau.ImapNotes2.Data; + +import android.annotation.SuppressLint; +import android.support.annotation.NonNull; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Created by kj on 11/1/16. + *

+ * Use this instead of integers in the account configuration. Store the name of the security type + * instead. + * + * The items annotated with @SuppressWarnings("unused") are used but only by code that calls the + * from method so the analyser cannot see that they are used + */ +public enum Security { + None("None", "", "imap", false), + @SuppressWarnings("unused") + SSL_TLS("SSL/TLS", "993", "imaps", false), + @SuppressWarnings("unused") + SSL_TLS_accept_all_certificates("SSL/TLS (accept all certificates)", "993", "imaps", true), + @SuppressWarnings("unused") + STARTTLS("STARTTLS", "143", "imaps", false), + @SuppressWarnings("unused") + STARTTLS_accept_all_certificates("STARTTLS (accept all certificates)", "143", "imaps", true); + + @NonNull + static final String TAG = "IN_Security"; + public final String proto; + public final boolean acceptcrt; + + + private final String printable; + public final String defaultPort; + + Security(String printable, + String defaultPort, + String proto, + boolean acceptcrt) { + this.printable = printable; + this.defaultPort = defaultPort; + this.proto = proto; + this.acceptcrt = acceptcrt; + } + + @NonNull + public static List Printables() { + List list = new ArrayList<>(); + for (Security e : Security.values()) { + list.add(e.printable); + } + return list; + } + + // Mapping from integer. See http://dan.clarke.name/2011/07/enum-in-java-with-int-conversion/ + @SuppressLint("UseSparseArrays") // False positive, SparseArray cannot be used here. + private static final Map _map = new HashMap<>(); + + static { + for (Security security : Security.values()) + _map.put(security.ordinal(), security); + } + + @NonNull + public static Security from(int ordinal) { + return _map.get(ordinal); + } + + @NonNull + public static Security from(String name) { + Log.d(TAG, "from: <" + name + ">"); + for (Security security : Security.values()) { + if (Objects.equals(security.name(), name)) { + return security; + } + } + // Wasn't recognized, try using the ordinal instead, backwards compatibility. + int i = Integer.parseInt(name); + return from(i); + } + + +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Utilities.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Utilities.java new file mode 100644 index 00000000..cc1ce5f8 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/Utilities.java @@ -0,0 +1,23 @@ +package com.Pau.ImapNotes2.Data; + +import android.support.annotation.NonNull; + +import java.text.SimpleDateFormat; +import java.util.Locale; + +/** + * Created by kj on 2016-11-12 17:21. + *

+ * Reduce repetition by providing static fields and methods for common operations. + */ +public class Utilities { + + /** + * The notes have a time stamp associated with thme and this is stored as a string on the + * server so we must define a fixed format for it. + */ + @NonNull + private static final String internalDateFormatString = "yyyy-MM-dd HH:mm:ss"; + @NonNull + public static final SimpleDateFormat internalDateFormat = new SimpleDateFormat(internalDateFormatString, Locale.ROOT); +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/VectorDb.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/VectorDb.java new file mode 100644 index 00000000..ca48036a --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/VectorDb.java @@ -0,0 +1,264 @@ +package com.Pau.ImapNotes2.Data; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.io.File; + + +/** + * Created by kj on 2017-01-09 10:28. + *

+ * This class manages the tables used for vector clocks and other data associated with automatic + * conflict resolution and merging. + */ + +class VectorDb { + + + /** + * Reference to the SQLite database used for all data in this application. + */ + @NonNull + private final Db db; + @NonNull + private final FileTable fileTable; + @NonNull + private final VectorTable vectorTable; + + public VectorDb(@NonNull Db db) { + this.db = db; + vectorTable = new VectorTable(); + fileTable = new FileTable(); + } + + void CreateTables(SQLiteDatabase db) { + vectorTable.CreateTable(db); + fileTable.CreateTable(db); + } + +/* + public void CreateTables(@NonNull SQLiteDatabase db) { + fileTable.CreateTable(db); + vectorTable.CreateTable(db); + } +*/ + + + class VectorTable { + + private static final String TAG = "IN_VectorTable"; + + private static final String COL_FILE_PATH = "filepath"; + private static final String COL_CLOCK = "clock"; + private static final String COL_ACCOUNT_NAME = "accountname"; + private static final String TABLE_NAME = "vector"; + //private static final String DATABASE_NAME = "NotesDb"; + + private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + + TABLE_NAME + + " (" + + COL_FILE_PATH + " text not null, " + + COL_CLOCK + " text not null, " + + COL_ACCOUNT_NAME + " text not null, " + + " PRIMARY KEY (" + COL_FILE_PATH + ", " + COL_ACCOUNT_NAME + "));"; + + + + void CreateTable(@NonNull SQLiteDatabase db) { + db.execSQL(CREATE_TABLE); + } + + public void Insert(@NonNull File file, + @NonNull String accountname, + int clock) { + ContentValues tableRow = new ContentValues(); + tableRow.put(COL_FILE_PATH, file.getName()); + tableRow.put(COL_ACCOUNT_NAME, accountname); + tableRow.put(COL_CLOCK, clock); + db.notesDb.insertWithOnConflict(TABLE_NAME, null, tableRow, + SQLiteDatabase.CONFLICT_REPLACE); + } +/* + // TODO: Should use place holders instead of string concatenation. + public void Delete(@NonNull File file, + @NonNull String accountname) { + db.notesDb.execSQL("delete from " + TABLE_NAME + " where " + COL_FILE_PATH + " = '" + file.getAbsolutePath() + + "' and accountname = '" + accountname + "'"); + }*/ + + + void IncrementVectorClock(String relativeFile, + String acountName) { + + } + } + + class FileTable { + + private static final String TAG = "IN_FileTable"; + + private static final String COL_FILE_PATH = "filepath"; + private static final String COL_MTIME = "mtime"; + private static final String COL_ACCOUNT_NAME = "accountname"; + private static final String TABLE_NAME = "file"; + //private static final String DATABASE_NAME = "NotesDb"; + + private static final String CREATE_FILE_TABLE = "CREATE TABLE IF NOT EXISTS " + + TABLE_NAME + + " (pk integer primary key autoincrement, " + + COL_FILE_PATH + " text not null, " + + COL_MTIME + " text not null, " + + COL_ACCOUNT_NAME + " text not null);"; + + + + + + void CreateTable(@NonNull SQLiteDatabase db) { + db.execSQL(CREATE_FILE_TABLE); + } + + /** + * Update the mtime in the database and increment the vetor clock for this device. + * + * @param relativeFile + * @param lastModified + * @param accountName + */ + private void UpdateLastModified(String relativeFile, + Long lastModified, + String accountName) { + ContentValues newMTime = new ContentValues(); + newMTime.put(COL_MTIME, lastModified); + + db.notesDb.update(TABLE_NAME, newMTime, + "? = ? and ? = ?", + new String[]{COL_FILE_PATH, relativeFile, COL_ACCOUNT_NAME, accountName}); + } + + public void Insert(@NonNull String relativeFile, + Long lastModified, + @NonNull String accountname, + @NonNull File accountDir) { + ContentValues tableRow = new ContentValues(); + tableRow.put(COL_FILE_PATH, relativeFile); + tableRow.put(COL_MTIME, lastModified); + tableRow.put(COL_ACCOUNT_NAME, accountname); + db.notesDb.insert(TABLE_NAME, null, tableRow); + Log.d(TAG, "note inserted"); + } + + + + // TODO: Should use place holders instead of string concatenation. + public void Delete(@NonNull File file, + @NonNull String accountname) { + db.notesDb.execSQL("delete from " + TABLE_NAME + " where " + COL_FILE_PATH + " = '" + file.getAbsolutePath() + + "' and accountname = '" + accountname + "'"); + } + + + public long GetMTime(@NonNull String relativeFilePath, + @NonNull String accountname) { + try (Cursor c = GetRecord(relativeFilePath, accountname)) { + return (c == null) ? 0 : c.getLong(c.getColumnIndex(COL_MTIME)); + } + } +/* + + public long GetMTime(@NonNull String relativeFilePath, + @NonNull String accountname) { + String selectQuery = "select mtime from " + TABLE_NAME + + " where " + COL_FILE_PATH + " = '" + relativeFilePath + "' " + " " + + " and accountname = '" + accountname + "'"; + try (Cursor c = db.notesDb.rawQuery(selectQuery, null)) { + if (c.moveToFirst()) { + return c.getLong(Cursor.FIELD_TYPE_NULL); + } + } + return 0; + } + +*/ + + @Nullable + public Cursor GetRecord(@NonNull String relativeFilePath, + @NonNull String accountname) { + String selectQuery = "select " + + COL_ACCOUNT_NAME + ", " + COL_FILE_PATH + ", " + COL_MTIME + + " from " + TABLE_NAME + + " where " + COL_FILE_PATH + " = '" + relativeFilePath + "' " + " " + + " and " + COL_ACCOUNT_NAME + " = '" + accountname + "'"; + Cursor c = db.notesDb.rawQuery(selectQuery, null); + return c.moveToFirst() ? c : null; + } + + + } + + + /** + * Update the mtime in the database and increment the vetor clock for this device. + * TODO: Use a transaction to ensure that either the mtime and vector succeed or neither. + * + * @param relativeFile + * @param lastModified + * @param accountName + */ + private void Update(String relativeFile, + Long lastModified, + String accountName) { + fileTable.UpdateLastModified(relativeFile, lastModified, accountName); + vectorTable.IncrementVectorClock(relativeFile, accountName); + } + + /** + * Enumerate the file in the given directory and update the mtime and vector clock in the + * database. + * + * @param accountName + * @param accountRoot + */ + public void UpdateAccount(@NonNull String accountName, + @NonNull File accountRoot) { + for (File file : accountRoot.listFiles()) { + UpdateFile(file, accountName, accountRoot); + } + } + + public void UpdateFile(@NonNull File file, + @NonNull String accountName, + @NonNull File accountRoot) { + + String relativeFile = RelativePath(accountRoot, file); + try (Cursor c = fileTable.GetRecord(RelativePath(accountRoot, file), accountName)) { + if (c == null) { + // Add new record + fileTable.Insert(relativeFile, file.lastModified(), accountName, accountRoot); + } else { + // Update existing record by incrementing the vector + Update(relativeFile, file.lastModified(), accountName); + } + } + } + + /** + * See http://www.java2s.com/Tutorial/Java/0180__File/Getrelativepath.htm + * + * @param base + * @param file + * @return + */ + @NonNull + public String RelativePath(@NonNull final File base, @NonNull final File file) { + final int rootLength = base.getAbsolutePath().length(); + final String absFileName = file.getAbsolutePath(); + return absFileName.substring(rootLength + 1); + } + +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/VectorNote.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/VectorNote.java new file mode 100644 index 00000000..2ffb74aa --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Data/VectorNote.java @@ -0,0 +1,50 @@ +package com.Pau.ImapNotes2.Data; + +import android.support.annotation.NonNull; + +import java.util.Date; +import java.util.HashMap; + + +/** + * Created by kj on 2017-01-09 10:59. + *

+ * Represents metadata about a note in a way that can be used by a ListAdapter. The list adapter + * needs objects that have a map interface because it must fetch the items by string name. + */ +public class VectorNote extends HashMap { + public static final String TITLE = "title"; + public static final String DATE = "date"; + //private static final String UID = "uid"; + + + public VectorNote(@NonNull String title, + long mtime) { + super(); + put(TITLE, title); + put(DATE, new Date(mtime).toString()); + } + + @NonNull + public String GetTitle() { + return this.get(TITLE); + } + + @NonNull + String GetDate() { + return this.get(DATE); + } + + + public void SetDate(@NonNull String date) { + this.put(DATE, date); + } + + + @NonNull + @Override + public String toString() { + return ("Title:" + this.GetTitle() + + " Date: " + this.GetDate()); + } +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/ImapNotes2.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/ImapNotes2.java deleted file mode 100644 index ba693bf2..00000000 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/ImapNotes2.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.Pau.ImapNotes2; - -import java.util.ArrayList; - -import com.Pau.ImapNotes2.Data.ConfigurationFile; -import com.Pau.ImapNotes2.Miscs.Imaper; -import com.Pau.ImapNotes2.Miscs.OneNote; - -import android.app.Application; -import android.content.Context; - -public class ImapNotes2 extends Application { - - private ConfigurationFile thisSessionConfigurationFile; - private Imaper thisSessionImapFolder; - private ArrayList noteList; - private static Context context; - - public void onCreate(){ - super.onCreate(); - ImapNotes2.context = getApplicationContext(); - } - - public static Context getAppContext() { - return ImapNotes2.context; - } - - public void SetConfigurationFile(ConfigurationFile currentSettings){ - this.thisSessionConfigurationFile = currentSettings; - } - - public ConfigurationFile GetConfigurationFile(){ - return this.thisSessionConfigurationFile; - } - - public void SetImaper(Imaper currentImaper){ - this.thisSessionImapFolder = currentImaper; - } - - public Imaper GetImaper(){ - return this.thisSessionImapFolder; - } - - public void SetNotesList(ArrayList currentNotesList){ - this.noteList = currentNotesList; - } - - public ArrayList GetNotesList(){ - return this.noteList; - } - -} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/ImapNotes2k.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/ImapNotes2k.java new file mode 100644 index 00000000..4458c75e --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/ImapNotes2k.java @@ -0,0 +1,82 @@ +package com.Pau.ImapNotes2; + +import android.app.Application; +import android.content.Context; +import android.support.annotation.NonNull; + +import com.Pau.ImapNotes2.Miscs.Imaper; + +import java.io.File; + + +/* +Changed name by appending a k so that I can have this and the original installed side by side, +perhaps. + */ +public class ImapNotes2k extends Application { + + private Imaper thisSessionImapFolder; + + private static final String configurationFileName = "ImapNotes2.conf"; +/* + // Called when starting the application. + public void onCreate() { + super.onCreate(); + // Save the context in a static so that it is easy to access everywhere. + //ImapNotes2k.context = getApplicationContext(); + }*/ + + @NonNull + public static String ConfigurationFilePath(@NonNull Context applicationContext) { + return ConfigurationDirPath(applicationContext) + "/" + configurationFileName; + } + + + public static String ConfigurationDirPath(@NonNull Context applicationContext) { + + return ConfigurationDir(applicationContext).getPath(); + } + + + public static File ConfigurationDir(@NonNull Context applicationContext) { + + return applicationContext.getFilesDir(); + } + + +// --Commented out by Inspection START (11/26/16 11:44 PM): +// // ? +// public void SetConfigurationFile(ConfigurationFile currentSettings) { +// this.thisSessionConfigurationFile = currentSettings; +// } +// --Commented out by Inspection STOP (11/26/16 11:44 PM) + +// --Commented out by Inspection START (11/26/16 11:44 PM): +// // ? +// public ConfigurationFile GetConfigurationFile() { +// return this.thisSessionConfigurationFile; +// } +// --Commented out by Inspection STOP (11/26/16 11:44 PM) + + // ? + public void SetImaper(Imaper currentImaper) { + this.thisSessionImapFolder = currentImaper; + } + + // ? + public Imaper GetImaper() { + return this.thisSessionImapFolder; + } + + /*// ? + public void SetNotesList(ArrayList currentNotesList) { + } +*/ +// --Commented out by Inspection START (11/26/16 11:44 PM): +// // ? +// public ArrayList GetNotesList() { +// return this.noteList; +// } +// --Commented out by Inspection STOP (11/26/16 11:44 PM) + +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Listactivity.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Listactivity.java index 31796e97..ece1e390 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Listactivity.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Listactivity.java @@ -1,27 +1,5 @@ package com.Pau.ImapNotes2; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.ListIterator; - -import org.apache.commons.io.FileUtils; - -import com.Pau.ImapNotes2.R; -import com.Pau.ImapNotes2.Data.NotesDb; -import com.Pau.ImapNotes2.Miscs.Imaper; -import com.Pau.ImapNotes2.Miscs.OneNote; -import com.Pau.ImapNotes2.Data.ImapNotes2Account; -import com.Pau.ImapNotes2.Miscs.UpdateThread; -import com.Pau.ImapNotes2.Miscs.SyncThread; -import com.Pau.ImapNotes2.Sync.SyncService; -import com.Pau.ImapNotes2.Sync.SyncUtils; - import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.OnAccountsUpdateListener; @@ -39,6 +17,8 @@ import android.content.PeriodicSync; import android.content.pm.PackageInfo; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -53,107 +33,157 @@ import android.widget.Filterable; import android.widget.ListView; import android.widget.SearchView; -import android.widget.SimpleAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -public class Listactivity extends Activity implements OnItemSelectedListener,Filterable { +import com.Pau.ImapNotes2.Data.Db; +import com.Pau.ImapNotes2.Data.ImapNotes2Account; +import com.Pau.ImapNotes2.Data.OneNote; +import com.Pau.ImapNotes2.Miscs.Imaper; +import com.Pau.ImapNotes2.Miscs.SyncThread; +import com.Pau.ImapNotes2.Miscs.UpdateThread; +import com.Pau.ImapNotes2.Sync.SyncService; +import com.Pau.ImapNotes2.Sync.SyncUtils; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.ListIterator; + +import static com.Pau.ImapNotes2.AccountConfigurationActivity.ACTION; +import static com.Pau.ImapNotes2.NoteDetailActivity.Colors; + +//import com.Pau.ImapNotes2.Data.NotesDb; + +//import com.Pau.ImapNotes2.R; +//import android.widget.SimpleAdapter; + + +public class Listactivity extends Activity implements OnItemSelectedListener, Filterable { private static final int SEE_DETAIL = 2; - private static final int DELETE_BUTTON = 3; + public static final int DELETE_BUTTON = 3; private static final int NEW_BUTTON = 4; private static final int SAVE_BUTTON = 5; private static final int EDIT_BUTTON = 6; - + + + //region Intent item names + public static final String EDIT_ITEM_NUM_IMAP = "EDIT_ITEM_NUM_IMAP"; + public static final String EDIT_ITEM_TXT = "EDIT_ITEM_TXT"; + public static final String EDIT_ITEM_COLOR = "EDIT_ITEM_COLOR"; + private static final String SAVE_ITEM_COLOR = "SAVE_ITEM_COLOR"; + private static final String SAVE_ITEM = "SAVE_ITEM"; + private static final String DELETE_ITEM_NUM_IMAP = "DELETE_ITEM_NUM_IMAP"; + public static final String ACCOUNTNAME = "ACCOUNTNAME"; + public static final String SYNCINTERVAL = "SYNCINTERVAL"; + public static final String CHANGED = "CHANGED"; + public static final String SYNCED = "SYNCED"; + //endregion + private ArrayList noteList; private NotesListAdapter listToView; private ArrayAdapter spinnerList; - - private Imaper imapFolder; - private static NotesDb storedNotes = null; + + @Nullable + private static Db storedNotes = null; private Spinner accountSpinner; public static ImapNotes2Account imapNotes2Account; private static AccountManager accountManager; - private static Account[] accounts; + // Ensure that we never have to check for null by initializing reference. + @NonNull + private static Account[] accounts = new Account[0]; private static List currentList; - private TextView status = null; + //@Nullable + private TextView status; private static String OldStatus; - private Button editAccountButton=null; - private ListView listview; - public static final String AUTHORITY = "com.Pau.ImapNotes2.provider"; + private static final String AUTHORITY = "com.Pau.ImapNotes2.provider"; private static final String TAG = "IN_Listactivity"; - - private OnClickListener clickListenerEditAccount = new View.OnClickListener() { + + private final OnClickListener clickListenerEditAccount = new View.OnClickListener() { @Override public void onClick(View v) { - // Clic on editAccount Button Intent res = new Intent(); String mPackage = "com.Pau.ImapNotes2"; - String mClass = ".AccontConfigurationActivity"; - res.setComponent(new ComponentName(mPackage,mPackage+mClass)); - res.putExtra("action", "EDIT_ACCOUNT"); - res.putExtra("accountname", Listactivity.imapNotes2Account.GetAccountname()); + String mClass = ".AccountConfigurationActivity"; + res.setComponent(new ComponentName(mPackage, mPackage + mClass)); + res.putExtra(ACTION, AccountConfigurationActivity.Actions.EDIT_ACCOUNT); + res.putExtra(AccountConfigurationActivity.ACCOUNTNAME, Listactivity.imapNotes2Account.accountName); startActivity(res); } }; - - /** Called when the activity is first created. */ + + + /** + * Called when the activity is first created. + */ @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - // Accounts spinner - this.accountSpinner = (Spinner) findViewById(R.id.accountSpinner); - Listactivity.currentList = new ArrayList(); - // Spinner item selection Listener - this.accountSpinner.setOnItemSelectedListener(this); - - imapNotes2Account = new ImapNotes2Account(); - Listactivity.accountManager = AccountManager.get(getApplicationContext()); - Listactivity.accountManager.addOnAccountsUpdatedListener((OnAccountsUpdateListener) - new AccountsUpdateListener(), null, true); - - status = (TextView)findViewById(R.id.status); - - this.spinnerList = new ArrayAdapter - (this, android.R.layout.simple_spinner_item,Listactivity.currentList); - spinnerList.setDropDownViewResource - (android.R.layout.simple_spinner_dropdown_item); - this.accountSpinner.setAdapter(spinnerList); - - this.noteList = new ArrayList(); - ((ImapNotes2)this.getApplicationContext()).SetNotesList(this.noteList); - this.listToView = new NotesListAdapter( - getApplicationContext(), - this.noteList, - R.layout.note_element, - new String[]{"title","date"}, - new int[]{R.id.noteTitle, R.id.noteInformation}); - listview = (ListView) findViewById(R.id.notesList); - listview.setAdapter(this.listToView); - - listview.setTextFilterEnabled(true); - - this.imapFolder = new Imaper(); - ((ImapNotes2)this.getApplicationContext()).SetImaper(this.imapFolder); - - if (Listactivity.storedNotes == null) - storedNotes = new NotesDb(getApplicationContext()); - - // When item is clicked, we go to NoteDetailActivity - listview.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView arg0, View widget, int selectedNote, long arg3) { - Intent toDetail = new Intent(widget.getContext(), NoteDetailActivity.class); - toDetail.putExtra("selectedNote", (OneNote)arg0.getItemAtPosition(selectedNote)); - toDetail.putExtra("useSticky", Listactivity.imapNotes2Account.GetUsesticky()); - startActivityForResult(toDetail,SEE_DETAIL); - } - }); + super.onCreate(savedInstanceState); + Log.d(TAG, "onCreate"); + setContentView(R.layout.main); + + this.accountSpinner = (Spinner) findViewById(R.id.accountSpinner); + Listactivity.currentList = new ArrayList<>(); + + this.accountSpinner.setOnItemSelectedListener(this); + + //imapNotes2Account = new ImapNotes2Account(); + Listactivity.accountManager = AccountManager.get(getApplicationContext()); + Listactivity.accountManager.addOnAccountsUpdatedListener( + new AccountsUpdateListener(), null, true); + + status = (TextView) findViewById(R.id.status); + + this.spinnerList = new ArrayAdapter<> + (this, android.R.layout.simple_spinner_item, Listactivity.currentList); + spinnerList.setDropDownViewResource + (android.R.layout.simple_spinner_dropdown_item); + this.accountSpinner.setAdapter(spinnerList); + + this.noteList = new ArrayList<>(); + //((ImapNotes2k) this.getApplicationContext()).SetNotesList(this.noteList); + this.listToView = new NotesListAdapter( + getApplicationContext(), + this.noteList, + new String[]{OneNote.TITLE, OneNote.DATE}, + new int[]{R.id.noteTitle, R.id.noteInformation}); + ListView listview = (ListView) findViewById(R.id.notesList); + listview.setAdapter(this.listToView); + + listview.setTextFilterEnabled(true); + + Imaper imapFolder = new Imaper(); + ((ImapNotes2k) this.getApplicationContext()).SetImaper(imapFolder); + + if (Listactivity.storedNotes == null) + storedNotes = new Db(getApplicationContext()); + + // When item is clicked, we go to NoteDetailActivity + listview.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(@NonNull AdapterView parent, + @NonNull View widget, + int selectedNote, + long rowId) { + Log.d(TAG, "onItemClick"); + Intent toDetail = new Intent(widget.getContext(), NoteDetailActivity.class); + toDetail.putExtra(NoteDetailActivity.selectedNote, (OneNote) parent.getItemAtPosition(selectedNote)); + toDetail.putExtra(NoteDetailActivity.useSticky, Listactivity.imapNotes2Account.usesticky); + startActivityForResult(toDetail, SEE_DETAIL); + Log.d(TAG, "onItemClick, back from detail."); + + //TriggerSync(status); + } + }); - editAccountButton = (Button) findViewById(R.id.editAccountButton); - editAccountButton.setOnClickListener(clickListenerEditAccount); + Button editAccountButton = (Button) findViewById(R.id.editAccountButton); + editAccountButton.setOnClickListener(clickListenerEditAccount); } @@ -168,10 +198,10 @@ public void onDestroy() { public void onStart() { super.onStart(); - int len = this.accounts == null ? 0 : this.accounts.length; + int len = accounts.length; if (len > 0) updateAccountSpinner(); } - + @Override protected void onResume() { super.onResume(); @@ -184,31 +214,38 @@ protected void onPause() { unregisterReceiver(syncFinishedReceiver); } - private BroadcastReceiver syncFinishedReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String accountname = intent.getStringExtra("ACCOUNTNAME"); - Boolean isChanged = intent.getBooleanExtra("CHANGED", false); - Boolean isSynced = intent.getBooleanExtra("SYNCED", false); - String syncInterval = intent.getStringExtra("SYNCINTERVAL"); - if (accountname.equals(Listactivity.imapNotes2Account.GetAccountname())) { + @NonNull + private final BroadcastReceiver syncFinishedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, @NonNull Intent intent) { + Log.d(TAG, "BroadcastReceiver.onReceive"); + String accountName = intent.getStringExtra(ACCOUNTNAME); + Boolean isChanged = intent.getBooleanExtra(CHANGED, false); + Boolean isSynced = intent.getBooleanExtra(SYNCED, false); + String syncInterval = intent.getStringExtra(SYNCINTERVAL); + Log.d(TAG, "if " + accountName + " " + Listactivity.imapNotes2Account.accountName); + if (accountName.equals(Listactivity.imapNotes2Account.accountName)) { + String statusText; if (isSynced) { // Display last sync date - DateFormat dateFormat = - android.text.format.DateFormat.getDateFormat(getApplicationContext()); + //DateFormat dateFormat = + // android.text.format.DateFormat.getDateFormat(getApplicationContext()); Date date = new Date(); - String sdate = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT).format(date); + String sdate = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date); String sinterval = " (interval:" + String.valueOf(syncInterval) + " min)"; - status.setText("Last sync: " + sdate + sinterval); + statusText = "Last sync: " + sdate + sinterval; } else { - status.setText(OldStatus); + statusText = OldStatus; } + //TextView status = (TextView) findViewById(R.id.status); + status.setText(statusText); if (isChanged) { - if (Listactivity.storedNotes == null) - storedNotes = new NotesDb(getApplicationContext()); + if (storedNotes == null) { + storedNotes = new Db(getApplicationContext()); + } storedNotes.OpenDb(); - storedNotes.GetStoredNotes(noteList, accountname); + storedNotes.notes.GetStoredNotes(noteList, accountName); listToView.notifyDataSetChanged(); storedNotes.CloseDb(); } @@ -216,131 +253,167 @@ public void onReceive(Context context, Intent intent) { } }; - public void RefreshList(){ - ProgressDialog loadingDialog = ProgressDialog.show(this, "ImapNotes2" , "Refreshing notes list... ", true); - - new SyncThread().execute(this.imapFolder, Listactivity.imapNotes2Account, this.noteList, this.listToView, loadingDialog, this.storedNotes, this.getApplicationContext()); - status.setText("Welcome"); + private void RefreshList() { + new SyncThread( + noteList, + listToView, + ShowProgress(R.string.refreshing_notes_list), + storedNotes, + this.getApplicationContext()).execute(); + //TextView status = (TextView) findViewById(R.id.status); + status.setText(R.string.welcome); } - - public void UpdateList(String suid, String noteBody, String color, String action){ - ProgressDialog loadingDialog = ProgressDialog.show(this, "imapnote2" , "Updating notes list... ", true); - - new UpdateThread().execute(this.imapFolder, Listactivity.imapNotes2Account, this.noteList, this.listToView, loadingDialog, suid, noteBody, color, this.getApplicationContext(), action, this.storedNotes); + private void UpdateList(String suid, + String noteBody, + Colors color, + UpdateThread.Action action) { + if (Listactivity.imapNotes2Account.usesAutomaticMerge) { + new UpdateThread(Listactivity.imapNotes2Account, + noteList, + listToView, + ShowProgress(R.string.updating_notes_list), + suid, + noteBody, + color, + getApplicationContext(), + action, + storedNotes).execute(); + } else { + new UpdateThread(Listactivity.imapNotes2Account, + noteList, + listToView, + ShowProgress(R.string.updating_notes_list), + suid, + noteBody, + color, + getApplicationContext(), + action, + storedNotes).execute(); + } } - - public boolean onCreateOptionsMenu(Menu menu){ - getMenuInflater().inflate(R.menu.list, menu); - - // Associate searchable configuration with the SearchView - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - SearchView searchView = - (SearchView) menu.findItem(R.id.search).getActionView(); - searchView.setSearchableInfo( - searchManager.getSearchableInfo(getComponentName())); - SearchView.OnQueryTextListener textChangeListener = new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextChange(String newText) { - // this is your adapter that will be filtered - listToView.getFilter().filter(newText); - return true; - } - - @Override - public boolean onQueryTextSubmit(String query) { - // this is your adapter that will be filtered - listToView.getFilter().filter(query); - return true; - } - }; - searchView.setOnQueryTextListener(textChangeListener); - - return true; + + private ProgressDialog ShowProgress(int detailId) { + return ProgressDialog.show(this, getString(R.string.app_name), + getString(detailId), true); } - - public boolean onOptionsItemSelected (MenuItem item){ - switch (item.getItemId()){ - case R.id.login: - Intent res = new Intent(); - String mPackage = "com.Pau.ImapNotes2"; - String mClass = ".AccontConfigurationActivity"; - res.setComponent(new ComponentName(mPackage,mPackage+mClass)); - res.putExtra("action", "CREATE_ACCOUNT"); - startActivity(res); - return true; - case R.id.refresh: - this.TriggerSync(this.status); - return true; - case R.id.newnote: - Intent toNew = new Intent(this, NewNoteActivity.class); - toNew.putExtra("usesSticky", Listactivity.imapNotes2Account.GetUsesticky()); - startActivityForResult(toNew,Listactivity.NEW_BUTTON); - return true; - case R.id.about: - try { - ComponentName comp = new ComponentName(this.getApplicationContext(), Listactivity.class); - PackageInfo pinfo = this.getApplicationContext().getPackageManager().getPackageInfo(comp.getPackageName(), 0); - String versionName = "Version: " + pinfo.versionName; - String versionCode = "Code: " + String.valueOf(pinfo.versionCode); - - new AlertDialog.Builder(this) - .setTitle("About ImapNotes2") - .setMessage(versionName + "\n" + versionCode) - .setPositiveButton("OK", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Do nothing - } - }) - .show(); - } catch (android.content.pm.PackageManager.NameNotFoundException e) { - Log.d("XXXXX","except"); + + public boolean onCreateOptionsMenu(@NonNull Menu menu) { + getMenuInflater().inflate(R.menu.list, menu); + + // Associate searchable configuration with the SearchView + SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + SearchView searchView = + (SearchView) menu.findItem(R.id.search).getActionView(); + searchView.setSearchableInfo( + searchManager.getSearchableInfo(getComponentName())); + SearchView.OnQueryTextListener textChangeListener = new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextChange(String newText) { + // this is your adapter that will be filtered + listToView.getFilter().filter(newText); + return true; + } + + @Override + public boolean onQueryTextSubmit(String query) { + // this is your adapter that will be filtered + listToView.getFilter().filter(query); + return true; } - return true; - default: - return super.onOptionsItemSelected(item); + }; + searchView.setOnQueryTextListener(textChangeListener); + + return true; } + + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.login: + Intent res = new Intent(); + String mPackage = "com.Pau.ImapNotes2"; + String mClass = ".AccountConfigurationActivity"; + res.setComponent(new ComponentName(mPackage, mPackage + mClass)); + res.putExtra(ACTION, AccountConfigurationActivity.Actions.CREATE_ACCOUNT); + res.putExtra(ACCOUNTNAME, Listactivity.imapNotes2Account.accountName); + startActivity(res); + return true; + case R.id.refresh: + //TextView status = (TextView) findViewById(R.id.status); + TriggerSync(status); + return true; + case R.id.newnote: + Intent toNew = new Intent(this, NewNoteActivity.class); + toNew.putExtra(NewNoteActivity.usesSticky, Listactivity.imapNotes2Account.usesticky); + startActivityForResult(toNew, Listactivity.NEW_BUTTON); + return true; + case R.id.about: + try { + ComponentName comp = new ComponentName(this.getApplicationContext(), Listactivity.class); + PackageInfo pinfo = this.getApplicationContext().getPackageManager().getPackageInfo(comp.getPackageName(), 0); + String versionName = "Version: " + pinfo.versionName; + String versionCode = "Code: " + String.valueOf(pinfo.versionCode); + + new AlertDialog.Builder(this) + .setTitle("About ImapNotes2") + .setMessage(versionName + "\n" + versionCode) + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Do nothing + } + }) + .show(); + } catch (android.content.pm.PackageManager.NameNotFoundException e) { + Log.d(TAG, "except"); + } + return true; + default: + return super.onOptionsItemSelected(item); + } } - - protected void onActivityResult(int requestCode, int resultCode, Intent data){ - switch(requestCode) { + + protected void onActivityResult(int requestCode, int resultCode, @NonNull Intent data) { + Log.d(TAG, "onActivityResult: " + requestCode + " " + resultCode); + switch (requestCode) { case Listactivity.SEE_DETAIL: - // Returning from NoteDetailActivity - if (resultCode == Listactivity.DELETE_BUTTON) { - // Delete Message asked for - // String suid will contain the Message Imap UID to delete - String suid = data.getStringExtra("DELETE_ITEM_NUM_IMAP"); - this.UpdateList(suid, null, null, "delete"); - } - if (resultCode == Listactivity.EDIT_BUTTON) { - String txt = data.getStringExtra("EDIT_ITEM_TXT"); - String suid = data.getStringExtra("EDIT_ITEM_NUM_IMAP"); - String color = data.getStringExtra("EDIT_ITEM_COLOR"); - //Log.d(TAG,"Received request to delete message:"+suid); - //Log.d(TAG,"Received request to replace message with:"+txt); - this.UpdateList(suid, txt, color, "update"); - } + // Returning from NoteDetailActivity + if (resultCode == Listactivity.DELETE_BUTTON) { + // Delete Message asked for + // String suid will contain the Message Imap UID to delete + String suid = data.getStringExtra(DELETE_ITEM_NUM_IMAP); + this.UpdateList(suid, null, null, UpdateThread.Action.Delete); + } + if (resultCode == Listactivity.EDIT_BUTTON) { + String txt = data.getStringExtra(EDIT_ITEM_TXT); + String suid = data.getStringExtra(EDIT_ITEM_NUM_IMAP); + Colors color = (Colors) data.getSerializableExtra(EDIT_ITEM_COLOR); + //Log.d(TAG,"Received request to edit message:"+suid); + //Log.d(TAG,"Received request to replace message with:"+txt); + this.UpdateList(suid, txt, color, UpdateThread.Action.Update); + //TextView status = (TextView) findViewById(R.id.status); + TriggerSync(status); + } case Listactivity.NEW_BUTTON: - // Returning from NewNoteActivity - if (resultCode == Listactivity.SAVE_BUTTON) { - String res = data.getStringExtra("SAVE_ITEM"); - //Log.d(TAG,"Received request to save message:"+res); - String color = data.getStringExtra("SAVE_ITEM_COLOR"); - this.UpdateList(null, res, color, "insert"); - } + // Returning from NewNoteActivity + if (resultCode == Listactivity.SAVE_BUTTON) { + String res = data.getStringExtra(SAVE_ITEM); + //Log.d(TAG,"Received request to save message:"+res); + Colors color = (Colors) data.getSerializableExtra(SAVE_ITEM_COLOR); + UpdateList(null, res, color, UpdateThread.Action.Insert); + } } } // Spinner item selected listener @Override public void onItemSelected(AdapterView parent, View view, int pos, long id) { - ((TextView) this.accountSpinner.getSelectedView()).setBackgroundColor(0xFFB6B6B6); + (this.accountSpinner.getSelectedView()).setBackgroundColor(0xFFB6B6B6); Account account = Listactivity.accounts[pos]; // Check periodic sync. If set to 86400 (once a day), set it to 900 (15 minutes) // this is due to bad upgrade to v4 which handles offline mode and syncing // Remove this code after V4.0 if version no more used - List currentSyncs = ContentResolver.getPeriodicSyncs (account, AUTHORITY); + List currentSyncs = ContentResolver.getPeriodicSyncs(account, AUTHORITY); for (PeriodicSync onesync : currentSyncs) { if (onesync.period == 86400) { ContentResolver.setIsSyncable(account, AUTHORITY, 1); @@ -350,86 +423,79 @@ public void onItemSelected(AdapterView parent, View view, int pos, long id) { Toast.LENGTH_LONG).show(); } } - // End of code - Listactivity.imapNotes2Account.SetAccountname(account.name); - Listactivity.imapNotes2Account.SetUsername(Listactivity.accountManager.getUserData (account, "username")); - String pwd = Listactivity.accountManager.getPassword(account); - Listactivity.imapNotes2Account.SetPassword(pwd); - Listactivity.imapNotes2Account.SetServer(Listactivity.accountManager.getUserData (account, "server")); - Listactivity.imapNotes2Account.SetPortnum(Listactivity.accountManager.getUserData (account, "portnum")); - Listactivity.imapNotes2Account.SetSecurity(Listactivity.accountManager.getUserData (account, "security")); - Listactivity.imapNotes2Account.SetUsesticky(accountManager.getUserData (account, "usesticky")); - Listactivity.imapNotes2Account.SetSyncinterval(Listactivity.accountManager.getUserData (account, "syncinterval")); - Listactivity.imapNotes2Account.SetaccountHasChanged(); - Listactivity.imapNotes2Account.SetAccount(account); + + Listactivity.imapNotes2Account = new ImapNotes2Account(account, getApplicationContext()); this.RefreshList(); } - + @Override public void onNothingSelected(AdapterView parent) { // TODO Auto-generated method stub - + } - private void updateAccountSpinner () { + private void updateAccountSpinner() { this.spinnerList.notifyDataSetChanged(); //this.accountSpinner.setSelection(spinnerList.getPosition(currentAccountname)); if (this.accountSpinner.getSelectedItemId() == android.widget.AdapterView.INVALID_ROW_ID) { - this.accountSpinner.setSelection(0); + this.accountSpinner.setSelection(0); } - + if (Listactivity.currentList.size() == 1) { Account account = Listactivity.accounts[0]; - Listactivity.imapNotes2Account.SetUsername(Listactivity.accountManager.getUserData (account, "username")); + Listactivity.imapNotes2Account = new ImapNotes2Account(account, getApplicationContext()); +/* imapNotes2Account.SetUsername(Listactivity.accountManager.getUserData(account, ConfigurationFieldNames.UserName)); String pwd = Listactivity.accountManager.getPassword(account); - Listactivity.imapNotes2Account.SetPassword(pwd); - Listactivity.imapNotes2Account.SetServer(Listactivity.accountManager.getUserData (account, "server")); - Listactivity.imapNotes2Account.SetPortnum(Listactivity.accountManager.getUserData (account, "portnum")); - Listactivity.imapNotes2Account.SetSecurity(Listactivity.accountManager.getUserData (account, "security")); - Listactivity.imapNotes2Account.SetUsesticky(accountManager.getUserData (account, "usesticky")); - Listactivity.imapNotes2Account.SetSyncinterval(Listactivity.accountManager.getUserData (account, "syncinterval")); - Listactivity.imapNotes2Account.SetaccountHasChanged(); + imapNotes2Account.SetPassword(pwd); + imapNotes2Account.SetServer(Listactivity.accountManager.getUserData(account, ConfigurationFieldNames.Server)); + imapNotes2Account.SetPortnum(Listactivity.accountManager.getUserData(account, ConfigurationFieldNames.PortNumber)); + imapNotes2Account.SetSecurity(Listactivity.accountManager.getUserData(account, ConfigurationFieldNames.Security)); + imapNotes2Account.SetUsesticky("true".equals(accountManager.getUserData(account, ConfigurationFieldNames.UseSticky))); + imapNotes2Account.SetSyncinterval(Listactivity.accountManager.getUserData(account, ConfigurationFieldNames.SyncInterval)); + //imapNotes2Account.SetaccountHasChanged(); + */ } } private class AccountsUpdateListener implements OnAccountsUpdateListener { private ArrayList newAccounts; - + @Override - public void onAccountsUpdated(Account[] accounts) { + public void onAccountsUpdated(@NonNull Account[] accounts) { List newList; - Integer newListSize = 0; + //Integer newListSize = 0; //invoked when the AccountManager starts up and whenever the account set changes - this.newAccounts = new ArrayList(); + this.newAccounts = new ArrayList<>(); for (final Account account : accounts) { if (account.type.equals("com.Pau.ImapNotes2")) { this.newAccounts.add(account); } } if (this.newAccounts.size() > 0) { - Account[] imapNotes2Accounts = new Account[this.newAccounts.size()] ; + Account[] imapNotes2Accounts = new Account[this.newAccounts.size()]; int i = 0; for (final Account account : this.newAccounts) { imapNotes2Accounts[i] = account; i++; } Listactivity.accounts = imapNotes2Accounts; - newList = new ArrayList(); - for (Account account: Listactivity.accounts ) { + newList = new ArrayList<>(); + for (Account account : Listactivity.accounts) { newList.add(account.name); } if (newList.size() == 0) return; - + Boolean equalLists = true; ListIterator iter = Listactivity.currentList.listIterator(); - while(iter.hasNext()){ + while (iter.hasNext()) { String s = iter.next(); if (!(newList.contains(s))) { iter.remove(); - String stringDir = (ImapNotes2.getAppContext()).getFilesDir() + "/" + s; + // Why try here? try { - FileUtils.deleteDirectory(new File (stringDir)); + String stringDir = ImapNotes2k.ConfigurationDirPath(getApplicationContext()) + "/" + s; + FileUtils.deleteDirectory(new File(stringDir)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -437,18 +503,18 @@ public void onAccountsUpdated(Account[] accounts) { equalLists = false; } } - for (String accountName: newList ) { + for (String accountName : newList) { if (!(Listactivity.currentList.contains(accountName))) { Listactivity.currentList.add(accountName); - SyncUtils.CreateDirs (accountName, ImapNotes2.getAppContext()); - + SyncUtils.CreateLocalDirectories(accountName, getApplicationContext()); + equalLists = false; } } if (equalLists) return; updateAccountSpinner(); } else { - File filesDir = (ImapNotes2.getAppContext()).getFilesDir(); + File filesDir = ImapNotes2k.ConfigurationDir(getApplicationContext()); try { FileUtils.cleanDirectory(filesDir); } catch (IOException e) { @@ -457,48 +523,50 @@ public void onAccountsUpdated(Account[] accounts) { } Intent res = new Intent(); String mPackage = "com.Pau.ImapNotes2"; - String mClass = ".AccontConfigurationActivity"; - res.setComponent(new ComponentName(mPackage,mPackage+mClass)); + String mClass = ".AccountConfigurationActivity"; + res.setComponent(new ComponentName(mPackage, mPackage + mClass)); startActivity(res); } } } - // In case of neccessary debug with user approval - // This function will be called from onDestroy - public void SendLogcatMail(){ - String emailData=""; - try { - Process process = Runtime.getRuntime().exec("logcat -d"); - BufferedReader bufferedReader = new BufferedReader( - new InputStreamReader(process.getInputStream())); - - StringBuilder sb=new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - sb.append(line + "\n"); - } - emailData=sb.toString(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - //send file using email - Intent emailIntent = new Intent(Intent.ACTION_SEND); - String to[] = {"nb@dagami.org"}; - emailIntent .putExtra(Intent.EXTRA_EMAIL, to); - // the attachment - emailIntent .putExtra(Intent.EXTRA_TEXT, emailData); - // the mail subject - emailIntent .putExtra(Intent.EXTRA_SUBJECT, "Logcat content for ImapNotes2 debugging"); - emailIntent.setType("message/rfc822"); - startActivity(Intent.createChooser(emailIntent , "Send email...")); - } - - public static void TriggerSync(TextView statusField) { - OldStatus=statusField.getText().toString(); - statusField.setText("Syncing..."); +// --Commented out by Inspection START (12/2/16 8:49 PM): +// // In case of neccessary debug with user approval +// // This function will be called from onDestroy +// public void SendLogcatMail() { +// String emailData = ""; +// try { +// Process process = Runtime.getRuntime().exec("logcat -d"); +// BufferedReader bufferedReader = new BufferedReader( +// new InputStreamReader(process.getInputStream())); +// +// StringBuilder sb = new StringBuilder(); +// String line; +// while ((line = bufferedReader.readLine()) != null) { +// sb.append(line).append("\n"); +// } +// emailData = sb.toString(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// +// //send file using email +// Intent emailIntent = new Intent(Intent.ACTION_SEND); +// String to[] = {"nb@dagami.org"}; +// emailIntent.putExtra(Intent.EXTRA_EMAIL, to); +// // the attachment +// emailIntent.putExtra(Intent.EXTRA_TEXT, emailData); +// // the mail subject +// emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Logcat content for ImapNotes2 debugging"); +// emailIntent.setType("message/rfc822"); +// startActivity(Intent.createChooser(emailIntent, "Send email...")); +// } +// --Commented out by Inspection STOP (12/2/16 8:49 PM) + + private static void TriggerSync(@NonNull TextView statusField) { + OldStatus = statusField.getText().toString(); + statusField.setText(R.string.syncing); Account mAccount = Listactivity.imapNotes2Account.GetAccount(); Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean( @@ -509,9 +577,10 @@ public static void TriggerSync(TextView statusField) { ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle); } - @Override - public Filter getFilter() { - return null; - } + @Nullable + @Override + public Filter getFilter() { + return null; + } } - + diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Merge/M.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Merge/M.java new file mode 100644 index 00000000..7e0b2289 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Merge/M.java @@ -0,0 +1,76 @@ +package com.Pau.ImapNotes2.Merge; + +class M { + + + /* // takes 2 indexable objects (e.g. strings or lists) +// returns a list of Change objects (Delete or Insert) +// guaranteed to produce an optimal diff + List str_diff(String a, + String b) { + ls = len(a); + lf = len(b); + memo = emptyDictionaryOfWhat; + + WhatType min_diff ( int si, + int fi){ + if (si,fi)in memo: + return memo[(si,fi)] + ans =[] + if si == ls and fi==lf: + ans =[] + elif si 0: + pos_a = pos_a_old + range_b_0 = pos_a_old + offset_b + range_b_1 = pos_a_old + offset_b + length + changes.append(Insert(b[range_b_0:range_b_1],pos_a, (range_b_0, range_b_1))) + offset_b += length + if pos_diff >= len(diff): + break + length=0 + pos_a_old = diff[pos_diff][0] + while pos_diff 0: + range_a_0 = pos_a_old + range_a_1 = pos_a_old + length + pos_b = pos_a_old + offset_b + changes.append(Delete(a[range_a_0:range_a_1],(range_a_0, range_a_1),pos_b)) + offset_b -= length + } + + return changes + } +*/ +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotes2Result.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotes2Result.java index 8a3b7856..45fff870 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotes2Result.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotes2Result.java @@ -1,20 +1,38 @@ package com.Pau.ImapNotes2.Miscs; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + import javax.mail.Folder; public class ImapNotes2Result { - public int returnCode; - public String errorMessage; - public Long UIDValidity; - public boolean hasUIDPLUS; - public Folder notesFolder; - - public ImapNotes2Result () { - returnCode = -1; - errorMessage = ""; - UIDValidity = (long) -1; - hasUIDPLUS = true; - notesFolder = null; - } + public final int returnCode; + @NonNull + public final String errorMessage; + public final Long UIDValidity; + // --Commented out by Inspection (11/26/16 11:45 PM):public boolean hasUIDPLUS; + @Nullable + public final Folder notesFolder; + + public ImapNotes2Result(int returnCode, + String errorMessage, + long UIDValidity, + Folder notesFolder) { + this.returnCode = returnCode; + this.errorMessage = errorMessage ; + this.UIDValidity = UIDValidity ; + this.notesFolder = notesFolder; + } + +/* + public ImapNotes2Result() { + returnCode = -1; + errorMessage = ""; + UIDValidity = (long) -1; + //hasUIDPLUS = true; + notesFolder = null; + } +*/ + } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotesAuthenticatorService.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotesAuthenticatorService.java deleted file mode 100644 index 6e71bd62..00000000 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/ImapNotesAuthenticatorService.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.Pau.ImapNotes2.Miscs; - -import com.Pau.ImapNotes2.AccontConfigurationActivity; -import com.Pau.ImapNotes2.Sync.SyncUtils; - -import android.accounts.AbstractAccountAuthenticator; -import android.accounts.Account; -import android.accounts.AccountAuthenticatorResponse; -import android.accounts.AccountManager; -import android.accounts.NetworkErrorException; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; - -public class ImapNotesAuthenticatorService extends Service{ - - private static final String TAG = "ImapNotesAuthenticationService"; - private Authenticator imapNotesAuthenticator; - - @Override - public void onCreate() { - this.imapNotesAuthenticator = new Authenticator(this); - } - - public IBinder onBind(Intent intent) { - IBinder ret = null; - if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) - ret = getAuthenticator().getIBinder(); - - return ret; - } - - private Authenticator getAuthenticator() { - if (this.imapNotesAuthenticator == null) - this.imapNotesAuthenticator = new Authenticator(this); - - return this.imapNotesAuthenticator; - } - - private static class Authenticator extends AbstractAccountAuthenticator { - - private Context mContext; - - public Authenticator(Context context) { - super(context); - this.mContext = context; - } - - @Override - public Bundle getAccountRemovalAllowed( - AccountAuthenticatorResponse response, Account account) - throws NetworkErrorException { - Bundle ret = super.getAccountRemovalAllowed(response, account); - if (ret.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) - SyncUtils.RemoveAccount(this.mContext, account); -/* - mContext.getContentResolver().delete(ListProvider.getClearUri(), - null, null); -*/ - return ret; - } - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { - - Intent toLoginActivity = new Intent(this.mContext, AccontConfigurationActivity.class); - toLoginActivity.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, toLoginActivity); - - return bundle; - } - - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { - - return null; - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { - - return null; - } - - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - - return null; - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - - return null; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { - - return null; - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - - return null; - } - - } - -} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Imaper.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Imaper.java index d6f89bfe..4825cf64 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Imaper.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Imaper.java @@ -1,126 +1,85 @@ package com.Pau.ImapNotes2.Miscs; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; +import android.support.annotation.NonNull; +import android.util.Log; + +import com.Pau.ImapNotes2.Data.Security; +import com.sun.mail.util.MailSSLSocketFactory; + import java.security.GeneralSecurityException; -import java.util.ArrayList; import java.util.Properties; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import javax.mail.Folder; -import javax.mail.Message; + import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Store; -import javax.mail.Flags; -import com.sun.mail.imap.IMAPFolder; -import com.sun.mail.imap.IMAPStore; -import com.sun.mail.util.MailSSLSocketFactory; -import java.util.regex.*; - -import com.Pau.ImapNotes2.Miscs.Sticky; -import com.Pau.ImapNotes2.ImapNotes2; -import com.Pau.ImapNotes2.Listactivity; -import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; public class Imaper { - - private Store store; - private Session session; - private static final String TAG = "IN_Imaper"; - private String proto; - private String acceptcrt; - private static String sfolder = "Notes"; - private String folderoverride; - private Folder notesFolder = null; - private ImapNotes2Result res; - private Long UIDValidity; -private Boolean useProxy = false; -public static final String PREFS_NAME = "PrefsFile"; - - public ImapNotes2Result ConnectToProvider(String username, String password, String server, String portnum, String security, String usesticky, String override) throws MessagingException{ - if (this.IsConnected()) - this.store.close(); - - res = new ImapNotes2Result(); - if (override==null) { - this.folderoverride = ""; - } else { - this.folderoverride = override; - } - this.proto = ""; - this.acceptcrt = ""; - int security_i = Integer.parseInt(security); - switch (security_i) { - case 0: - // None - this.proto = "imap"; - this.acceptcrt = ""; - break; - case 1: - // SSL/TLS - this.proto = "imaps"; - this.acceptcrt = "false"; - break; - case 2: - // SSL/TLS/TRUST ALL - this.proto = "imaps"; - this.acceptcrt = "true"; - break; - case 3: - // STARTTLS - this.proto = "imap"; - this.acceptcrt = "false"; - break; - case 4: - // STARTTLS/TRUST ALL - this.proto = "imap"; - this.acceptcrt = "true"; - break; -////////////////////// Change this - default: this.proto = "Invalid proto"; - break; - } - MailSSLSocketFactory sf = null; - try { - sf = new MailSSLSocketFactory(); - } catch (GeneralSecurityException e) { - e.printStackTrace(); - this.res.errorMessage = "Can't connect to server"; - this.res.returnCode = -1; - return this.res; - } - Properties props = new Properties(); - - props.setProperty(String.format("mail.%s.host", this.proto), server); - props.setProperty(String.format("mail.%s.port", this.proto), portnum); - props.setProperty("mail.store.protocol", this.proto); - - if ((this.acceptcrt.equals("true"))) { - sf.setTrustedHosts(new String[] {server}); - if (this.proto.equals("imap")) { - props.put("mail.imap.ssl.socketFactory", sf); - props.put("mail.imap.starttls.enable", "true"); - } - } else if (this.acceptcrt.equals("false")) { - props.put(String.format("mail.%s.ssl.checkserveridentity", this.proto), "true"); - if (this.proto.equals("imap")) { - props.put("mail.imap.starttls.enable", "true"); - } - } + private Store store; + private static final String TAG = "IN_Imaper"; - if (this.proto.equals("imaps")) { - props.put("mail.imaps.socketFactory", sf); - } + // --Commented out by Inspection (11/26/16 11:43 PM):public static final String PREFS_NAME = "PrefsFile"; + + public static final int ResultCodeSuccess = 0; + public static final int ResultCodeException = -2; + public static final int ResultCodeCantConnect = -1; - props.setProperty("mail.imap.connectiontimeout","1000"); - if (this.useProxy) { - props.put("mail.imap.socks.host","10.0.2.2"); - props.put("mail.imap.socks.port","1080"); + + + @NonNull + public ImapNotes2Result ConnectToProvider(String username, + String password, + String server, + String portnum, + @NonNull Security security) throws MessagingException { + if (IsConnected()) { + store.close(); + } + + MailSSLSocketFactory sf; + try { + sf = new MailSSLSocketFactory(); + } catch (GeneralSecurityException e) { + e.printStackTrace(); + return new ImapNotes2Result(-1, + "Can't connect to server", + -1, + null); + } + + String proto = security.proto; + + Properties props = new Properties(); + + props.setProperty(String.format("mail.%s.host", proto), server); + props.setProperty(String.format("mail.%s.port", proto), portnum); + props.setProperty("mail.store.protocol", proto); + + if (security.acceptcrt) { + sf.setTrustedHosts(new String[]{server}); + if (proto.equals("imap")) { + props.put("mail.imap.ssl.socketFactory", sf); + props.put("mail.imap.starttls.enable", "true"); + } + } else if (security != Security.None) { + props.put(String.format("mail.%s.ssl.checkserveridentity", proto), "true"); + if (proto.equals("imap")) { + props.put("mail.imap.starttls.enable", "true"); + } + } + + if (proto.equals("imaps")) { + props.put("mail.imaps.socketFactory", sf); + } + + props.setProperty("mail.imap.connectiontimeout", "1000"); + Boolean useProxy = false; + //noinspection ConstantConditions + // TODO: implement proxy handling properly. + //noinspection ConstantConditions,ConstantConditions + if (useProxy) { + props.put("mail.imap.socks.host", "10.0.2.2"); + props.put("mail.imap.socks.port", "1080"); /* props.put("proxySet","true"); props.put("socksProxyHost","10.0.2.2"); @@ -128,58 +87,29 @@ public ImapNotes2Result ConnectToProvider(String username, String password, Stri props.put("sun.net.spi.nameservice.provider.1", "dns,sun"); props.put("sun.net.spi.nameservice.nameservers", "192.168.0.99"); */ - } - this.session = Session.getInstance(props, null); -//this.session.setDebug(true); - this.store = this.session.getStore(this.proto); - try { - this.store.connect(server, username, password); -Boolean hasUIDPLUS = ((IMAPStore) this.store).hasCapability("UIDPLUS"); -//Log.d(TAG, "has UIDPLUS="+hasUIDPLUS); - - Folder[] folders = store.getPersonalNamespaces(); - Folder folder = folders[0]; -//Log.d(TAG,"Personal Namespaces="+folder.getFullName()); - if (this.folderoverride.length() > 0) { - Imaper.sfolder = this.folderoverride; - } else if (folder.getFullName().length() == 0) { - Imaper.sfolder = "Notes"; - } else { - char separator = folder.getSeparator(); - Imaper.sfolder = folder.getFullName() + separator + "Notes"; - } - this.res.errorMessage = ""; - this.res.returnCode = 0; - return this.res; - } catch (Exception e) { - e.printStackTrace(); - Log.d(TAG, e.getMessage()); - this.res.errorMessage = e.getMessage(); - this.res.returnCode = -2; - return this.res; + } + Session session = Session.getInstance(props, null); +//session.setDebug(true); + store = session.getStore(proto); + try { + store.connect(server, username, password); + + return new ImapNotes2Result(ResultCodeSuccess, + "", + -1, + null); + } catch (Exception e) { + e.printStackTrace(); + Log.d(TAG, e.getMessage()); + return new ImapNotes2Result(ResultCodeException, + e.getMessage(), + -1, + null); + } } - } - - public boolean IsConnected(){ - return this.store!=null && this.store.isConnected(); - } - - // Put values in shared preferences: - public void SetPrefs() { - SharedPreferences preferences = ImapNotes2.getAppContext().getSharedPreferences(Listactivity.imapNotes2Account.GetAccountname(), Context.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString("Name","valid_data"); - editor.putLong("UIDValidity", this.UIDValidity); - editor.apply(); - } - - // Retrieve values from shared preferences: - public void GetPrefs() { - SharedPreferences preferences = (ImapNotes2.getAppContext()).getSharedPreferences(Listactivity.imapNotes2Account.GetAccountname(), Context.MODE_PRIVATE); - String name = preferences.getString("Name", ""); - if(!name.equalsIgnoreCase("")) { - this.UIDValidity = preferences.getLong("UIDValidity", -1); + private boolean IsConnected() { + return store != null && store.isConnected(); } - } + } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/OneNote.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/OneNote.java deleted file mode 100644 index 144fd431..00000000 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/OneNote.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.Pau.ImapNotes2.Miscs; - -import java.util.HashMap; - -public class OneNote extends HashMap{ - - /** - * - */ - private static final long serialVersionUID = 1L; - - public OneNote(){ - super(); - this.put("title", "No Title"); - this.put("date", "No Date"); - this.put("uid", "0"); - - } - - public OneNote(String title, String date, String uid){ - super(); - this.put("title", title); - this.put("date", date); - this.put("uid", uid); - - } - - public String GetTitle(){ - return this.get("title"); - } - - public String GetDate(){ - return this.get("date"); - } - - public String GetUid(){ - return this.get("uid"); - } - - public void SetTitle(String title){ - this.put("title", title); - } - - public void SetDate(String date){ - this.put("date", date); - } - - public void SetUid(String uid){ - this.put("uid", uid); - } - - @Override - public String toString() { - return ("Title:"+this.GetTitle()+ - " Date: "+ this.GetDate() + - " Uid: "+ this.GetUid()); - } -} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Result.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Result.java new file mode 100644 index 00000000..b2232de6 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Result.java @@ -0,0 +1,24 @@ +package com.Pau.ImapNotes2.Miscs; + +/** + * Created by kj on 2017-02-15 10:12. + * Simple class to allow functions to return a value and a status as a single object. + * @param the type parameter + */ +public class Result { + /** + * The result can be any type, usually a String or number. + */ + public final T result; + /** + * True if the result is valid, else false. + */ + public final boolean succeeded; + + + public Result(T result, + boolean succeeded){ + this.result = result; + this.succeeded = succeeded; + } +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Sticky.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Sticky.java index 346ad5f4..5c0f986b 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Sticky.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/Sticky.java @@ -1,44 +1,44 @@ package com.Pau.ImapNotes2.Miscs; +import android.support.annotation.NonNull; + +import static com.Pau.ImapNotes2.NoteDetailActivity.Colors; + public class Sticky { - private static String text; - private static String position; - private static String color; - - public Sticky() { - Sticky.text = ""; - Sticky.position = "0 0 0 0"; - Sticky.color = "YELLOW"; - } - - public Sticky(String text, String position, String color) { - Sticky.text = text; - Sticky.position = position; - Sticky.color = color; - } - - public String GetPosition(){ - return Sticky.position; - } - - public String GetText(){ - return Sticky.text; - } - - public String GetColor(){ - return Sticky.color; - } - - public void SetText(String text){ - Sticky.text = text; - } - - public void SetPosition(String position){ - Sticky.position = position; - } - - public void SetColor(String color){ - Sticky.color = color; - } + public final String text; + // --Commented out by Inspection (11/26/16 11:50 PM):private final String position; + @NonNull + public final Colors color; + + public Sticky(String text, + @NonNull Colors color) { + this.text = text; + // this.position = position; + this.color = color; + } +/* + public String GetPosition() { + return Sticky.position; + } + + public String GetText() { + return Sticky.text; + } + + public Colors GetColor() { + return Sticky.color; + } + + public void SetText(String text) { + Sticky.text = text; + } + + public void SetPosition(String position) { + Sticky.position = position; + } + + public void SetColor(Colors color) { + Sticky.color = color; + }*/ } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/SyncThread.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/SyncThread.java index c1096fbc..5f9616ff 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/SyncThread.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/SyncThread.java @@ -1,58 +1,88 @@ package com.Pau.ImapNotes2.Miscs; -import java.util.ArrayList; +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.Pau.ImapNotes2.Data.Db; +import com.Pau.ImapNotes2.Data.OneNote; import com.Pau.ImapNotes2.Listactivity; import com.Pau.ImapNotes2.NotesListAdapter; -import com.Pau.ImapNotes2.Data.ImapNotes2Account; -import com.Pau.ImapNotes2.Data.NotesDb; -import android.app.ProgressDialog; -import android.content.Context; -import android.os.AsyncTask; -import android.util.Log; -import android.widget.SimpleAdapter; -import android.widget.Toast; +import java.util.ArrayList; + +//import com.Pau.ImapNotes2.Data.NotesDb; public class SyncThread extends AsyncTask { - NotesListAdapter adapter; - ArrayList notesList; - NotesDb storedNotes; - boolean bool_to_return; - ImapNotes2Result res = new ImapNotes2Result(); - Context ctx; + private final ProgressDialog progressDialog; + private final NotesListAdapter adapter; + private final ArrayList notesList; + /** + * SQLite database that holds status information about the notes. + */ + // TODO: NoteDb should probably never be null. + @NonNull + private final Db storedNotes; + // --Commented out by Inspection (11/26/16 11:48 PM):boolean bool_to_return; +// --Commented out by Inspection START (11/26/16 11:48 PM): +// @NonNull +// ImapNotes2Result res = new ImapNotes2Result(); +// --Commented out by Inspection STOP (11/26/16 11:48 PM) + @SuppressWarnings("unused") private static final String TAG = "SyncThread"; - + + // TODO: remove unused arguments. + public SyncThread(ArrayList noteList, + NotesListAdapter listToView, + ProgressDialog loadingDialog, + @Nullable Db storedNotes, + Context applicationContext) { + //this.imapFolder = imapFolder; + //this.imapNotes2Account = imapNotes2Account; + this.notesList = noteList; + this.adapter = listToView; + this.progressDialog = loadingDialog; + //this.storedNotes = storedNotes; + this.storedNotes = (storedNotes == null) ? new Db(applicationContext) : storedNotes; + + } + + // Do not pass arguments via execute; the object is never reused so it is quite safe to pass + // the arguments in the constructor. + @NonNull @Override protected Boolean doInBackground(Object... stuffs) { - String username = null; + /*String username = null; String password = null; String server = null; String portnum = null; String security = null; String usesticky = null; - this.adapter = ((NotesListAdapter)stuffs[3]); - this.notesList = ((ArrayList)stuffs[2]); - this.storedNotes = ((NotesDb)stuffs[5]); - this.ctx = (Context)stuffs[6]; - username = ((ImapNotes2Account)stuffs[1]).GetUsername(); - password = ((ImapNotes2Account)stuffs[1]).GetPassword(); - server = ((ImapNotes2Account)stuffs[1]).GetServer(); - portnum = ((ImapNotes2Account)stuffs[1]).GetPortnum(); - security = ((ImapNotes2Account)stuffs[1]).GetSecurity(); - usesticky = ((ImapNotes2Account)stuffs[1]).GetUsesticky(); - - - if (this.storedNotes == null) this.storedNotes = new NotesDb(this.ctx); - this.storedNotes.OpenDb(); - this.storedNotes.GetStoredNotes(this.notesList, Listactivity.imapNotes2Account.GetAccountname()); - this.storedNotes.CloseDb(); - ((ProgressDialog)stuffs[4]).dismiss(); +*/ + /* this.adapter = ((NotesListAdapter) stuffs[3]); + this.notesList = ((ArrayList) stuffs[2]); + this.storedNotes = ((NotesDb) stuffs[5]); + this.ctx = (Context) stuffs[6]; + */ + //username = ((ImapNotes2Account) stuffs[1]).GetUsername(); + //password = ((ImapNotes2Account) stuffs[1]).GetPassword(); + //server = ((ImapNotes2Account) stuffs[1]).GetServer(); + //portnum = ((ImapNotes2Account) stuffs[1]).GetPortnum(); + //security = ((ImapNotes2Account) stuffs[1]).GetSecurity(); + //usesticky = ((ImapNotes2Account) stuffs[1]).GetUsesticky(); + + + storedNotes.OpenDb(); + storedNotes.notes.GetStoredNotes(this.notesList, Listactivity.imapNotes2Account.accountName); + storedNotes.CloseDb(); + progressDialog.dismiss(); return true; } - - protected void onPostExecute(Boolean result){ - if(result){ + + protected void onPostExecute(Boolean result) { + if (result) { this.adapter.notifyDataSetChanged(); } } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/UpdateThread.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/UpdateThread.java index 91c632f7..29d3c735 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/UpdateThread.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Miscs/UpdateThread.java @@ -1,5 +1,18 @@ package com.Pau.ImapNotes2.Miscs; +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.text.Html; +import android.util.Log; + +import com.Pau.ImapNotes2.Data.Db; +import com.Pau.ImapNotes2.Data.ImapNotes2Account; +import com.Pau.ImapNotes2.Data.OneNote; +import com.Pau.ImapNotes2.Listactivity; +import com.Pau.ImapNotes2.NotesListAdapter; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -8,191 +21,264 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; +import java.util.Locale; import java.util.Properties; import java.util.UUID; import javax.mail.Flags; +import javax.mail.Message; import javax.mail.MessagingException; +import javax.mail.Multipart; import javax.mail.Session; -import javax.mail.internet.MimeMessage; import javax.mail.internet.MailDateFormat; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; -import com.Pau.ImapNotes2.ImapNotes2; -import com.Pau.ImapNotes2.Listactivity; -import com.Pau.ImapNotes2.NotesListAdapter; -import com.Pau.ImapNotes2.Data.ImapNotes2Account; -import com.Pau.ImapNotes2.Data.NotesDb; +import static com.Pau.ImapNotes2.NoteDetailActivity.Colors; + +//import com.Pau.ImapNotes2.Data.NotesDb; + +// TODO: move arguments from execute to constructor. +public class UpdateThread extends AsyncTask { + private final ImapNotes2Account imapNotes2Account; + private final ProgressDialog progressDialog; + private final NotesListAdapter adapter; + private final ArrayList notesList; + private String suid; + private final String noteBody; + private final Colors color; + private boolean bool_to_return; + private Db storedNotes; + private final Context applicationContext; + private final Action action; + private static final String TAG = "IN_UpdateThread"; + + public enum Action { + Update, + Insert, + Delete + } + + /* + Assign all fields in the constructor because we never reuse this object. This makes the code + typesafe. Make them final to prevent accidental reuse. + */ + public UpdateThread(ImapNotes2Account imapNotes2Account, + ArrayList noteList, + NotesListAdapter listToView, + ProgressDialog loadingDialog, + String suid, + String noteBody, + Colors color, + Context applicationContext, + Action action, + Db storedNotes) { + Log.d(TAG, "UpdateThread: " + noteBody); + this.imapNotes2Account = imapNotes2Account; + this.notesList = noteList; + this.adapter = listToView; + this.progressDialog = loadingDialog; + this.suid = suid; + this.noteBody = noteBody; + this.color = color; + this.applicationContext = applicationContext; + this.action = action; + this.storedNotes = storedNotes; + + } -import android.app.ProgressDialog; -import android.content.Context; -import android.os.AsyncTask; -import android.text.Html; -import android.util.Log; -import android.widget.SimpleAdapter; - -public class UpdateThread extends AsyncTask{ - NotesListAdapter adapter; - ArrayList notesList; - String suid; - String noteBody; - String color; - Imaper imapFolder; - boolean bool_to_return; - OneNote currentNote = null; - NotesDb storedNotes; - Context ctx; - ProgressDialog pDialog; - String body = null; - String action; - private static final String TAG = "UpdateThread"; - @Override protected Boolean doInBackground(Object... stuffs) { - this.adapter = ((NotesListAdapter)stuffs[3]); - this.notesList = ((ArrayList)stuffs[2]); - this.suid = ((String)stuffs[5]); - this.noteBody = ((String)stuffs[6]); - this.color = ((String)stuffs[7]); - this.imapFolder = ((Imaper)stuffs[0]); - this.ctx = (Context)stuffs[8]; - this.action = (String)stuffs[9]; - this.storedNotes = (NotesDb)stuffs[10]; try { // Do we have a note to remove? - if (this.action.equals("delete") || this.action.equals("update")) { + if ((action == Action.Delete) || (action == Action.Update)) { //Log.d(TAG,"Received request to delete message #"+suid); // Here we delete the note from the local notes list //Log.d(TAG,"Delete note in Listview"); - this.notesList.remove(getIndexByNumber(this.suid)); - MoveMailToDeleted(this.suid); - this.storedNotes.OpenDb(); - this.storedNotes.DeleteANote(this.suid, Listactivity.imapNotes2Account.GetAccountname()); - this.storedNotes.CloseDb(); - this.bool_to_return = true; + notesList.remove(getIndexByNumber(suid)); + MoveMailToDeleted(suid); + storedNotes.OpenDb(); + storedNotes.notes.DeleteANote(suid, Listactivity.imapNotes2Account.accountName); + storedNotes.CloseDb(); + bool_to_return = true; } // Do we have a note to add? - if (this.action.equals("insert") || this.action.equals("update")) { + if ((action == Action.Insert) || (action == Action.Update)) { //Log.d(TAG,"Sticky ? "+((ImapNotes2Account)stuffs[1]).GetUsesticky()); -//Log.d(TAG,"Color:"+this.color); - //Log.d(TAG,"Received request to add new message"+this.noteBody+"==="); - String noteTxt = Html.fromHtml(this.noteBody).toString(); - String[] tok = noteTxt.split("\n", 2); +//Log.d(TAG,"Color:"+color); + Log.d(TAG, "Received request to add new message: " + noteBody + "==="); + //String noteTxt = Html.fromHtml(noteBody).toString(); + String noteTxt = noteBody; + Log.d(TAG, "noteTxt: " + noteTxt + "==="); + // Use the first line as the tile + String[] tok = Html.fromHtml(noteBody).toString().split("\n", 2); String title = tok[0]; - String position = "0 0 0 0"; - if (((ImapNotes2Account)stuffs[1]).GetUsesticky().equals("true")) - body = noteTxt.replaceAll("\n", "\\\\n"); - else - body = "" + this.noteBody + ""; - + //String position = "0 0 0 0"; + String body = (imapNotes2Account.usesticky) ? + noteTxt.replaceAll("\n", "\\\\n") : + "" + noteBody + ""; String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; Date date = new Date(); - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.ROOT); String stringDate = sdf.format(date); - this.currentNote = new OneNote(title, stringDate, ""); + OneNote currentNote = new OneNote(title, stringDate, ""); // Add note to database - if (this.storedNotes == null) this.storedNotes = new NotesDb(this.ctx); - this.storedNotes.OpenDb(); - this.suid = this.storedNotes.GetTempNumber(Listactivity.imapNotes2Account.GetAccountname()); - this.currentNote.SetUid(this.suid); - // Here we ask to add the new note to the "new" folder + if (storedNotes == null) storedNotes = new Db(applicationContext); + storedNotes.OpenDb(); + suid = storedNotes.notes.GetTempNumber(Listactivity.imapNotes2Account.accountName); + currentNote.SetUid(suid); + // Here we ask to add the new note to the new note folder // Must be done AFTER uid has been set in currenteNote - WriteMailToNew(currentNote, - ((ImapNotes2Account)stuffs[1]).GetUsesticky(), body); - this.storedNotes.InsertANoteInDb(this.currentNote, Listactivity.imapNotes2Account.GetAccountname()); - this.storedNotes.CloseDb(); + Log.d(TAG, "doInBackground body: " + body); + WriteMailToNew(currentNote, + imapNotes2Account.usesticky, + imapNotes2Account.usesAutomaticMerge, + body); + storedNotes.notes.InsertANoteInDb(currentNote, Listactivity.imapNotes2Account.accountName); + storedNotes.CloseDb(); // Add note to noteList but chage date format before - DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(this.ctx); + //DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(applicationContext); String sdate = DateFormat.getDateTimeInstance().format(date); - this.currentNote.SetDate(sdate); - this.notesList.add(0,this.currentNote); - this.bool_to_return = true; + currentNote.SetDate(sdate); + notesList.add(0, currentNote); + return true; } } catch (Exception e) { - e.printStackTrace(); - this.bool_to_return=false; + Log.d(TAG, "Action: " + action.toString()); + e.printStackTrace(); + return false; } finally { - ((ProgressDialog)stuffs[4]).dismiss(); + progressDialog.dismiss(); } - return this.bool_to_return; + return bool_to_return; } - - protected void onPostExecute(Boolean result){ + + protected void onPostExecute(Boolean result) { if (result) { - if (this.bool_to_return) /* note added or removed */ - this.adapter.notifyDataSetChanged(); + if (bool_to_return) /* note added or removed */ + adapter.notifyDataSetChanged(); } } - public int getIndexByNumber(String pNumber) { - for(OneNote _item : this.notesList) - { - if(_item.GetUid().equals(pNumber)) - return this.notesList.indexOf(_item); + private int getIndexByNumber(String pNumber) { + for (OneNote _item : notesList) { + if (_item.GetUid().equals(pNumber)) + return notesList.indexOf(_item); } return -1; } - private void MoveMailToDeleted (String suid) { - String directory = (ImapNotes2.getAppContext()).getFilesDir() + "/" + - Listactivity.imapNotes2Account.GetAccountname(); - String positiveUid = suid.substring(1); - File from = new File (directory, suid); - File to = new File (directory + "/deleted/" + suid); + /** + * @param suid IMAP ID of the note. + */ + private void MoveMailToDeleted(@NonNull String suid) { + String directory = applicationContext.getFilesDir() + "/" + + Listactivity.imapNotes2Account.accountName; + // TODO: Explain why we need to omit the first character of the UID + File from = new File(directory, suid); if (!from.exists()) { - from = new File (directory + "/new", positiveUid); + String positiveUid = suid.substring(1); + from = new File(directory + "/new", positiveUid); + // TODO: Explain why it is safe to ignore the result of delete. + //noinspection ResultOfMethodCallIgnored from.delete(); } else { + File to = new File(directory + "/deleted/" + suid); + // TODO: Explain why it is safe to ignore the result of rename. + //noinspection ResultOfMethodCallIgnored from.renameTo(to); } } - public void WriteMailToNew(OneNote note, String usesticky, String noteBody) throws MessagingException, IOException { - String body = null; - - // Here we add the new note to the "new" folder - //Log.d(TAG,"Add new note"); - Properties props = new Properties(); - Session session = Session.getDefaultInstance(props, null); - MimeMessage message = new MimeMessage(session); - if (usesticky.equals("true")) { - body = "BEGIN:STICKYNOTE\nCOLOR:" + this.color + "\nTEXT:" + noteBody + - "\nPOSITION:0 0 0 0\nEND:STICKYNOTE"; - message.setText(body); - message.setHeader("Content-Transfer-Encoding", "8bit"); - message.setHeader("Content-Type","text/x-stickynote; charset=\"utf-8\""); - } else { - message.setHeader("X-Uniform-Type-Identifier","com.apple.mail-note"); - UUID uuid = UUID.randomUUID(); - message.setHeader("X-Universally-Unique-Identifier", uuid.toString()); - body = noteBody; - body = body.replaceFirst("

", "

"); - body = body.replaceFirst("

", "

"); - body = body.replaceAll("

", "


"); - body = body.replaceAll("

", "


"); - body = body.replaceAll("

", "
"); - body = body.replaceAll("
\n", "
"); - message.setText(body, "utf-8", "html"); - message.setFlag(Flags.Flag.SEEN,true); + @NonNull + private Message MakeMessageWithAttachment(String subject, + String message, + String filePath, + Session session) + throws IOException, MessagingException { + + Message msg = new MimeMessage(session); + + + msg.setSubject(subject); + msg.setSentDate(new Date()); + + // creates message part + MimeBodyPart messageBodyPart = new MimeBodyPart(); + messageBodyPart.setContent(message, "text/html"); + + // creates multi-part + Multipart multipart = new MimeMultipart(); + multipart.addBodyPart(messageBodyPart); + + // add attachment + + MimeBodyPart attachPart = new MimeBodyPart(); + + attachPart.attachFile(filePath); + + + multipart.addBodyPart(attachPart); + + // sets the multi-part as e-mail's content + msg.setContent(multipart); + return msg; + } + + private void WriteMailToNew(@NonNull OneNote note, + boolean usesticky, + boolean usesAutomaticMerge, + String noteBody) throws MessagingException, IOException { + Log.d(TAG, "WriteMailToNew: " + noteBody); + //String body = null; + + // Here we add the new note to the new note folder + //Log.d(TAG,"Add new note"); + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + MimeMessage message = new MimeMessage(session); + + if (usesticky) { + String body = "BEGIN:STICKYNOTE\nCOLOR:" + color.name() + "\nTEXT:" + noteBody + + "\nPOSITION:0 0 0 0\nEND:STICKYNOTE"; + message.setText(body); + message.setHeader("Content-Transfer-Encoding", "8bit"); + message.setHeader("Content-Type", "text/x-stickynote; charset=\"utf-8\""); + } else { + message.setHeader("X-Uniform-Type-Identifier", "com.apple.mail-note"); + UUID uuid = UUID.randomUUID(); + message.setHeader("X-Universally-Unique-Identifier", uuid.toString()); + String body = noteBody; + body = body.replaceFirst("

", "

"); + body = body.replaceFirst("

", "

"); + body = body.replaceAll("

", "


"); + body = body.replaceAll("

", "


"); + body = body.replaceAll("

", "
"); + body = body.replaceAll("
\n", "
"); + message.setText(body, "utf-8", "html"); + message.setFlag(Flags.Flag.SEEN, true); + } + message.setSubject(note.GetTitle()); + MailDateFormat mailDateFormat = new MailDateFormat(); + // Remove (CET) or (GMT+1) part as asked in github issue #13 + String headerDate = (mailDateFormat.format(new Date())).replaceAll("\\(.*$", ""); + message.addHeader("Date", headerDate); + // Get temporary UID + String uid = Integer.toString(Math.abs(Integer.parseInt(note.GetUid()))); + File accountDirectory = new File(applicationContext.getFilesDir(), + Listactivity.imapNotes2Account.accountName); + File directory = new File(accountDirectory, "new"); + //message.setFrom(new InternetAddress("ImapNotes2", Listactivity.imapNotes2Account.accountName)); + message.setFrom(Listactivity.imapNotes2Account.accountName); + File outfile = new File(directory, uid); + OutputStream str = new FileOutputStream(outfile); + message.writeTo(str); + } - message.setSubject(note.GetTitle()); - MailDateFormat mailDateFormat = new MailDateFormat(); - // Remove (CET) or (GMT+1) part as asked in github issue #13 - String headerDate = (mailDateFormat.format(new Date())).replaceAll("\\(.*$", ""); - message.addHeader("Date", headerDate); - //déterminer l'uid temporaire - String uid = Integer.toString(Math.abs(Integer.parseInt(note.GetUid()))); - File directory = new File ((ImapNotes2.getAppContext()).getFilesDir() + "/" + - Listactivity.imapNotes2Account.GetAccountname() + "/new"); - //message.setFrom(new InternetAddress("ImapNotes2", Listactivity.imapNotes2Account.GetAccountname())); - message.setFrom(Listactivity.imapNotes2Account.GetAccountname()); - File outfile = new File (directory, uid); - OutputStream str = new FileOutputStream(outfile); - message.writeTo(str); - - } } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/NewNoteActivity.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/NewNoteActivity.java index 1016e67e..7f1375c9 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/NewNoteActivity.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/NewNoteActivity.java @@ -4,54 +4,64 @@ import android.content.Intent; import android.graphics.Color; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.NavUtils; import android.text.Html; import android.view.Menu; import android.view.MenuItem; import android.widget.EditText; -public class NewNoteActivity extends Activity{ - +import static com.Pau.ImapNotes2.NoteDetailActivity.Colors; + +public class NewNoteActivity extends Activity { + private static final int SAVE_BUTTON = 5; - private static final String TAG = "IN_NewNoteActivity"; - private String sticky; - private String color = "NONE"; - - public void onCreate(Bundle savedInstanceState) { + @SuppressWarnings("unused") + private static final String TAG = "IN_NewNoteActivity"; + private boolean sticky; + @NonNull + private Colors color = Colors.NONE; + //region Intent item names + public static final String usesSticky = "usesSticky"; + //endregion + + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.new_note); - getActionBar().setDisplayHomeAsUpEnabled(true); - this.ResetColors(); - this.sticky = (String)getIntent().getExtras().get("usesSticky"); - } - - private void ResetColors(){ - ((EditText)findViewById(R.id.editNote)).setBackgroundColor(Color.TRANSPARENT); - ((EditText)findViewById(R.id.editNote)).setTextColor(Color.BLACK); - } - - public boolean onCreateOptionsMenu(Menu menu){ - getMenuInflater().inflate(R.menu.newnote, menu); + //noinspection ConstantConditions + getActionBar().setDisplayHomeAsUpEnabled(true); + this.ResetColors(); + this.sticky = (boolean) getIntent().getExtras().get(usesSticky); + } + + private void ResetColors() { + findViewById(R.id.editNote).setBackgroundColor(Color.TRANSPARENT); + ((EditText) findViewById(R.id.editNote)).setTextColor(Color.BLACK); + } + + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.newnote, menu); return true; } - public boolean onOptionsItemSelected (MenuItem item){ - switch (item.getItemId()){ - case R.id.save: - Intent intent=new Intent(); - intent.putExtra("SAVE_ITEM",Html.toHtml(((EditText)findViewById(R.id.editNote)).getText())); - if (this.sticky.equals("true")) { - this.color="YELLOW"; - } - intent.putExtra("SAVE_ITEM_COLOR",this.color); - setResult(SAVE_BUTTON, intent); - finish();//finishing activity - return true; - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - default: - return super.onOptionsItemSelected(item); + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.save: + Intent intent = new Intent(); + intent.putExtra("SAVE_ITEM", Html.toHtml(((EditText) findViewById(R.id.editNote)).getText())); + if (this.sticky) { + this.color = Colors.YELLOW; + } + intent.putExtra("SAVE_ITEM_COLOR", this.color); + setResult(SAVE_BUTTON, intent); + finish();//finishing activity + return true; + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + default: + return super.onOptionsItemSelected(item); } } + } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/NoteDetailActivity.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/NoteDetailActivity.java index 8dee10f2..9aae130a 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/NoteDetailActivity.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/NoteDetailActivity.java @@ -1,29 +1,14 @@ package com.Pau.ImapNotes2; -import java.util.HashMap; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Part; -import javax.mail.internet.ContentType; -import javax.mail.internet.MimeMessage; - -import org.apache.commons.io.IOUtils; - -import com.Pau.ImapNotes2.Miscs.OneNote; -import com.Pau.ImapNotes2.Miscs.Sticky; -import com.Pau.ImapNotes2.Sync.SyncUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.BuildConfig; import android.support.v4.app.NavUtils; import android.text.Html; import android.text.Spanned; @@ -32,193 +17,458 @@ import android.view.MenuItem; import android.view.View; import android.view.WindowManager; -import android.widget.EditText; + +import com.Pau.ImapNotes2.Miscs.Sticky; +import com.Pau.ImapNotes2.Sync.SyncUtils; + +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; + +import javax.mail.Message; +import javax.mail.internet.ContentType; + +import jp.wasabeef.richeditor.RichEditor; + public class NoteDetailActivity extends Activity { - - private static final int DELETE_BUTTON = 3; + + //private static final int DELETE_BUTTON = 3; private static final int EDIT_BUTTON = 6; - private HashMap hm; - private String usesticky; - private Sticky sticky; - private String stringres; - private String color; - private String position; - private int realColor = R.id.yellow; - private Boolean isClicked = false; - private Message message; + private boolean usesticky; + @NonNull + private Colors color = Colors.YELLOW; + //private int realColor = R.id.yellow; private String suid; // uid as string - private final static int ROOT_AND_NEW = 3; + // --Commented out by Inspection (11/26/16 11:52 PM):private final static int ROOT_AND_NEW = 3; private static final String TAG = "IN_NoteDetailActivity"; - + + + private RichEditor editText; + + //region Intent item names + public static final String useSticky = "useSticky"; + public static final String selectedNote = "selectedNote"; + //endregion + + public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.note_detail); - getActionBar().setDisplayHomeAsUpEnabled(true); - // Don't display keyboard when on note detail, only if user touches the screen - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN - ); - - this.hm = (HashMap)getIntent().getExtras().get("selectedNote"); - this.usesticky = (String)getIntent().getExtras().get("useSticky"); - - suid = this.hm.get("uid").toString(); - String rootDir = (ImapNotes2.getAppContext()).getFilesDir() + "/" + - Listactivity.imapNotes2Account.GetAccountname(); - message = SyncUtils.ReadMailFromFile(suid, ROOT_AND_NEW, true, rootDir); - sticky = GetInfoFromMessage(message); - stringres = sticky.GetText(); - position = sticky.GetPosition(); - color = sticky.GetColor(); - Spanned plainText = Html.fromHtml(stringres); - ((EditText)findViewById(R.id.bodyView)).setText(plainText); - this.ResetColors(); - //invalidateOptionsMenu(); + super.onCreate(savedInstanceState); + setContentView(R.layout.note_detail); + //noinspection ConstantConditions + getActionBar().setDisplayHomeAsUpEnabled(true); + // Don't display keyboard when on note detail, only if user touches the screen + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN + ); + + Bundle extras = getIntent().getExtras(); + HashMap hm = (HashMap) extras.get(selectedNote); + usesticky = (boolean) extras.get(useSticky); + assert hm != null; + suid = hm.get("uid").toString(); + File rootDir = new File(getApplicationContext().getFilesDir(), + Listactivity.imapNotes2Account.accountName); + Message message = SyncUtils.ReadMailFromFileRootAndNew(suid, rootDir); + //Log.d(TAG, "rootDir is null: " + (rootDir == null)); + Log.d(TAG, "rootDir: " + rootDir.toString()); + Sticky sticky = GetInfoFromMessage(message); + String stringres = sticky.text; + //String position = sticky.position; + color = sticky.color; + //Spanned plainText = Html.fromHtml(stringres); + //EditText editText = ((EditText) findViewById(R.id.bodyView)); + editText = (RichEditor) findViewById(R.id.bodyView); + //editText.setText(plainText); + SetupRichEditor(editText); + editText.setHtml(stringres); + +/* // TODO: Watch for changes so that we can auto save. + // See http://stackoverflow.com/questions/7117209/how-to-know-key-presses-in-edittext#14251047 + editText.addTextChangedListener(new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + //here is your code + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // TODO Work in progess + } + + @Override + public void afterTextChanged(Editable s) { + // TODO Work in progess + } + + }); + */ + ResetColors(); + //invalidateOptionsMenu(); + } + + + + private void SetupRichEditor(@NonNull final RichEditor mEditor) { + //mEditor = (RichEditor) findViewById(R.id.editor); + //mEditor.setEditorHeight(200); + //mEditor.setEditorFontSize(22); + mEditor.setEditorFontColor(Color.RED); + //mEditor.setEditorBackgroundColor(Color.BLUE); + //mEditor.setBackgroundColor(Color.BLUE); + //mEditor.setBackgroundResource(R.drawable.bg); + mEditor.setPadding(10, 10, 10, 10); + // mEditor.setBackground("https://raw.githubusercontent.com/wasabeef/art/master/chip.jpg"); + mEditor.setPlaceholder("Insert text here..."); + +/* + mPreview = (TextView) findViewById(R.id.preview); + mEditor.setOnTextChangeListener(new RichEditor.OnTextChangeListener() { + @Override public void onTextChange(String text) { + mPreview.setText(text); + } + }); + +*/ + findViewById(R.id.action_undo).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.undo(); + } + }); + + findViewById(R.id.action_redo).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.redo(); + } + }); + + findViewById(R.id.action_bold).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setBold(); + } + }); + + findViewById(R.id.action_italic).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setItalic(); + } + }); + + findViewById(R.id.action_subscript).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setSubscript(); + } + }); + + findViewById(R.id.action_superscript).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setSuperscript(); + } + }); + + findViewById(R.id.action_strikethrough).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setStrikeThrough(); + } + }); + + findViewById(R.id.action_underline).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setUnderline(); + } + }); + + findViewById(R.id.action_heading1).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setHeading(1); + } + }); + + findViewById(R.id.action_heading2).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setHeading(2); + } + }); + + findViewById(R.id.action_heading3).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setHeading(3); + } + }); + + findViewById(R.id.action_heading4).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setHeading(4); + } + }); + + findViewById(R.id.action_heading5).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setHeading(5); + } + }); + + findViewById(R.id.action_heading6).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setHeading(6); + } + }); + + findViewById(R.id.action_txt_color).setOnClickListener(new View.OnClickListener() { + private boolean isChanged; + + @Override public void onClick(View v) { + mEditor.setTextColor(isChanged ? Color.BLACK : Color.RED); + isChanged = !isChanged; + } + }); + + findViewById(R.id.action_bg_color).setOnClickListener(new View.OnClickListener() { + private boolean isChanged; + + @Override public void onClick(View v) { + mEditor.setTextBackgroundColor(isChanged ? Color.TRANSPARENT : Color.YELLOW); + isChanged = !isChanged; + } + }); + + findViewById(R.id.action_indent).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setIndent(); + } + }); + + findViewById(R.id.action_outdent).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setOutdent(); + } + }); + + findViewById(R.id.action_align_left).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setAlignLeft(); + } + }); + + findViewById(R.id.action_align_center).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setAlignCenter(); + } + }); + + findViewById(R.id.action_align_right).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setAlignRight(); + } + }); + + findViewById(R.id.action_blockquote).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setBlockquote(); + } + }); + + findViewById(R.id.action_insert_bullets).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setBullets(); + } + }); + + findViewById(R.id.action_insert_numbers).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.setNumbers(); + } + }); + + findViewById(R.id.action_insert_image).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.insertImage("http://www.1honeywan.com/dachshund/image/7.21/7.21_3_thumb.JPG", + "dachshund"); + } + }); + + findViewById(R.id.action_insert_link).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.insertLink("https://github.com/wasabeef", "wasabeef"); + } + }); + findViewById(R.id.action_insert_checkbox).setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + mEditor.insertTodo(); + } + }); } - - public void onClick(View v){ - this.isClicked = true; + +/* + // TODO: delete this? + public void onClick(View view) { + Log.d(TAG, "onClick"); + //Boolean isClicked = true; } - - private void ResetColors(){ - ((EditText)findViewById(R.id.bodyView)).setBackgroundColor(Color.TRANSPARENT); - ((EditText)findViewById(R.id.bodyView)).setTextColor(Color.BLACK); - Colors currentColor = Colors.valueOf(color); - switch (currentColor) { - case BLUE: - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFA6CAFD); - this.realColor = R.id.blue; - break; - case WHITE: - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFFFFFFF); - this.realColor = R.id.white; - break; - case YELLOW: - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFFFFFCC); - this.realColor = R.id.yellow; - break; - case PINK: - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFFFCCCC); - this.realColor = R.id.pink; - break; - case GREEN: - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFCCFFCC); - this.realColor = R.id.green; - break; - default: - (findViewById(R.id.scrollView)).setBackgroundColor(Color.TRANSPARENT); - } +*/ + + + // realColor is misnamed. It is the ID of the radio button widget that chooses the background + // colour. + private void ResetColors() { +/* + EditText bodyView = (EditText) findViewById(R.id.bodyView); + bodyView.setBackgroundColor(Color.TRANSPARENT); + bodyView.setTextColor(Color.BLACK); +*/ + (findViewById(R.id.scrollView)).setBackgroundColor(color.colorCode); + //realColor = color.id; invalidateOptionsMenu(); } - public boolean onCreateOptionsMenu(Menu menu){ + public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.detail, menu); return true; } @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem item= menu.findItem(R.id.color); + public boolean onPrepareOptionsMenu(@NonNull Menu menu) { + MenuItem item = menu.findItem(R.id.color); super.onPrepareOptionsMenu(menu); //depending on your conditions, either enable/disable - if (this.usesticky.equals("true")) { - item.setVisible(true); - } else { - item.setVisible(false); + item.setVisible(usesticky); + Log.d(TAG, "color.id: " + Integer.toString(color.id)); + Log.d(TAG, "mfi: " + (menu.findItem(color.id) == null)); + if (BuildConfig.DEBUG && (color == null)) { + throw new AssertionError("color is null"); } - menu.findItem(this.realColor).setChecked(true); + + menu.findItem(color.id).setChecked(true); return true; } - - public boolean onOptionsItemSelected (MenuItem item){ - Intent intent=new Intent(); - switch (item.getItemId()){ - case R.id.delete: - //Log.d(TAG,"We ask to delete Message #"+this.currentNote.get("number")); - intent.putExtra("DELETE_ITEM_NUM_IMAP",suid); - setResult(NoteDetailActivity.DELETE_BUTTON, intent); - finish();//finishing activity - return true; - case R.id.save: - //Log.d(TAG,"We ask to modify Message #"+this.currentNote.get("number")); - intent.putExtra("EDIT_ITEM_NUM_IMAP",suid); - intent.putExtra("EDIT_ITEM_TXT", - Html.toHtml(((EditText)findViewById(R.id.bodyView)).getText())); - if (!this.usesticky.equals("true")) { - this.color="NONE"; - } - intent.putExtra("EDIT_ITEM_COLOR",this.color); - setResult(NoteDetailActivity.EDIT_BUTTON, intent); - finish();//finishing activity - return true; - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - case R.id.blue: - item.setChecked(true); - this.color = "BLUE"; - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFA6CAFD); - return true; - case R.id.white: - item.setChecked(true); - this.color = "WHITE"; - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFFFFFFF); - return true; - case R.id.yellow: - item.setChecked(true); - this.color = "YELLOW"; - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFFFFFCC); - return true; - case R.id.pink: - item.setChecked(true); - this.color = "PINK"; - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFFFCCCC); - return true; - case R.id.green: - item.setChecked(true); - this.color = "GREEN"; - (findViewById(R.id.scrollView)).setBackgroundColor(0xFFCCFFCC); - return true; - default: - return super.onOptionsItemSelected(item); + + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + final Intent intent = new Intent(); + int itemId = item.getItemId(); + switch (itemId) { + case R.id.delete: + new AlertDialog.Builder(this) + .setTitle("Delete note") + .setMessage("Are you sure you wish to delete the note?") + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + //Log.d(TAG,"We ask to delete Message #"+this.currentNote.get("number")); + intent.putExtra("DELETE_ITEM_NUM_IMAP", suid); + setResult(Listactivity.DELETE_BUTTON, intent); + finish();//finishing activity + } + }) + .setNegativeButton(android.R.string.no, null).show(); + return true; + case R.id.save: + Save(); + return true; + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + case R.id.blue: + case R.id.white: + case R.id.yellow: + case R.id.pink: + case R.id.green: + item.setChecked(true); + color = Colors.fromId(itemId); + (findViewById(R.id.scrollView)).setBackgroundColor(color.colorCode); + return true; + default: + return super.onOptionsItemSelected(item); } } + + /** + * Note that this function does not save the note to permanent storage it just passes it back to + * the calling activity to be saved in whatever fashion it that activity wishes. + */ + private void Save() { + Log.d(TAG, "Save"); + Intent intent = new Intent(); + intent.putExtra(Listactivity.EDIT_ITEM_NUM_IMAP, suid); + /*intent.putExtra(Listactivity.EDIT_ITEM_TXT, + Html.toHtml(((EditText) findViewById(R.id.bodyView)).getText())); + */ + Log.d(TAG, "Save html: " + ((RichEditor) findViewById(R.id.bodyView)).getHtml()); + intent.putExtra(Listactivity.EDIT_ITEM_TXT, + ((RichEditor) findViewById(R.id.bodyView)).getHtml()); + if (!usesticky) { + Log.d(TAG, "not sticky so set color to none"); + color = Colors.NONE; + } + intent.putExtra(Listactivity.EDIT_ITEM_COLOR, color); + setResult(NoteDetailActivity.EDIT_BUTTON, intent); + finish();//finishing activity + + } + + // List the colours together with the ids of the option widgets used to select them and the + // RGB values used as the actual colours. Doing this means that we do not need so much code + // in switch statements, etc. public enum Colors { - BLUE, - WHITE, - YELLOW, - PINK, - GREEN, - NONE + BLUE(R.id.blue, 0xFFA6CAFD), + WHITE(R.id.white, 0xFFFFFFFF), + YELLOW(R.id.yellow, 0xFFFFFFCC), + PINK(R.id.pink, 0xFFFFCCCC), + GREEN(R.id.green, 0xFFCCFFCC), + NONE(R.id.white, 0xFFFFFFFF); + + public final int id; + public final int colorCode; + + Colors(int id, + int colorCode) { + this.id = id; + this.colorCode = colorCode; + } + + @NonNull + public static Colors fromId(int id) { + + for (Colors color : Colors.values()) { + if (color.id == id) + return color; + } + throw new IllegalArgumentException("id not found in Colors: " + Integer.toString(id)); + } } - private Sticky GetInfoFromMessage (Message message) { + @Nullable + private Sticky GetInfoFromMessage(@NonNull Message message) { ContentType contentType = null; String stringres = null; - InputStream iis = null; - String color = "NONE"; - String charset; - Sticky sticky = null; + //InputStream iis = null; + //Colors color = NONE; + //String charset; try { -//Log.d(TAG, "Contenttype as string:"+message.getContentType()); - contentType = new ContentType(message.getContentType() ); - charset = contentType.getParameter("charset"); - iis = (InputStream)message.getContent(); + Log.d(TAG, "message :" + message.toString()); + + contentType = new ContentType(message.getContentType()); + String charset = contentType.getParameter("charset"); + InputStream iis = (InputStream) message.getContent(); stringres = IOUtils.toString(iis, charset); } catch (Exception e) { // TODO Auto-generated catch block + Log.d(TAG, "Exception GetInfoFromMessage:"); + Log.d(TAG, e.toString()); e.printStackTrace(); - } + } -//Log.d(TAG,"contentType:"+contentType); + Log.d(TAG,"contentType:"+contentType); + Sticky sticky = null; if (contentType.match("text/x-stickynote")) { - sticky = SyncUtils.ReadStickynote(stringres); + sticky = SyncUtils.ReadStickyNote(stringres); } else if (contentType.match("TEXT/HTML")) { - sticky = ReadHtmlnote(stringres); + sticky = ReadHtmlnote(stringres); } else if (contentType.match("TEXT/PLAIN")) { - sticky = ReadPlainnote(stringres); + sticky = ReadPlainnote(stringres); } else if (contentType.match("multipart/related")) { // All next is a workaround // All function need to be rewritten to handle correctly multipart and images @@ -228,37 +478,38 @@ private Sticky GetInfoFromMessage (Message message) { sticky = ReadPlainnote(stringres); } } else if (contentType.getParameter("BOUNDARY") != null) { - sticky = ReadHtmlnote(stringres); + sticky = ReadHtmlnote(stringres); } return sticky; } - private void GetPart(Part message) throws Exception { -if (message.isMimeType("text/plain")) { -Log.d(TAG,"+++ isMimeType text/plain (contentType):"+message.getContentType()); -} else if (message.isMimeType("multipart/*")) { -Log.d(TAG,"+++ isMimeType multipart/* (contentType):"+message.getContentType()); -Object content = message.getContent(); - Multipart mp = (Multipart) content; - int count = mp.getCount(); - for (int i = 0; i < count; i++) GetPart(mp.getBodyPart(i)); -} else if (message.isMimeType("message/rfc822")) { -Log.d(TAG,"+++ isMimeType message/rfc822/* (contentType):"+message.getContentType()); -GetPart((Part) message.getContent()); -} else if (message.isMimeType("image/jpeg")) { -Log.d(TAG,"+++ isMimeType image/jpeg (contentType):"+message.getContentType()); -} else if (message.getContentType().contains("image/")) { -Log.d(TAG,"+++ isMimeType image/jpeg (contentType):"+message.getContentType()); -} else { - Object o = message.getContent(); - if (o instanceof String) { - Log.d(TAG,"+++ instanceof String"); - } else if (o instanceof InputStream) { - Log.d(TAG,"+++ instanceof InputStream"); - } else Log.d(TAG,"+++ instanceof ???"); -} + /* private void GetPart(@NonNull Part message) throws Exception { + if (message.isMimeType("text/plain")) { + Log.d(TAG, "+++ isMimeType text/plain (contentType):" + message.getContentType()); + } else if (message.isMimeType("multipart*//*")) { + Log.d(TAG, "+++ isMimeType multipart*//* (contentType):" + message.getContentType()); + Object content = message.getContent(); + Multipart mp = (Multipart) content; + int count = mp.getCount(); + for (int i = 0; i < count; i++) GetPart(mp.getBodyPart(i)); + } else if (message.isMimeType("message/rfc822")) { + Log.d(TAG, "+++ isMimeType message/rfc822*//* (contentType):" + message.getContentType()); + GetPart((Part) message.getContent()); + } else if (message.isMimeType("image/jpeg")) { + Log.d(TAG, "+++ isMimeType image/jpeg (contentType):" + message.getContentType()); + } else if (message.getContentType().contains("image/")) { + Log.d(TAG, "+++ isMimeType image/jpeg (contentType):" + message.getContentType()); + } else { + Object o = message.getContent(); + if (o instanceof String) { + Log.d(TAG, "+++ instanceof String"); + } else if (o instanceof InputStream) { + Log.d(TAG, "+++ instanceof InputStream"); + } else Log.d(TAG, "+++ instanceof ???"); + } } - +*/ + @NonNull private Sticky ReadHtmlnote(String stringres) { // Log.d(TAG,"From server (html):"+stringres); Spanned spanres = Html.fromHtml(stringres); @@ -269,29 +520,31 @@ private Sticky ReadHtmlnote(String stringres) { stringres = stringres.replaceAll("

", "
"); stringres = stringres.replaceAll("

", ""); - return new Sticky(stringres, "", "NONE"); + return new Sticky(stringres, Colors.NONE); } + @NonNull private Sticky ReadPlainnote(String stringres) { // Log.d(TAG,"From server (plain):"+stringres); stringres = stringres.replaceAll("\n", "
"); - return new Sticky(stringres, "", "NONE"); - } - - private void WriteMailToFile (String suid, Message message) { - String directory = (ImapNotes2.getAppContext()).getFilesDir() + "/" + - Listactivity.imapNotes2Account.GetAccountname(); - try { - File outfile = new File (directory, suid); - OutputStream str = new FileOutputStream(outfile); - message.writeTo(str); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + return new Sticky(stringres, Colors.NONE); } +// --Commented out by Inspection START (12/2/16 8:50 PM): +// private void WriteMailToFile(@NonNull String suid, @NonNull Message message) { +// String directory = getApplicationContext().getFilesDir() + "/" + +// Listactivity.imapNotes2Account.accountName; +// try { +// File outfile = new File(directory, suid); +// OutputStream str = new FileOutputStream(outfile); +// message.writeTo(str); +// } catch (Exception e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// --Commented out by Inspection STOP (12/2/16 8:50 PM) } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/NotesListAdapter.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/NotesListAdapter.java index 2568530c..7b6821a7 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/NotesListAdapter.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/NotesListAdapter.java @@ -17,6 +17,10 @@ */ import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; @@ -24,9 +28,8 @@ import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; +import android.widget.SimpleAdapter; import android.widget.TextView; -import android.view.LayoutInflater; -import android.net.Uri; import java.util.ArrayList; import java.util.List; @@ -38,60 +41,57 @@ * in the list. The Maps contain the data for each row. You also specify an XML file that * defines the views used to display the row, and a mapping from keys in the Map to specific * views. - * + *

* Binding data to views occurs in two phases. First, if a * {@link android.widget.SimpleAdapter.ViewBinder} is available, * {@link ViewBinder#setViewValue(android.view.View, Object, String)} - * is invoked. If the returned value is true, binding has occurred. + * is invoked. If the returned value is true, binding has occurred. * If the returned value is false, the following views are then tried in order: *

    *
  • A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean. - *
  • TextView. The expected bind value is a string and {@link #setViewText(TextView, String)} + *
  • TextView. The expected bind value is a string and {@link #setViewText(TextView, String)} * is invoked. - *
  • ImageView. The expected bind value is a resource id or a string and - * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked. + *
  • ImageView. The expected bind value is a resource id or a string and + * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked. *
* If no appropriate binding can be found, an {@link IllegalStateException} is thrown. */ public class NotesListAdapter extends BaseAdapter implements Filterable { - private int[] mTo; - private String[] mFrom; - private ViewBinder mViewBinder; + private final int[] mTo; + private final String[] mFrom; + // --Commented out by Inspection (12/3/16 11:31 PM):private ViewBinder mViewBinder; private List> mData; - private int mResource; - private int mDropDownResource; - private LayoutInflater mInflater; + private final int mResource; + private final int mDropDownResource; + @NonNull + private final LayoutInflater mInflater; private SimpleFilter mFilter; private ArrayList> mUnfilteredData; /** * Constructor - * - * @param context The context where the View associated with this SimpleAdapter is running - * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The - * Maps contain the data for each row, and should include all the entries specified in - * "from" - * @param resource Resource identifier of a view layout that defines the views for this list - * item. The layout file should include at least those named views defined in "to" - * @param from A list of column names that will be added to the Map associated with each - * item. - * @param to The views that should display column in the "from" parameter. These should all be - * TextViews. The first N views in this list are given the values of the first N columns - * in the from parameter. + * @param context The context where the View associated with this SimpleAdapter is running + * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The + * Maps contain the data for each row, and should include all the entries specified in + * "from" + * @param from A list of column names that will be added to the Map associated with each + * item. + * @param to The views that should display column in the "from" parameter. These should all be + * TextViews. The first N views in this list are given the values of the first N columns */ - public NotesListAdapter(Context context, List> data, - int resource, String[] from, int[] to) { + NotesListAdapter(@NonNull Context context, List> data, + String[] from, int[] to) { mData = data; - mResource = mDropDownResource = resource; + mResource = mDropDownResource = R.layout.note_element; mFrom = from; mTo = to; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } - + /** * @see android.widget.Adapter#getCount() */ @@ -116,12 +116,15 @@ public long getItemId(int position) { /** * @see android.widget.Adapter#getView(int, View, ViewGroup) */ + @Nullable public View getView(int position, View convertView, ViewGroup parent) { return createViewFromResource(position, convertView, parent, mResource); } - private View createViewFromResource(int position, View convertView, - ViewGroup parent, int resource) { + // TODO: this should never return null and the convertView argument should never be null. + @Nullable + private View createViewFromResource(int position, @Nullable View convertView, + ViewGroup parent, int resource) { View v; if (convertView == null) { v = mInflater.inflate(resource, parent, false); @@ -134,28 +137,32 @@ private View createViewFromResource(int position, View convertView, return v; } - /** - *

Sets the layout resource to create the drop down views.

- * - * @param resource the layout resource defining the drop down views - * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) - */ - public void setDropDownViewResource(int resource) { - this.mDropDownResource = resource; - } - +// --Commented out by Inspection START (12/2/16 9:22 PM): +// /** +// *

Sets the layout resource to create the drop down views.

+// * +// * @param resource the layout resource defining the drop down views +// * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) +// */ +// public void setDropDownViewResource(int resource) { +// this.mDropDownResource = resource; +// } +// --Commented out by Inspection STOP (12/2/16 9:22 PM) + + // TODO: Should never return null. + @Nullable @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { return createViewFromResource(position, convertView, parent, mDropDownResource); } - private void bindView(int position, View view) { + private void bindView(int position, @NonNull View view) { final Map dataSet = mData.get(position); if (dataSet == null) { return; } - final ViewBinder binder = mViewBinder; + //final ViewBinder binder = mViewBinder; final String[] from = mFrom; final int[] to = mTo; final int count = to.length; @@ -170,9 +177,9 @@ private void bindView(int position, View view) { } boolean bound = false; - if (binder != null) { - bound = binder.setViewValue(v, data, text); - } + //if (binder != null) { + // bound = binder.setViewValue(v, data, text); + //} if (!bound) { if (v instanceof Checkable) { @@ -193,7 +200,7 @@ private void bindView(int position, View view) { setViewText((TextView) v, text); } else if (v instanceof ImageView) { if (data instanceof Integer) { - setViewImage((ImageView) v, (Integer) data); + setViewImage((ImageView) v, (Integer) data); } else { setViewImage((ImageView) v, text); } @@ -206,43 +213,44 @@ private void bindView(int position, View view) { } } - /** - * Returns the {@link ViewBinder} used to bind data to views. - * - * @return a ViewBinder or null if the binder does not exist - * - * @see #setViewBinder(android.widget.SimpleAdapter.ViewBinder) - */ - public ViewBinder getViewBinder() { - return mViewBinder; - } - - /** - * Sets the binder used to bind data to views. - * - * @param viewBinder the binder used to bind data to views, can be null to - * remove the existing binder - * - * @see #getViewBinder() - */ - public void setViewBinder(ViewBinder viewBinder) { - mViewBinder = viewBinder; - } +// --Commented out by Inspection START (12/2/16 9:22 PM): +// /** +// * Returns the {@link ViewBinder} used to bind data to views. +// * +// * @return a ViewBinder or null if the binder does not exist +// * @see #getViewBinder() +// */ +// private ViewBinder getViewBinder() { +// return mViewBinder; +// } +// --Commented out by Inspection STOP (12/2/16 9:22 PM) + +// --Commented out by Inspection START (12/2/16 9:13 PM): +// /** +// * Sets the binder used to bind data to views. +// * +// * @param viewBinder the binder used to bind data to views, can be null to +// * remove the existing binder +// * @see #getViewBinder() +// */ +// public void setViewBinder(ViewBinder viewBinder) { +// mViewBinder = viewBinder; +// } +// --Commented out by Inspection STOP (12/2/16 9:13 PM) /** * Called by bindView() to set the image for an ImageView but only if * there is no existing ViewBinder or if the existing ViewBinder cannot * handle binding to an ImageView. - * + *

* This method is called instead of {@link #setViewImage(ImageView, String)} * if the supplied data is an int or Integer. * - * @param v ImageView to receive an image + * @param v ImageView to receive an image * @param value the value retrieved from the data set - * * @see #setViewImage(ImageView, String) */ - public void setViewImage(ImageView v, int value) { + private void setViewImage(@NonNull ImageView v, int value) { v.setImageResource(value); } @@ -250,20 +258,19 @@ public void setViewImage(ImageView v, int value) { * Called by bindView() to set the image for an ImageView but only if * there is no existing ViewBinder or if the existing ViewBinder cannot * handle binding to an ImageView. - * + *

* By default, the value will be treated as an image resource. If the * value cannot be used as an image resource, the value is used as an * image Uri. - * + *

* This method is called instead of {@link #setViewImage(ImageView, int)} * if the supplied data is not an int or Integer. * - * @param v ImageView to receive an image + * @param v ImageView to receive an image * @param value the value retrieved from the data set - * - * @see #setViewImage(ImageView, int) + * @see #setViewImage(ImageView, int) */ - public void setViewImage(ImageView v, String value) { + private void setViewImage(@NonNull ImageView v, String value) { try { v.setImageResource(Integer.parseInt(value)); } catch (NumberFormatException nfe) { @@ -276,10 +283,10 @@ public void setViewImage(ImageView v, String value) { * there is no existing ViewBinder or if the existing ViewBinder cannot * handle binding to a TextView. * - * @param v TextView to receive text + * @param v TextView to receive text * @param text the text to be set for the TextView */ - public void setViewText(TextView v, String text) { + private void setViewText(@NonNull TextView v, String text) { v.setText(text); } @@ -293,7 +300,7 @@ public Filter getFilter() { /** * This class can be used by external clients of SimpleAdapter to bind * values to views. - * + *

* You should use this class to bind values to views that are not * directly supported by SimpleAdapter or to change the way binding * occurs for views supported by SimpleAdapter. @@ -302,20 +309,19 @@ public Filter getFilter() { * @see SimpleAdapter#setViewImage(ImageView, String) * @see SimpleAdapter#setViewText(TextView, String) */ - public static interface ViewBinder { + interface ViewBinder { /** * Binds the specified data to the specified view. - * + *

* When binding is handled by this ViewBinder, this method must return true. * If this method returns false, SimpleAdapter will attempts to handle * the binding on its own. * - * @param view the view to bind the data to - * @param data the data to bind to the view + * @param view the view to bind the data to + * @param data the data to bind to the view * @param textRepresentation a safe String representation of the supplied data: - * it is either the result of data.toString() or an empty String but it - * is never null - * + * it is either the result of data.toString() or an empty String but it + * is never null * @return true if the data was bound to the view, false otherwise */ boolean setViewValue(View view, Object data, String textRepresentation); @@ -328,12 +334,13 @@ public static interface ViewBinder { */ private class SimpleFilter extends Filter { + @NonNull @Override - protected FilterResults performFiltering(CharSequence prefix) { + protected FilterResults performFiltering(@Nullable CharSequence prefix) { FilterResults results = new FilterResults(); if (mUnfilteredData == null) { - mUnfilteredData = new ArrayList>(mData); + mUnfilteredData = new ArrayList<>(mData); } if (prefix == null || prefix.length() == 0) { @@ -346,17 +353,17 @@ protected FilterResults performFiltering(CharSequence prefix) { ArrayList> unfilteredValues = mUnfilteredData; int count = unfilteredValues.size(); - ArrayList> newValues = new ArrayList>(count); + ArrayList> newValues = new ArrayList<>(count); for (int i = 0; i < count; i++) { Map h = unfilteredValues.get(i); if (h != null) { - + int len = mTo.length; - for (int j=0; j>) results.values; if (results.count > 0) { diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/ImapNotesAuthenticatorService.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/ImapNotesAuthenticatorService.java new file mode 100644 index 00000000..be564d56 --- /dev/null +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/ImapNotesAuthenticatorService.java @@ -0,0 +1,128 @@ +package com.Pau.ImapNotes2.Sync; + +import com.Pau.ImapNotes2.AccountConfigurationActivity; +import com.Pau.ImapNotes2.Sync.SyncUtils; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class ImapNotesAuthenticatorService extends Service { + + private static final String TAG = "ImapNotesAuthenticationService"; + private Authenticator imapNotesAuthenticator; + + @Override + public void onCreate() { + this.imapNotesAuthenticator = new Authenticator(this); + } + + public IBinder onBind(@NonNull Intent intent) { + IBinder ret = null; + if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) + ret = getAuthenticator().getIBinder(); + + return ret; + } + + private Authenticator getAuthenticator() { + if (this.imapNotesAuthenticator == null) + this.imapNotesAuthenticator = new Authenticator(this); + + return this.imapNotesAuthenticator; + } + + private static class Authenticator extends AbstractAccountAuthenticator { + + private final Context mContext; + + public Authenticator(Context context) { + super(context); + this.mContext = context; + } + + @Override + public Bundle getAccountRemovalAllowed( + AccountAuthenticatorResponse response, @NonNull Account account) + throws NetworkErrorException { + Bundle ret = super.getAccountRemovalAllowed(response, account); + if (ret.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) + SyncUtils.RemoveAccount(this.mContext, account); +/* + mContext.getContentResolver().delete(ListProvider.getClearUri(), + null, null); +*/ + return ret; + } + + @NonNull + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { + + Intent toLoginActivity = new Intent(this.mContext, AccountConfigurationActivity.class); + toLoginActivity.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, toLoginActivity); + + return bundle; + } + + @Nullable + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { + + return null; + } + + // TODO: Describe the purpose of this method. + @Nullable + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { + + return null; + } + + // TODO: Describe the purpose of this method. + @Nullable + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + + return null; + } + + // TODO: Describe the purpose of this method. + @Nullable + @Override + public String getAuthTokenLabel(String authTokenType) { + + return null; + } + + // TODO: Describe the purpose of this method. + @Nullable + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { + + return null; + } + + // TODO: Describe the purpose of this method. + @Nullable + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + + return null; + } + + } + +} diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/StubProvider.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/StubProvider.java index f7adae8b..737ee786 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/StubProvider.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/StubProvider.java @@ -4,12 +4,16 @@ import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; -/* +/** * Define an implementation of ContentProvider that stubs out - * all methods + * all methods. + * + * TODO: Find out what this is for and explain it in the comments. */ -public class StubProvider extends ContentProvider { +class StubProvider extends ContentProvider { /* * Always return true, indicating that the * provider loaded correctly. @@ -21,17 +25,19 @@ public boolean onCreate() { /* * Return no type for MIME type */ + @Nullable @Override - public String getType(Uri uri) { + public String getType(@NonNull Uri uri) { return null; } /* * query() always returns no results * */ + @Nullable @Override public Cursor query( - Uri uri, + @NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, @@ -41,22 +47,23 @@ public Cursor query( /* * insert() always returns null (no URI) */ + @Nullable @Override - public Uri insert(Uri uri, ContentValues values) { + public Uri insert(@NonNull Uri uri, ContentValues values) { return null; } /* * delete() always returns "no rows affected" (0) */ @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { return 0; } /* * update() always returns "no rows affected" (0) */ public int update( - Uri uri, + @NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncAdapter.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncAdapter.java index 87c50650..4773cadf 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncAdapter.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncAdapter.java @@ -4,127 +4,146 @@ import android.accounts.AccountManager; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SyncResult; import android.os.Bundle; +import android.support.annotation.NonNull; import android.util.Log; +import com.Pau.ImapNotes2.Data.ConfigurationFieldNames; +import com.Pau.ImapNotes2.Data.Db; +import com.Pau.ImapNotes2.Data.ImapNotes2Account; +import com.Pau.ImapNotes2.Data.Security; +import com.Pau.ImapNotes2.Listactivity; +import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; +import com.sun.mail.imap.AppendUID; + import java.io.File; import java.io.IOException; + import javax.mail.Flags; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; -import com.Pau.ImapNotes2.Data.NotesDb; -import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; -import com.Pau.ImapNotes2.Sync.SyncUtils; -import com.sun.mail.imap.AppendUID; +import static com.Pau.ImapNotes2.Miscs.Imaper.ResultCodeSuccess; + +//import com.Pau.ImapNotes2.Data.NotesDb; +/// A SyncAdapter provides methods to be called by the Android +/// framework when the framework is ready for the synchronization to +/// occur. The application does not need to consider threading +/// because the sync happens under Android control not under control +/// of the application. class SyncAdapter extends AbstractThreadedSyncAdapter { - public static final String TAG = "SyncAdapter"; - private static Context context; - private static Boolean isChanged; - private static Boolean isSynced; - private NotesDb storedNotes; - private String[] listOfNew; - private String[] listOfDeleted; - private static Account account; - private Long UIDValidity = (long) -1; - private static ImapNotes2Result res; - private final static int NEW = 1; - private final static int DELETED = 2; - - private final ContentResolver mContentResolver; - - public SyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - mContentResolver = context.getContentResolver(); - this.context = context; - } + private static final String TAG = "SyncAdapter"; + @NonNull + private final Context applicationContext; + private Db storedNotes; + // TODO: Why was this static? + //private Account account; + private ImapNotes2Account account; + +// --Commented out by Inspection START (11/26/16 11:49 PM): +// /// See RFC 3501: http://www.faqs.org/rfcs/rfc3501.html +// @NonNull +// private Long UIDValidity = (long) -1; +// --Commented out by Inspection STOP (11/26/16 11:49 PM) + // --Commented out by Inspection (11/26/16 11:49 PM):private final static int NEW = 1; + // --Commented out by Inspection (11/26/16 11:49 PM):private final static int DELETED = 2; + + // --Commented out by Inspection (12/2/16 8:51 PM):private final ContentResolver mContentResolver; - public SyncAdapter(Context context, boolean autoInitialize, - boolean allowParallelSyncs) { - super(context, autoInitialize, allowParallelSyncs); - mContentResolver = context.getContentResolver(); - this.context = context; + SyncAdapter(@NonNull Context applicationContext) { + super(applicationContext, true); + Log.d(TAG, "SyncAdapter"); + + //mContentResolver = applicationContext.getContentResolver(); + // TODO: do we really need a copy of the applicationContext reference? + this.applicationContext = applicationContext; } + +// --Commented out by Inspection START (12/2/16 8:50 PM): +// /** +// * TODO: What does allowParallelSyncs do and is it useful to us? +// * +// * @param applicationContext In our case this is always the global application context but it +// * might not need to be. +// * @param autoInitialize I've read the documentation for this argument and am no wiser. +// * @param allowParallelSyncs Allow sync for different accounts to run at the same time. +// */ +// public SyncAdapter(@NonNull Context applicationContext, +// boolean autoInitialize, // ? +// boolean allowParallelSyncs // always false, set in syncadapter.xml +// ) { +// super(applicationContext, autoInitialize, allowParallelSyncs); +// mContentResolver = applicationContext.getContentResolver(); +// this.applicationContext = applicationContext; +// } +// --Commented out by Inspection STOP (12/2/16 8:50 PM) + @Override - public void onPerformSync(Account account, Bundle extras, String authority, - ContentProviderClient provider, SyncResult syncResult) { - //Log.d(TAG, "Beginning network synchronization of account: "+account.name); - this.account = account; - isChanged = false; - isSynced = false; - String syncinterval; - - SyncUtils.CreateDirs (account.name, this.context); - - storedNotes = new NotesDb(this.context); + public void onPerformSync(@NonNull Account accountArg, + Bundle extras, + String authority, + ContentProviderClient provider, + SyncResult syncResult) { + Log.d(TAG, "Beginning network synchronization of account: " + accountArg.name); + // TODO: should the account be static? Should it be local? If static then why do we not + // provide it in the constructor? What happens if we allow parallel syncs? + account = new ImapNotes2Account(accountArg, applicationContext); + + //SyncUtils.CreateLocalDirectories(accountArg.name, applicationContext); + account.CreateLocalDirectories(); + storedNotes = new Db(applicationContext); storedNotes.OpenDb(); - AccountManager am = AccountManager.get(this.context); - syncinterval = am.getUserData(account, "syncinterval"); -/* -// Temporary workaround for a bug -// Portnum was put into account manager sync interval (143 or 993 or ... minutes) -if (syncinterval != null) -if (syncinterval.equals("143") || syncinterval.equals("993")) am.setUserData(account, "syncinterval", "15"); -else am.setUserData(account, "syncinterval", "15"); -*/ + AccountManager am = AccountManager.get(applicationContext); + //String syncInterval = am.getUserData(accountArg, "syncinterval"); + //String syncInterval = account.GetSyncinterval(); // Connect to remote and get UIDValidity - this.res = ConnectToRemote(); - if (this.res.returnCode != 0) { + ImapNotes2Result res = ConnectToRemote(); + if (res.returnCode != ResultCodeSuccess) { storedNotes.CloseDb(); - - // Notify Listactivity that it's finished, but it can't refresh display - Intent i = new Intent(SyncService.SYNC_FINISHED); - i.putExtra("ACCOUNTNAME",account.name); - isChanged = false; - isSynced = false; - i.putExtra("CHANGED", isChanged); - i.putExtra("SYNCED", isSynced); - i.putExtra("SYNCINTERVAL", syncinterval); - context.sendBroadcast(i); + NotifySyncFinished(false, false); return; } // Compare UIDValidity to old saved one - if (!(this.res.UIDValidity.equals - (SyncUtils.GetUIDValidity(this.account, this.context)))) { - // Replace local data by remote + // + if (!(res.UIDValidity.equals( + SyncUtils.GetUIDValidity(accountArg, applicationContext)))) { + // Replace local data by remote. UIDs are no longer valid. try { // delete notes in NotesDb for this account - storedNotes.ClearDb(account.name); + storedNotes.notes.ClearDb(accountArg.name); // delete notes in folders for this account and recreate dirs - SyncUtils.ClearHomeDir(account, this.context); - SyncUtils.CreateDirs (account.name, this.context); + //SyncUtils.ClearHomeDir(accountArg, applicationContext); + account.ClearHomeDir(); + //SyncUtils.CreateLocalDirectories(accountArg.name, applicationContext); + account.CreateLocalDirectories(); // Get all notes from remote and replace local - SyncUtils.GetNotes(account,this.res.notesFolder,this.context,storedNotes); + SyncUtils.GetNotes(accountArg, + res.notesFolder, + applicationContext, storedNotes); storedNotes.CloseDb(); } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + // TODO Auto-generated catch block + e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + // TODO Auto-generated catch block + e.printStackTrace(); } - SyncUtils.SetUIDValidity(account, this.res.UIDValidity, this.context); + SyncUtils.SetUIDValidity(accountArg, res.UIDValidity, applicationContext); // Notify Listactivity that it's finished, and that it can refresh display - Intent i = new Intent(SyncService.SYNC_FINISHED); - i.putExtra("ACCOUNTNAME",account.name); - isChanged = true; - isSynced = true; - i.putExtra("CHANGED", isChanged); - i.putExtra("SYNCED", isSynced); - i.putExtra("SYNCINTERVAL", syncinterval); - context.sendBroadcast(i); + NotifySyncFinished(true, true); return; } - + + boolean isChanged = false; + // Send new local messages to remote, move them to local folder // and update uids in database boolean newNotesManaged = handleNewNotes(); @@ -136,16 +155,17 @@ public void onPerformSync(Account account, Bundle extras, String authority, // handle notes created or removed on remote boolean remoteNotesManaged = false; - String usesticky = am.getUserData(account, "usesticky"); - try { - remoteNotesManaged = SyncUtils.handleRemoteNotes(context, res.notesFolder, storedNotes, account.name, usesticky); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + String usesticky = am.getUserData(accountArg, ConfigurationFieldNames.UseSticky); + try { + remoteNotesManaged = SyncUtils.handleRemoteNotes(applicationContext, res.notesFolder, + storedNotes, accountArg.name, usesticky); + } catch (MessagingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } if (remoteNotesManaged) isChanged = true; storedNotes.CloseDb(); @@ -154,95 +174,109 @@ public void onPerformSync(Account account, Bundle extras, String authority, SyncUtils.DisconnectFromRemote(); //Log.d(TAG, "Network synchronization complete of account: "+account.name); // Notify Listactivity that it's finished, and that it can refresh display + NotifySyncFinished(isChanged, true); + } + + private void NotifySyncFinished(boolean isChanged, + boolean isSynced) { + Log.d(TAG, "NotifySyncFinished: " + isChanged + " " + isSynced); Intent i = new Intent(SyncService.SYNC_FINISHED); - i.putExtra("ACCOUNTNAME",account.name); - i.putExtra("CHANGED", isChanged); - isSynced = true; - i.putExtra("SYNCED", isSynced); - i.putExtra("SYNCINTERVAL", syncinterval); - context.sendBroadcast(i); + i.putExtra(Listactivity.ACCOUNTNAME, account.accountName); + i.putExtra(Listactivity.CHANGED, isChanged); + i.putExtra(Listactivity.SYNCED, isSynced); + i.putExtra(Listactivity.SYNCINTERVAL, account.syncInterval); + applicationContext.sendBroadcast(i); + } - ImapNotes2Result ConnectToRemote() { - AccountManager am = AccountManager.get(this.context); - ImapNotes2Result res = null; - try { - res = SyncUtils.ConnectToRemote( - am.getUserData(account, "username"), - am.getPassword(account), - am.getUserData(account, "server"), - am.getUserData(account, "portnum"), - am.getUserData(account, "security"), - am.getUserData(account, "usesticky"), - am.getUserData(account, "imapfolder")); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (res.returnCode != 0) { - Log.d(TAG,"Connection problem !!!"); + /* It is possible for this function to throw exceptions; the original code caught + MessagingException but just logged it instead of handling it. This results in a possibility of + returning null. Removing the catch fixes the possible null reference but of course means that + the caller becomes responsible. This is the correct approach. + */ + @NonNull + private ImapNotes2Result ConnectToRemote() { + Log.d(TAG, "ConnectToRemote"); + AccountManager am = AccountManager.get(applicationContext); + ImapNotes2Result res = SyncUtils.ConnectToRemote( + account.username, + //am.getUserData(account.GetAccount(), ConfigurationFieldNames.UserName), + am.getPassword(account.GetAccount()), + am.getUserData(account.GetAccount(), ConfigurationFieldNames.Server), + am.getUserData(account.GetAccount(), ConfigurationFieldNames.PortNumber), + Security.from(am.getUserData(account.GetAccount(), ConfigurationFieldNames.Security)), + account.imapfolder); + if (res.returnCode != ResultCodeSuccess) { + // TODO: Notify the user? + Log.d(TAG, "Connection problem: " + res.errorMessage); } return res; } private boolean handleNewNotes() { - Message message = null; + Log.d(TAG, "handleNewNotes"); + //Message message = null; boolean newNotesManaged = false; - AppendUID[] uids = null; - String rootString = context.getFilesDir() + "/" + account.name; - File rootDir = new File (rootString); - File dirNew = new File (rootDir + "/new"); - listOfNew = dirNew.list(); + //AppendUID[] uids = null; + //String rootString = applicationContext.getFilesDir() + File.separator + account.name; + //File rootDir = new File(rootString); + File accountDir = new File(applicationContext.getFilesDir(), account.GetAccount().name); + File dirNew = new File(accountDir, "new"); + Log.d(TAG, "dn path: " + dirNew.getAbsolutePath()); + Log.d(TAG, "dn exists: " + Boolean.toString(dirNew.exists())); + String[] listOfNew = dirNew.list(); for (String fileNew : listOfNew) { - //Log.d(TAG,"New Note to process:"+fileNew); + Log.d(TAG,"New Note to process:"+fileNew); newNotesManaged = true; // Read local new message from file - message = SyncUtils.ReadMailFromFile(fileNew, NEW, false, rootString); + Message message = SyncUtils.ReadMailFromFileNew(fileNew, dirNew); + Log.d(TAG,"handleNewNotes message: " + message.toString()); try { - message.setFlag(Flags.Flag.SEEN,true); // set message as seen + message.setFlag(Flags.Flag.SEEN, true); // set message as seen } catch (MessagingException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Send this new message to remote - final MimeMessage[] msg = {(MimeMessage)message}; - + final MimeMessage[] msg = {(MimeMessage) message}; + try { - uids = SyncUtils.sendMessageToRemote(msg); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + AppendUID[] uids = SyncUtils.sendMessageToRemote(msg); + // Update uid in database entry + String newuid = Long.toString(uids[0].uid); + storedNotes.notes.UpdateANote(fileNew, newuid, account.accountName); + // move new note from new dir, one level up + File fileInNew = new File(dirNew, fileNew); + File to = new File(accountDir, newuid); + //noinspection ResultOfMethodCallIgnored + fileInNew.renameTo(to); + } catch (Exception e) { + // TODO: Handle message properly. + Log.d(TAG, e.getMessage()); } - // Update uid in database entry - String newuid = Long.toString(uids[0].uid); - storedNotes.UpdateANote(fileNew,newuid,account.name); - // move new note from new dir, one level up - File fileInNew = new File (dirNew, fileNew); - File to = new File (rootDir, newuid); - fileInNew.renameTo(to); } return newNotesManaged; } private boolean handleDeletedNotes() { - Message message = null; + //Message message = null; + Log.d(TAG, "handleDeletedNotes"); boolean deletedNotesManaged = false; - String rootString = context.getFilesDir() + "/" + account.name; - File rootDir = new File (rootString); - File dirDeleted = new File (rootDir + "/deleted"); - listOfDeleted = dirDeleted.list(); + String rootString = applicationContext.getFilesDir() + "/" + account.accountName; + File rootDir = new File(rootString); + File dirDeleted = new File(rootDir + "/deleted"); + String[] listOfDeleted = dirDeleted.list(); for (String fileDeleted : listOfDeleted) { try { - SyncUtils.DeleteNote(this.res.notesFolder, Integer.parseInt(fileDeleted)); + SyncUtils.DeleteNote(Integer.parseInt(fileDeleted)); } catch (Exception e) { + Log.d(TAG, "DeletNote failed:"); e.printStackTrace(); } // remove file from deleted - File toDelete = new File (dirDeleted, fileDeleted); + File toDelete = new File(dirDeleted, fileDeleted); + //noinspection ResultOfMethodCallIgnored toDelete.delete(); deletedNotesManaged = true; } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncService.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncService.java index 4537d6e1..171bd7f4 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncService.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncService.java @@ -3,22 +3,39 @@ import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.support.annotation.Nullable; import android.util.Log; + +/** + * There can be as many SyncService instances as you like but they all return the same statically + * allocated SyncAdapter. + * TODO: explain why this should be so. I have made it non-static and it still seems to work. + */ public class SyncService extends Service { public static final String SYNC_FINISHED = "SYNC_FINISHED"; private static final String TAG = "SyncService"; private static final Object sSyncAdapterLock = new Object(); - private static SyncAdapter sSyncAdapter = null; + @Nullable + private SyncAdapter sSyncAdapter = null; @Override public void onCreate() { super.onCreate(); Log.d(TAG, "Service created"); + // TODO: fix these comments now thatsSyncAdapter is no longer static. Fix the code too. + // This sync lock is necessary because sSyncAdapter is static so if we have more than one + // SyncService object we would otherwise have a race condition synchronized (sSyncAdapterLock) { + // We check for null because the sync adapter is static and might have been created by + // another SyncService instance. + // TODO: find out if it is possible for there to be more than one SyncService object. + // If there cannot then we need neither the lock nor the null check. + // TODO: find out why we do not do this in the constructor. + // We could then annotate sSyncAdapter as @NonNull. if (sSyncAdapter == null) { - sSyncAdapter = new SyncAdapter(getApplicationContext(), true); + sSyncAdapter = new SyncAdapter(getApplicationContext()); } } } diff --git a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncUtils.java b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncUtils.java index 40dad63f..d015f1a6 100644 --- a/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncUtils.java +++ b/ImapNote2/src/main/java/com/Pau/ImapNotes2/Sync/SyncUtils.java @@ -1,5 +1,23 @@ package com.Pau.ImapNotes2.Sync; +import android.accounts.Account; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.Pau.ImapNotes2.Data.Db; +import com.Pau.ImapNotes2.Data.OneNote; +import com.Pau.ImapNotes2.Data.Security; +import com.Pau.ImapNotes2.Data.Utilities; +import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; +import com.Pau.ImapNotes2.Miscs.Imaper; +import com.Pau.ImapNotes2.Miscs.Sticky; +import com.sun.mail.imap.AppendUID; +import com.sun.mail.imap.IMAPFolder; +import com.sun.mail.util.MailSSLSocketFactory; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -8,9 +26,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.GeneralSecurityException; -import java.text.SimpleDateFormat; +import java.text.DateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.Properties; import java.util.regex.Matcher; @@ -18,490 +35,691 @@ import javax.mail.Flags; import javax.mail.Folder; +import javax.mail.Message; import javax.mail.MessagingException; +import javax.mail.Session; import javax.mail.Store; import javax.mail.UIDFolder; import javax.mail.internet.MimeMessage; -import org.apache.commons.io.FileUtils; +import static com.Pau.ImapNotes2.NoteDetailActivity.Colors; -import javax.mail.Message; -import javax.mail.Session; -import com.Pau.ImapNotes2.Data.NotesDb; -import com.Pau.ImapNotes2.Miscs.ImapNotes2Result; -import com.Pau.ImapNotes2.Miscs.OneNote; -import com.Pau.ImapNotes2.Miscs.Sticky; -import com.sun.mail.util.MailSSLSocketFactory; +//import com.Pau.ImapNotes2.Data.NotesDb; -import com.sun.mail.imap.IMAPStore; -import com.sun.mail.imap.AppendUID; -import com.sun.mail.imap.IMAPFolder; +public class SyncUtils { -import android.accounts.Account; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; + private static Store store; + private static final String TAG = "IN_SyncUtils"; + // TODO: Why do we have two folder fields and why are they both nullable? + @NonNull + private static String sfolder = "Notes"; + @Nullable + private static Folder notesFolder = null; + private static Long UIDValidity; + //private final static int NEW = 1; + //private final static int DELETED = 2; + //private final static int ROOT_AND_NEW = 3; + + @NonNull + static ImapNotes2Result ConnectToRemote(@NonNull String username, + @NonNull String password, + @NonNull String server, + String portnum, + @NonNull Security security, + @NonNull String folderOverride) { + Log.d(TAG,"ConnectToRemote: " + username); + //final ImapNotes2Result res = new ImapNotes2Result(); + if (IsConnected()) { + try { + store.close(); + } catch (MessagingException e) { + // Log the error but do not propagate the exception because the connection is now + // closed even if an exception was thrown. + Log.d(TAG, e.getMessage()); + } + } + + //boolean acceptcrt = security.acceptcrt; + + MailSSLSocketFactory sf; + try { + sf = new MailSSLSocketFactory(); + } catch (GeneralSecurityException e) { + e.printStackTrace(); + return new ImapNotes2Result(Imaper.ResultCodeCantConnect, + "Can't connect to server: " + e.getMessage(), + -1, + null); + //res.errorMessage = "Can't connect to server: " + e.getMessage(); + //res.returnCode = Imaper.ResultCodeCantConnect; + //return res; + } + + Properties props = new Properties(); + + String proto = security.proto; + props.setProperty(String.format("mail.%s.host", proto), server); + props.setProperty(String.format("mail.%s.port", proto), portnum); + props.setProperty("mail.store.protocol", proto); + + if (security.acceptcrt) { + sf.setTrustedHosts(new String[]{server}); + if (proto.equals("imap")) { + props.put("mail.imap.ssl.socketFactory", sf); + props.put("mail.imap.starttls.enable", "true"); + } + } else if (security != Security.None) { + props.put(String.format("mail.%s.ssl.checkserveridentity", proto), "true"); + if (proto.equals("imap")) { + props.put("mail.imap.starttls.enable", "true"); + } + } + + if (proto.equals("imaps")) { + props.put("mail.imaps.socketFactory", sf); + } + + props.setProperty("mail.imap.connectiontimeout", "1000"); + // TODO: use user defined proxy. + Boolean useProxy = false; + //noinspection ConstantConditions + if (useProxy) { + props.put("mail.imap.socks.host", "10.0.2.2"); + props.put("mail.imap.socks.port", "1080"); + } + try { + Session session = Session.getInstance(props, null); +//this.session.setDebug(true); + store = session.getStore(proto); + store.connect(server, username, password); + //res.hasUIDPLUS = ((IMAPStore) store).hasCapability("UIDPLUS"); +//Log.d(TAG, "has UIDPLUS="+res.hasUIDPLUS); + + Folder[] folders = store.getPersonalNamespaces(); + Folder rootFolder = folders[0]; + Log.d(TAG, "Personal Namespaces=" + rootFolder.getFullName()); + // TODO: this the wrong place to make decisions about the name of the notes folder, that + // should be done where it is created. + if (folderOverride.length() > 0) { + sfolder = folderOverride; + } else if (rootFolder.getFullName().length() == 0) { + sfolder = "Notes"; + } else { + char separator = rootFolder.getSeparator(); + sfolder = rootFolder.getFullName() + separator + "Notes"; + } + // Get UIDValidity + notesFolder = store.getFolder(sfolder); + return new ImapNotes2Result(Imaper.ResultCodeSuccess, + "", + ((IMAPFolder) notesFolder).getUIDValidity(), + notesFolder); + //res.UIDValidity = ((IMAPFolder) notesFolder).getUIDValidity(); + //res.errorMessage = ""; + //res.returnCode = Imaper.ResultCodeSuccess; + //res.notesFolder = notesFolder; + //return res; + } catch (Exception e) { + Log.d(TAG, e.getMessage()); + return new ImapNotes2Result(Imaper.ResultCodeException, + e.getMessage(), + -1, + null); + //res.errorMessage = e.getMessage(); + //res.returnCode = Imaper.ResultCodeException; + //return res; + } -public class SyncUtils { - - static Store store; - static Session session; - static final String TAG = "IN_SyncUtils"; - static String proto; - static String acceptcrt; - static String sfolder = "Notes"; - static private String folderoverride; - static Folder notesFolder = null; - static ImapNotes2Result res; - static Long UIDValidity; - private final static int NEW = 1; - private final static int DELETED = 2; - private final static int ROOT_AND_NEW = 3; -private static Boolean useProxy = false; - - public static ImapNotes2Result ConnectToRemote(String username, String password, String server, String portnum, String security, String usesticky, String override) throws MessagingException{ - if (IsConnected()) - store.close(); - - res = new ImapNotes2Result(); - if (override==null) { - folderoverride = ""; - } else { - folderoverride = override; - } - proto = ""; - acceptcrt = ""; - int security_i = Integer.parseInt(security); - switch (security_i) { - case 0: - // None - proto = "imap"; - acceptcrt = ""; - break; - case 1: - // SSL/TLS - proto = "imaps"; - acceptcrt = "false"; - break; - case 2: - // SSL/TLS/TRUST ALL - proto = "imaps"; - acceptcrt = "true"; - break; - case 3: - // STARTTLS - proto = "imap"; - acceptcrt = "false"; - break; - case 4: - // STARTTLS/TRUST ALL - proto = "imap"; - acceptcrt = "true"; - break; -////////////////////// Change this - default: proto = "Invalid proto"; - break; - } - MailSSLSocketFactory sf = null; - try { - sf = new MailSSLSocketFactory(); - } catch (GeneralSecurityException e) { - e.printStackTrace(); - res.errorMessage = "Can't connect to server"; - res.returnCode = -1; - return res; } - Properties props = new Properties(); - - props.setProperty(String.format("mail.%s.host", proto), server); - props.setProperty(String.format("mail.%s.port", proto), portnum); - props.setProperty("mail.store.protocol", proto); - - if ((acceptcrt.equals("true"))) { - sf.setTrustedHosts(new String[] {server}); - if (proto.equals("imap")) { - props.put("mail.imap.ssl.socketFactory", sf); - props.put("mail.imap.starttls.enable", "true"); - } - } else if (acceptcrt.equals("false")) { - props.put(String.format("mail.%s.ssl.checkserveridentity", proto), "true"); - if (proto.equals("imap")) { - props.put("mail.imap.starttls.enable", "true"); - } + /* Copy all notes from the IMAP server to the local directory using the UID as the file name. + + */ + static void GetNotes(@NonNull Account account, + @NonNull Folder imapNotesFolder, + @NonNull Context applicationContext, + @NonNull Db storedNotes) throws MessagingException, IOException { + Log.d(TAG,"GetNotes: " + account.name); + //Long UIDM; + //Message notesMessage; + File directory = new File(applicationContext.getFilesDir(), account.name); + if (imapNotesFolder.isOpen()) { + if ((imapNotesFolder.getMode() & Folder.READ_ONLY) != 0) + imapNotesFolder.open(Folder.READ_ONLY); + } else { + imapNotesFolder.open(Folder.READ_ONLY); + } + UIDValidity = GetUIDValidity(account, applicationContext); + SetUIDValidity(account, UIDValidity, applicationContext); + // From the docs: "Folder implementations are expected to provide light-weight Message + // objects, which get filled on demand. " + // This means that at this point we can ask for the subject without getting the rest of the + // message. + Message[] notesMessages = imapNotesFolder.getMessages(); + //Log.d(TAG,"number of messages in folder="+(notesMessages.length)); + // TODO: explain why we enumerate the messages in descending order of index. + for (int index = notesMessages.length - 1; index >= 0; index--) { + Message notesMessage = notesMessages[index]; + // write every message in files/{accountname} directory + // filename is the original message uid + Long UIDM = ((IMAPFolder) imapNotesFolder).getUID(notesMessage); + String suid = UIDM.toString(); + File outfile = new File(directory, suid); + SaveNoteAndUpdatDatabase(outfile, notesMessage, storedNotes, account.name, suid); + } } - if (proto.equals("imaps")) { - props.put("mail.imaps.socketFactory", sf); + private static final Pattern patternColor = Pattern.compile("^COLOR:(.*?)$", Pattern.MULTILINE); + // --Commented out by Inspection (12/2/16 8:50 PM):private static final Pattern patternPosition = Pattern.compile("^POSITION:(.*?)$", Pattern.MULTILINE); + private static final Pattern patternText = Pattern.compile("TEXT:(.*?)(END:|POSITION:)", Pattern.DOTALL); + + @NonNull + public static Sticky ReadStickyNote(@NonNull String stringres) { + Log.d(TAG,"ReadStickyNote"); +/* + Matcher matcherColor = patternColor.matcher(stringres); + Colors color = Colors.NONE; + if (matcherColor.find()) { + String colorName = matcherColor.group(1); + color = ((colorName == null) || colorName.equals("null")) ? + Colors.NONE : + Colors.valueOf(colorName); + } else { + color = Colors.NONE; + }*/ +/* + + Matcher matcherPosition = patternPosition.matcher(stringres); + String position = matcherPosition.find() ? + matcherPosition.group(1) : + ""; +*/ + + /*Matcher matcherText = patternText.matcher(stringres); + String text = ""; + if (matcherText.find()) { + text = matcherText.group(1); + // Kerio Connect puts CR+LF+space every 78 characters from line 2 + // first line seem to be smaller. We remove these characters + text = text.replaceAll("\r\n ", ""); + // newline in Kerio is the string (not the character) "\n" + text = text.replaceAll("\\\\n", "
"); + } + */ + return new Sticky( + getText(stringres), + getColor(stringres)); } +/* + private static String getPosition(String stringres) { + + Matcher matcherPosition = patternPosition.matcher(stringres); + return matcherPosition.find() ? + matcherPosition.group(1) : + ""; + }*/ + + private static String getText(@NonNull String stringres) { + Matcher matcherText = patternText.matcher(stringres); + String text = ""; + if (matcherText.find()) { + text = matcherText.group(1); + // Kerio Connect puts CR+LF+space every 78 characters from line 2 + // first line seem to be smaller. We remove these characters + text = text.replaceAll("\r\n ", ""); + // newline in Kerio is the string (not the character) "\n" + text = text.replaceAll("\\\\n", "
"); + } + return text; + } + + @NonNull + private static Colors getColor(@NonNull String stringres) { + Matcher matcherColor = patternColor.matcher(stringres); + if (matcherColor.find()) { + String colorName = matcherColor.group(1); + return ((colorName == null) || colorName.equals("null")) ? + Colors.NONE : + Colors.valueOf(colorName); + } else { + return Colors.NONE; + } - props.setProperty("mail.imap.connectiontimeout","1000"); - if (useProxy) { - props.put("mail.imap.socks.host","10.0.2.2"); - props.put("mail.imap.socks.port","1080"); } - session = Session.getInstance(props, null); -//this.session.setDebug(true); - store = session.getStore(proto); - try { - store.connect(server, username, password); - res.hasUIDPLUS = ((IMAPStore) store).hasCapability("UIDPLUS"); -//Log.d(TAG, "has UIDPLUS="+res.hasUIDPLUS); - Folder[] folders = store.getPersonalNamespaces(); - Folder folder = folders[0]; -//Log.d(TAG,"Personal Namespaces="+folder.getFullName()); - if (folderoverride.length() > 0) { - sfolder = folderoverride; - } else if (folder.getFullName().length() == 0) { - sfolder = "Notes"; - } else { - char separator = folder.getSeparator(); - sfolder = folder.getFullName() + separator + "Notes"; - } - // Get UIDValidity - notesFolder = store.getFolder(sfolder); - res.UIDValidity = ((IMAPFolder) notesFolder).getUIDValidity(); - res.errorMessage = ""; - res.returnCode = 0; - res.notesFolder = notesFolder; - return res; - } catch (Exception e) { - Log.d(TAG, e.getMessage()); - res.errorMessage = e.getMessage(); - res.returnCode = -2; - return res; + private static boolean IsConnected() { + return store != null && store.isConnected(); + } + + static void DeleteNote(int numMessage) throws MessagingException { + Log.d(TAG,"DeleteNote: " + numMessage); + Folder notesFolder = store.getFolder(sfolder); + if (notesFolder.isOpen()) { + if ((notesFolder.getMode() & Folder.READ_WRITE) != 0) { + notesFolder.open(Folder.READ_WRITE); + } + } else { + notesFolder.open(Folder.READ_WRITE); + } + + //Log.d(TAG,"UID to remove:"+numMessage); + Message[] msgs = {((IMAPFolder) notesFolder).getMessageByUID(numMessage)}; + notesFolder.setFlags(msgs, new Flags(Flags.Flag.DELETED), true); + ((IMAPFolder) notesFolder).expunge(msgs); + } + + // Put values in shared preferences + static void SetUIDValidity(@NonNull Account account, + Long UIDValidity, + @NonNull Context ctx) { + Log.d(TAG,"SetUIDValidity: " + account.name); + SharedPreferences preferences = ctx.getSharedPreferences(account.name, Context.MODE_MULTI_PROCESS); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("Name", "valid_data"); + //Log.d(TAG, "UIDValidity set to in shared_prefs:"+UIDValidity); + editor.putLong("UIDValidity", UIDValidity); + editor.apply(); } - } - - public static void GetNotes(Account account, Folder notesFolder, Context ctx, NotesDb storedNotes) throws MessagingException, IOException{ - Long UIDM; - Message notesMessage; - File directory = new File (ctx.getFilesDir() + "/" + account.name); - if (notesFolder.isOpen()) { - if ((notesFolder.getMode() & Folder.READ_ONLY) != 0) - notesFolder.open(Folder.READ_ONLY); - } else { - notesFolder.open(Folder.READ_ONLY); + // Retrieve values from shared preferences: + static Long GetUIDValidity(@NonNull Account account, + @NonNull Context ctx) { + Log.d(TAG,"GetUIDValidity: " + account.name); + UIDValidity = (long) -1; + SharedPreferences preferences = ctx.getSharedPreferences(account.name, Context.MODE_MULTI_PROCESS); + String name = preferences.getString("Name", ""); + if (!name.equalsIgnoreCase("")) { + UIDValidity = preferences.getLong("UIDValidity", -1); + //Log.d(TAG, "UIDValidity got from shared_prefs:"+UIDValidity); + } + return UIDValidity; } - UIDValidity = GetUIDValidity(account, ctx); - SetUIDValidity(account, UIDValidity, ctx); - Message[] notesMessages = notesFolder.getMessages(); - //Log.d(TAG,"number of messages in folder="+(notesMessages.length)); - for(int index=notesMessages.length-1; index>=0; index--) { - notesMessage = notesMessages[index]; - // write every message in files/{accountname} directory - // filename is the original message uid - UIDM=((IMAPFolder)notesFolder).getUID(notesMessage); - String suid = UIDM.toString(); - File outfile = new File (directory, suid); - GetOneNote(outfile, notesMessage, storedNotes, account.name, suid, true); + + static void DisconnectFromRemote() { + Log.d(TAG,"DisconnectFromRemote"); + try { + store.close(); + } catch (MessagingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - } - - public static Sticky ReadStickynote(String stringres) { - String color=new String(""); - String position=new String(""); - String text=new String(""); - Pattern p = null; - Matcher m = null; - - p = Pattern.compile("^COLOR:(.*?)$",Pattern.MULTILINE); - m = p.matcher(stringres); - if (m.find()) { color = m.group(1); } - - p = Pattern.compile("^POSITION:(.*?)$",Pattern.MULTILINE); - m = p.matcher(stringres); - if (m.find()) { position = m.group(1); } - - p = Pattern.compile("TEXT:(.*?)(END:|POSITION:)",Pattern.DOTALL); - m = p.matcher(stringres); - if (m.find()) { - text = m.group(1); - // Kerio Connect puts CR+LF+space every 78 characters from line 2 - // first line seem to be smaller. We remove these characters - text = text.replaceAll("\r\n ", ""); - // newline in Kerio is the string (not the character) "\n" - text = text.replaceAll("\\\\n", "
"); +/* + + */ +/** + * @param uid ID of the message as created by the IMAP server + * @param where TODO: what is this? + * @param removeMinus TODO: Why? + * @param nameDir Name of the account with which this message is associated, used to create the + * directory in which to store it. + * @return A Java mail message object. + *//* + + @Nullable + public static Message ReadMailFromFile(@NonNull String uid, + Where where, + boolean removeMinus, + @NonNull String nameDir) { + File mailFile; + Message message = null; + mailFile = new File(nameDir, uid); + + switch (where) { + case NEW: + nameDir = nameDir + "/new"; + if (removeMinus) uid = uid.substring(1); + break; + case DELETED: + nameDir = nameDir + "/deleted"; + break; + case ROOT_AND_NEW: + if (!mailFile.exists()) { + nameDir = nameDir + "/new"; + if (removeMinus) uid = uid.substring(1); + } + break; + default: + throw new UnsupportedOperationException("Unrecognized value for where argument: " + where); + } + + mailFile = new File(nameDir, uid); + InputStream mailFileInputStream = null; + try { + mailFileInputStream = new FileInputStream(mailFile); + } catch (FileNotFoundException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + try { + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + message = new MimeMessage(session, mailFileInputStream); + } catch (MessagingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return message; } - return new Sticky(text, position, color); - } - - public static boolean IsConnected(){ - return store!=null && store.isConnected(); - } - - public static void DeleteNote(Folder notesFolder, int numMessage) throws MessagingException, IOException { - notesFolder = store.getFolder(sfolder); - if (notesFolder.isOpen()) { - if ((notesFolder.getMode() & Folder.READ_WRITE) != 0) - notesFolder.open(Folder.READ_WRITE); - } else { - notesFolder.open(Folder.READ_WRITE); +*/ + + /** + * @param uid ID of the message as created by the IMAP server + * @param newFilesDir Directory in which it is stored. + * @return A Java mail message object. + */ + @Nullable + static Message ReadMailFromFileNew(@NonNull String uid, + @NonNull File newFilesDir) { + Log.d(TAG,"ReadMailFromFileNew"); + //File mailFile; + //Message message = null; + //mailFile = new File(nameDir, uid); + + return ReadMailFromFile(newFilesDir, uid); } - - //Log.d(TAG,"UID to remove:"+numMessage); - Message[] msgs = {((IMAPFolder)notesFolder).getMessageByUID(numMessage)}; - notesFolder.setFlags(msgs, new Flags(Flags.Flag.DELETED), true); - ((IMAPFolder)notesFolder).expunge(msgs); - } - - // Put values in shared preferences - public static void SetUIDValidity(Account account, Long UIDValidity, Context ctx) { - SharedPreferences preferences = ctx.getSharedPreferences(account.name, Context.MODE_MULTI_PROCESS); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString("Name","valid_data"); - //Log.d(TAG, "UIDValidity set to in shared_prefs:"+UIDValidity); - editor.putLong("UIDValidity", UIDValidity); - editor.apply(); - } - - // Retrieve values from shared preferences: - public static Long GetUIDValidity(Account account, Context ctx) { - UIDValidity = (long) -1; - SharedPreferences preferences = ctx.getSharedPreferences(account.name, Context.MODE_MULTI_PROCESS); - String name = preferences.getString("Name", ""); - if(!name.equalsIgnoreCase("")) { - UIDValidity = preferences.getLong("UIDValidity", -1); - //Log.d(TAG, "UIDValidity got from shared_prefs:"+UIDValidity); + + /** + * @param uid ID of the message as created by the IMAP server + * @param fileDir Name of the account with which this message is associated, used to find the + * directory in which it is stored. + * @return A Java mail message object. + */ + @Nullable + public static Message ReadMailFromFileRootAndNew(@NonNull String uid, + @NonNull File fileDir) { + Log.d(TAG,"ReadMailFromFileRootAndNew: " + fileDir.getPath() + " " + uid); + //File mailFile; + //Message message = null; + File mailFile = new File(fileDir, uid); + + if (!mailFile.exists()) { + fileDir = new File(fileDir, "new"); + uid = uid.substring(1); + } + + return ReadMailFromFile(fileDir, uid); } - return UIDValidity; - } - - public static void DisconnectFromRemote() { - try { - store.close(); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + +// --Commented out by Inspection START (11/26/16 11:46 PM): +// /** +// * @param uid ID of the message as created by the IMAP server +// * @param nameDir Name of the account with which this message is associated, used to find the +// * directory in which it is stored. +// * @return A Java mail message object. +// */ +// @Nullable +// public static Message ReadMailFromFileDeleted(@NonNull String uid, +// @NonNull String nameDir) { +// return ReadMailFromFile(new File(nameDir, "deleted"), uid); +// } +// --Commented out by Inspection STOP (11/26/16 11:46 PM) + + + /** + * @param uid ID of the message as created by the IMAP server + * @param nameDir Name of the account with which this message is associated, used to find the + * directory in which it is stored. + * @return A Java mail message object. + */ + @Nullable + private static Message ReadMailFromFile(@NonNull File nameDir, + @NonNull String uid) { + Log.d(TAG,"ReadMailFromFile: " + nameDir.getPath() + " " + uid); + File mailFile = new File(nameDir, uid); + + try (InputStream mailFileInputStream = new FileInputStream(mailFile)) { + try { + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + Log.d(TAG, "ReadMailFromFile return new MimeMessage."); + return new MimeMessage(session, mailFileInputStream); + } catch (MessagingException e) { + // TODO Auto-generated catch block + Log.d(TAG, "Exception getting MimeMessage."); + e.printStackTrace(); + } + } catch (FileNotFoundException e1) { + // TODO Auto-generated catch block + Log.d(TAG, "File not found opening mailFile: " + mailFile.getAbsolutePath()); + e1.printStackTrace(); + } catch (IOException exIO) { + //TODO: handle this properly + Log.d(TAG, "IO exception opening mailFile: " + mailFile.getAbsolutePath()); + exIO.printStackTrace(); + } + Log.d(TAG,"ReadMailFromFile return null."); + return null; } - } - - public static Message ReadMailFromFile (String uid, int where, boolean removeMinus, String nameDir) { - File mailFile; - Message message = null; - mailFile = new File (nameDir,uid); - - switch (where){ - case NEW: - nameDir = nameDir + "/new"; - if (removeMinus) uid = uid.substring(1); - break; - case DELETED: - nameDir = nameDir + "/deleted"; - break; - case ROOT_AND_NEW: - if (!mailFile.exists()) { - nameDir = nameDir + "/new"; - if (removeMinus) uid = uid.substring(1); - } - break; - default: - break; - } - - mailFile = new File (nameDir,uid); - InputStream mailFileInputStream = null; - try { - mailFileInputStream = new FileInputStream(mailFile); - } catch (FileNotFoundException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - try { - Properties props = new Properties(); - Session session = Session.getDefaultInstance(props, null); - message = new MimeMessage(session, mailFileInputStream); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return message; - } - - public static AppendUID[] sendMessageToRemote (Message[] message) throws MessagingException, IOException { - notesFolder = store.getFolder(sfolder); - if (notesFolder.isOpen()) { - if ((notesFolder.getMode() & Folder.READ_WRITE) != 0) - notesFolder.open(Folder.READ_WRITE); - } else { - notesFolder.open(Folder.READ_WRITE); + + static AppendUID[] sendMessageToRemote(@NonNull Message[] message) throws MessagingException { + notesFolder = store.getFolder(sfolder); + if (notesFolder.isOpen()) { + if ((notesFolder.getMode() & Folder.READ_WRITE) != 0) + notesFolder.open(Folder.READ_WRITE); + } else { + notesFolder.open(Folder.READ_WRITE); + } + return ((IMAPFolder) notesFolder).appendUIDMessages(message); } - AppendUID[] uids = ((IMAPFolder) notesFolder).appendUIDMessages(message); - return uids; - } - - public static void ClearHomeDir(Account account, Context ctx) { - File directory = new File (ctx.getFilesDir() + "/" + account.name); - try { - FileUtils.deleteDirectory(directory); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + +// --Commented out by Inspection START (12/3/16 11:31 PM): +// static void ClearHomeDir(@NonNull Account account, @NonNull Context ctx) { +// File directory = new File(ctx.getFilesDir() + "/" + account.name); +// try { +// FileUtils.deleteDirectory(directory); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// --Commented out by Inspection STOP (12/3/16 11:31 PM) + + /** + * Do we really need the Context argument or could we call getApplicationContext instead? + * + * @param accountName Name of the account as defined by the user, this is not the email address. + * @param applicationContext Global context not an activity context. + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + public static void CreateLocalDirectories(@NonNull String accountName, @NonNull Context applicationContext) { + Log.d(TAG, "CreateDirs(String: " + accountName); + File dir = new File(applicationContext.getFilesDir(), accountName); + //File directory = new File(stringDir); + //directory.mkdirs(); + (new File(dir, "new")).mkdirs(); + (new File(dir, "deleted")).mkdirs(); } - } - - public static void CreateDirs (String accountName, Context ctx) { - String stringDir = ctx.getFilesDir() + "/" + accountName; - File directory = new File (stringDir); - directory.mkdirs(); - directory = new File (stringDir + "/new"); - directory.mkdirs(); - directory = new File (stringDir + "/deleted"); - directory.mkdirs(); - } - - public static void GetOneNote(File outfile, Message notesMessage, NotesDb storedNotes, String accountName, String suid, boolean updateDb) { - OutputStream str=null; - - try { - str = new FileOutputStream(outfile); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - try { - notesMessage.writeTo(str); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - if (!(updateDb)) return; - - String title = null; - String[] rawvalue = null; - // Some servers (such as posteo.de) don't encode non us-ascii characters in subject - // This is a workaround to handle them - // "lä ö ë" subject should be stored as =?charset?encoding?encoded-text?= - // either =?utf-8?B?bMOkIMO2IMOr?= -> Quoted printable - // or =?utf-8?Q?l=C3=A4 =C3=B6 =C3=AB?= -> Base64 - try { rawvalue = notesMessage.getHeader("Subject"); } catch (Exception e) {e.printStackTrace(); }; - try { title = notesMessage.getSubject(); } catch (Exception e) {e.printStackTrace();} - if (rawvalue[0].length() >= 2) { - if (!(rawvalue[0].substring(0,2).equals("=?"))) { - try { title = new String ( title.getBytes("ISO-8859-1")); } catch (Exception e) {e.printStackTrace();} - } - } else { - try { title = new String ( title.getBytes("ISO-8859-1")); } catch (Exception e) {e.printStackTrace();} - } - - // Get INTERNALDATE - String internaldate = null; - Date MessageInternaldate = null; - try { - MessageInternaldate = notesMessage.getReceivedDate(); - } catch (MessagingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - String DATE_FORMAT = "yyyy-MM-dd HH:MM:ss"; - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); - internaldate = sdf.format(MessageInternaldate); - - OneNote aNote = new OneNote( - title, - internaldate, - suid); - storedNotes.InsertANoteInDb(aNote, accountName); - } - - public static boolean handleRemoteNotes (Context context, Folder notesFolder, NotesDb storedNotes, String accountName, String usesticky) - throws MessagingException, IOException { - - Message notesMessage; - boolean result = false; - ArrayList uids = new ArrayList(); - ArrayList localListOfNotes = new ArrayList(); - String remoteInternaldate; - String localInternaldate; - Flags flags; - Boolean deleted; - - if (notesFolder.isOpen()) { - if ((notesFolder.getMode() & Folder.READ_ONLY) != 0) - notesFolder.open(Folder.READ_WRITE); - } else { - notesFolder.open(Folder.READ_WRITE); + + + /** + * @param outfile Name of local file in which to store the note. + * @param notesMessage The note in the form of a mail message. + */ + private static void SaveNote(@NonNull File outfile, + @NonNull Message notesMessage) throws IOException, MessagingException { + + Log.d(TAG,"SaveNote: " + outfile.getCanonicalPath()); + try (OutputStream str = new FileOutputStream(outfile)) { + notesMessage.writeTo(str); +/* + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (MessagingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); +*/ + } + } - // Get local list of notes uids - String rootString = context.getFilesDir() + "/" + accountName; - File rootDir = new File (rootString); - File[] files = rootDir.listFiles(); - for (File file : files) { - if (file.isFile()) { - localListOfNotes.add(file.getName()); + private static void SaveNoteAndUpdatDatabase(@NonNull File outfile, + @NonNull Message notesMessage, + @NonNull Db storedNotes, + @NonNull String accountName, + @NonNull String suid) throws IOException, MessagingException { + Log.d(TAG,"SaveNoteAndUpdatDatabase: " + outfile.getCanonicalPath() + " " + accountName); + + SaveNote(outfile, notesMessage); + + // Now update or save the metadata about the message + + String title = null; + String[] rawvalue = null; + // Some servers (such as posteo.de) don't encode non us-ascii characters in subject + // This is a workaround to handle them + // "lä ö ë" subject should be stored as =?charset?encoding?encoded-text?= + // either =?utf-8?B?bMOkIMO2IMOr?= -> Quoted printable + // or =?utf-8?Q?l=C3=A4 =C3=B6 =C3=AB?= -> Base64 + try { + rawvalue = notesMessage.getHeader("Subject"); + } catch (Exception e) { + e.printStackTrace(); + } + try { + title = notesMessage.getSubject(); + } catch (Exception e) { + e.printStackTrace(); + } + if (rawvalue[0].length() >= 2) { + if (!(rawvalue[0].substring(0, 2).equals("=?"))) { + try { + title = new String(title.getBytes("ISO-8859-1")); + } catch (Exception e) { + e.printStackTrace(); + } + } + } else { + try { + title = new String(title.getBytes("ISO-8859-1")); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Get INTERNALDATE + //String internaldate = null; + Date MessageInternaldate = null; + try { + MessageInternaldate = notesMessage.getReceivedDate(); + } catch (MessagingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } + //String DATE_FORMAT = "yyyy-MM-dd HH:MM:ss"; + //SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.ROOT); + String internaldate = Utilities.internalDateFormat.format(MessageInternaldate); + + OneNote aNote = new OneNote( + title, + internaldate, + suid); + storedNotes.notes.InsertANoteInDb(aNote, accountName); } - // Add to local device, new notes added to remote - Message[] notesMessages = ((IMAPFolder)notesFolder).getMessagesByUID(1, UIDFolder.LASTUID); - for(int index=notesMessages.length-1; index>=0; index--) { - notesMessage = notesMessages[index]; - Long uid = ((IMAPFolder)notesFolder).getUID(notesMessage); - // Get FLAGS - flags = notesMessage.getFlags(); - deleted = notesMessage.isSet(Flags.Flag.DELETED); - // Buils remote list while in the loop, but only if not deleted on remote - if (!deleted) { - uids.add(((IMAPFolder)notesFolder).getUID(notesMessage)); + static boolean handleRemoteNotes(@NonNull Context context, + @NonNull javax.mail.Folder remoteIMAPNotesFolder, + @NonNull Db storedNotes, + @NonNull String accountName, + @NonNull String usesticky) + throws MessagingException, IOException { + Log.d(TAG,"handleRemoteNotes: " + remoteIMAPNotesFolder.getFullName() + " " + accountName + " " + usesticky); + + Message notesMessage; + boolean result = false; + ArrayList uids = new ArrayList<>(); + ArrayList localListOfNotes = new ArrayList<>(); + String remoteInternaldate; + String localInternaldate; + //Flags flags; + + if (remoteIMAPNotesFolder.isOpen()) { + if ((remoteIMAPNotesFolder.getMode() & Folder.READ_ONLY) != 0) + remoteIMAPNotesFolder.open(Folder.READ_WRITE); + } else { + remoteIMAPNotesFolder.open(Folder.READ_WRITE); + } + + // Get local list of notes uids + String rootString = context.getFilesDir() + "/" + accountName; + File rootDir = new File(rootString); + File[] files = rootDir.listFiles(); + for (File file : files) { + if (file.isFile()) { + localListOfNotes.add(file.getName()); + } } - String suid = uid.toString(); - if (!(localListOfNotes.contains(suid))) { - File outfile = new File (rootDir, suid); - GetOneNote(outfile, notesMessage, storedNotes, accountName, suid, true); - result = true; - } else if (usesticky.equals("true")) { - //Log.d (TAG,"MANAGE STICKY"); - remoteInternaldate = notesMessage.getSentDate().toLocaleString(); - localInternaldate = storedNotes.GetDate(suid, accountName); - if (!(remoteInternaldate.equals(localInternaldate))) { - File outfile = new File (rootDir, suid); - GetOneNote(outfile, notesMessage, storedNotes, accountName, suid, false); + + // Add to local device, new notes added to remote + Message[] notesMessages = ((IMAPFolder) remoteIMAPNotesFolder).getMessagesByUID(1, UIDFolder.LASTUID); + for (int index = notesMessages.length - 1; index >= 0; index--) { + notesMessage = notesMessages[index]; + Long uid = ((IMAPFolder) remoteIMAPNotesFolder).getUID(notesMessage); + // Get FLAGS + //flags = notesMessage.getFlags(); + boolean deleted = notesMessage.isSet(Flags.Flag.DELETED); + // Builds remote list while in the loop, but only if not deleted on remote + if (!deleted) { + uids.add(((IMAPFolder) remoteIMAPNotesFolder).getUID(notesMessage)); + } + String suid = uid.toString(); + if (!(localListOfNotes.contains(suid))) { + File outfile = new File(rootDir, suid); + SaveNoteAndUpdatDatabase(outfile, notesMessage, storedNotes, accountName, suid); result = true; + } else if (usesticky.equals("true")) { + //Log.d (TAG,"MANAGE STICKY"); + remoteInternaldate = DateFormat.getDateInstance().format(notesMessage.getSentDate()); + localInternaldate = storedNotes.notes.GetDate(suid, accountName); + if (!(remoteInternaldate.equals(localInternaldate))) { + File outfile = new File(rootDir, suid); + SaveNote(outfile, notesMessage); + result = true; + } } } - } - // Remove from local device, notes removed from remote - for(String suid : localListOfNotes) { - int uid = Integer.valueOf(suid); - if (!(uids.contains(new Long(uid)))) { - // remove file from deleted - File toDelete = new File (rootDir, suid); - toDelete.delete(); - // Remove note from database - storedNotes.DeleteANote(suid, accountName); - result = true; + // Remove from local device, notes removed from remote + for (String suid : localListOfNotes) { + Long uid = Long.valueOf(suid); + if (!(uids.contains(uid))) { + // remove file from deleted + File toDelete = new File(rootDir, suid); + //noinspection ResultOfMethodCallIgnored + toDelete.delete(); + // Remove note from database + storedNotes.notes.DeleteANote(suid, accountName); + result = true; + } } - } - return result; - } + return result; + } - public static void RemoveAccount(Context context, Account account) { + static void RemoveAccount(@NonNull Context context, @NonNull Account account) { + Log.d(TAG,"RemoveAccount: " + account.name); // remove Shared Preference file String rootString = context.getFilesDir().getParent() + - File.separator + "shared_prefs"; - File rootDir = new File (rootString); - File toDelete = new File (rootDir, account.name + ".xml"); + File.separator + "shared_prefs"; + File rootDir = new File(rootString); + File toDelete = new File(rootDir, account.name + ".xml"); + //noinspection ResultOfMethodCallIgnored toDelete.delete(); // Remove all files and sub directories File filesDir = context.getFilesDir(); File[] files = filesDir.listFiles(); - for (int i = 0; i < files.length; i++) { - files[i].delete(); + for (File file : files) { + //noinspection ResultOfMethodCallIgnored + file.delete(); } // Delete account name entries in database - NotesDb storedNotes = new NotesDb(context); + Db storedNotes = new Db(context); storedNotes.OpenDb(); - storedNotes.ClearDb(account.name); + storedNotes.notes.ClearDb(account.name); storedNotes.CloseDb(); } } diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/bg.png b/ImapNote2/src/main/res/drawable-xxhdpi/bg.png new file mode 100644 index 00000000..edd0fa38 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/bg.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/bg_color.png b/ImapNote2/src/main/res/drawable-xxhdpi/bg_color.png new file mode 100755 index 00000000..c6bd0ef6 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/bg_color.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/blockquote.png b/ImapNote2/src/main/res/drawable-xxhdpi/blockquote.png new file mode 100644 index 00000000..638a40cd Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/blockquote.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/bold.png b/ImapNote2/src/main/res/drawable-xxhdpi/bold.png new file mode 100755 index 00000000..7184e00b Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/bold.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/bullets.png b/ImapNote2/src/main/res/drawable-xxhdpi/bullets.png new file mode 100644 index 00000000..91a0fc02 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/bullets.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/h1.png b/ImapNote2/src/main/res/drawable-xxhdpi/h1.png new file mode 100644 index 00000000..774786c6 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/h1.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/h2.png b/ImapNote2/src/main/res/drawable-xxhdpi/h2.png new file mode 100644 index 00000000..3715b7a7 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/h2.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/h3.png b/ImapNote2/src/main/res/drawable-xxhdpi/h3.png new file mode 100644 index 00000000..4a8915cb Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/h3.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/h4.png b/ImapNote2/src/main/res/drawable-xxhdpi/h4.png new file mode 100644 index 00000000..67814476 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/h4.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/h5.png b/ImapNote2/src/main/res/drawable-xxhdpi/h5.png new file mode 100644 index 00000000..682a91d5 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/h5.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/h6.png b/ImapNote2/src/main/res/drawable-xxhdpi/h6.png new file mode 100644 index 00000000..dee7ff49 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/h6.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/html_edit.png b/ImapNote2/src/main/res/drawable-xxhdpi/html_edit.png new file mode 100755 index 00000000..3bbdedb6 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/html_edit.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/indent.png b/ImapNote2/src/main/res/drawable-xxhdpi/indent.png new file mode 100755 index 00000000..77efb718 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/indent.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/insert_image.png b/ImapNote2/src/main/res/drawable-xxhdpi/insert_image.png new file mode 100755 index 00000000..3c888424 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/insert_image.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/insert_link.png b/ImapNote2/src/main/res/drawable-xxhdpi/insert_link.png new file mode 100755 index 00000000..e86e04c7 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/insert_link.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/italic.png b/ImapNote2/src/main/res/drawable-xxhdpi/italic.png new file mode 100755 index 00000000..f1a13636 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/italic.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/justify_center.png b/ImapNote2/src/main/res/drawable-xxhdpi/justify_center.png new file mode 100755 index 00000000..450ce245 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/justify_center.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/justify_left.png b/ImapNote2/src/main/res/drawable-xxhdpi/justify_left.png new file mode 100755 index 00000000..5125d0e8 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/justify_left.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/justify_right.png b/ImapNote2/src/main/res/drawable-xxhdpi/justify_right.png new file mode 100755 index 00000000..47be92be Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/justify_right.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/numbers.png b/ImapNote2/src/main/res/drawable-xxhdpi/numbers.png new file mode 100644 index 00000000..44d388e7 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/numbers.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/outdent.png b/ImapNote2/src/main/res/drawable-xxhdpi/outdent.png new file mode 100755 index 00000000..3a953ce1 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/outdent.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/redo.png b/ImapNote2/src/main/res/drawable-xxhdpi/redo.png new file mode 100644 index 00000000..eb9bf475 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/redo.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/strikethrough.png b/ImapNote2/src/main/res/drawable-xxhdpi/strikethrough.png new file mode 100755 index 00000000..7f3e4649 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/strikethrough.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/subscript.png b/ImapNote2/src/main/res/drawable-xxhdpi/subscript.png new file mode 100755 index 00000000..34d4c575 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/subscript.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/superscript.png b/ImapNote2/src/main/res/drawable-xxhdpi/superscript.png new file mode 100755 index 00000000..0ad35765 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/superscript.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/txt_color.png b/ImapNote2/src/main/res/drawable-xxhdpi/txt_color.png new file mode 100755 index 00000000..7d0e2ebc Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/txt_color.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/underline.png b/ImapNote2/src/main/res/drawable-xxhdpi/underline.png new file mode 100755 index 00000000..24d55a49 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/underline.png differ diff --git a/ImapNote2/src/main/res/drawable-xxhdpi/undo.png b/ImapNote2/src/main/res/drawable-xxhdpi/undo.png new file mode 100644 index 00000000..9fa457a7 Binary files /dev/null and b/ImapNote2/src/main/res/drawable-xxhdpi/undo.png differ diff --git a/ImapNote2/src/main/res/layout/account_selection.xml b/ImapNote2/src/main/res/layout/account_selection.xml index f340f988..b9dc1a9c 100644 --- a/ImapNote2/src/main/res/layout/account_selection.xml +++ b/ImapNote2/src/main/res/layout/account_selection.xml @@ -1,193 +1,197 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="fill_parent" + android:orientation="vertical"> + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/ImapNote2/src/main/res/layout/main.xml b/ImapNote2/src/main/res/layout/main.xml index 5104500c..f5a09275 100644 --- a/ImapNote2/src/main/res/layout/main.xml +++ b/ImapNote2/src/main/res/layout/main.xml @@ -1,7 +1,8 @@ + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> @@ -19,15 +20,15 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@color/ListColor" - android:text="Select account" + android:text="@string/select_account" android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="#000000" > + android:textColor="#000000"> + android:layout_height="wrap_content"> - + android:layout_height="fill_parent" /> diff --git a/ImapNote2/src/main/res/layout/new_note.xml b/ImapNote2/src/main/res/layout/new_note.xml index 12b5bb1e..87209423 100644 --- a/ImapNote2/src/main/res/layout/new_note.xml +++ b/ImapNote2/src/main/res/layout/new_note.xml @@ -1,26 +1,28 @@ + + + android:layout_height="fill_parent"> + + android:orientation="vertical"> - + android:ems="10" + android:inputType="textMultiLine" + tools:ignore="LabelFor"> + - - - diff --git a/ImapNote2/src/main/res/layout/note_detail.xml b/ImapNote2/src/main/res/layout/note_detail.xml index b6e0d9a6..7309d3e1 100644 --- a/ImapNote2/src/main/res/layout/note_detail.xml +++ b/ImapNote2/src/main/res/layout/note_detail.xml @@ -1,20 +1,298 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImapNote2/src/main/res/layout/note_element.xml b/ImapNote2/src/main/res/layout/note_element.xml index c43c4a1f..dea1fa4a 100644 --- a/ImapNote2/src/main/res/layout/note_element.xml +++ b/ImapNote2/src/main/res/layout/note_element.xml @@ -1,31 +1,33 @@ - - + + + + + + android:scrollHorizontally="false" + android:maxLines="1"> - - + + diff --git a/ImapNote2/src/main/res/layout/rich_editor.xml b/ImapNote2/src/main/res/layout/rich_editor.xml new file mode 100644 index 00000000..7b5a8ee7 --- /dev/null +++ b/ImapNote2/src/main/res/layout/rich_editor.xml @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImapNote2/src/main/res/menu/detail.xml b/ImapNote2/src/main/res/menu/detail.xml index 15b0e6a9..11fe47b4 100644 --- a/ImapNote2/src/main/res/menu/detail.xml +++ b/ImapNote2/src/main/res/menu/detail.xml @@ -1,35 +1,47 @@ -

+ - + android:showAsAction="ifRoom" + android:title="@string/save"> + - + - + android:showAsAction="ifRoom" + android:title="@string/delete"> + - - - - - - - - - - + android:showAsAction="ifRoom" + android:title="@string/color"> + + + + + + + + + - + diff --git a/ImapNote2/src/main/res/menu/list.xml b/ImapNote2/src/main/res/menu/list.xml index 69f0757d..df41ffeb 100644 --- a/ImapNote2/src/main/res/menu/list.xml +++ b/ImapNote2/src/main/res/menu/list.xml @@ -1,39 +1,34 @@ - + - - + android:showAsAction="ifRoom" + android:title="@string/account" /> + - - + android:showAsAction="ifRoom" + android:title="@string/titleNew" /> + - - + android:showAsAction="ifRoom" + android:title="@string/refresh" /> + - - + android:title="@string/search" /> + - - + android:showAsAction="ifRoom" + android:title="@string/about" /> + diff --git a/ImapNote2/src/main/res/menu/newnote.xml b/ImapNote2/src/main/res/menu/newnote.xml index 16db92cd..d5be148c 100644 --- a/ImapNote2/src/main/res/menu/newnote.xml +++ b/ImapNote2/src/main/res/menu/newnote.xml @@ -1,10 +1,10 @@ - + - + android:showAsAction="ifRoom" + android:title="@string/save"> + diff --git a/ImapNote2/src/main/res/values-nb/strings.xml b/ImapNote2/src/main/res/values-nb/strings.xml new file mode 100644 index 00000000..30993e34 --- /dev/null +++ b/ImapNote2/src/main/res/values-nb/strings.xml @@ -0,0 +1,29 @@ + + + ImapNotes2k + Slette + Grøṇn + Tittel + Passord + Fjerne + Lagre + søḳ + Velg konto til aa se eller rediger + Ny + Synkronisering.. + Brukernavn + Hvit + Gul + Ditt innloggingsnavn + Passordet ditt + Om + Konto + Kontoen er lagt + Kontoen eksisterer eller er null + Konto navn + Kontoen er fjernet + Legge til konto + Bl + Kontollerer og lag kontoen + Velg et navn. + diff --git a/ImapNote2/src/main/res/values-v14/styles.xml b/ImapNote2/src/main/res/values-v14/styles.xml index 48d2b67b..c57b298e 100644 --- a/ImapNote2/src/main/res/values-v14/styles.xml +++ b/ImapNote2/src/main/res/values-v14/styles.xml @@ -1,24 +1,23 @@ - - - #e8d731 - - - + + + #e8d731 + + + - diff --git a/ImapNote2/src/main/res/values/strings.xml b/ImapNote2/src/main/res/values/strings.xml index c30c1c9d..cddc490e 100644 --- a/ImapNote2/src/main/res/values/strings.xml +++ b/ImapNote2/src/main/res/values/strings.xml @@ -1,4 +1,49 @@ - ImapNotes2 + ImapNotes2k + Account + New + Refresh + About + Search + Account has been removed + Quotation marks are not allowed in accountname + Remove + Save + + "Logging into your account… " + Account already exists or is null + Account has been added + Add Account + Account name + Choose a name for this account + Username + Your login name + Password + Your password + Server + IMAP notes server + Server port number to use + Sync Interval (minutes) + sync interval for this account (in minutes) + Notes Folder (optional) + Manually set full IMAP path to notes folder + Use Sticky Notes + Welcome + Select account to view or edit + Note Title + Delete + Color + Blue + White + Yellow + Pink + Green + Syncing... + "Refreshing notes list... " + "Updating notes list... " + 15 + Edit Account + Device name + Choose a name for this device. diff --git a/ImapNote2/src/main/res/values/styles.xml b/ImapNote2/src/main/res/values/styles.xml index f44993ab..28929866 100644 --- a/ImapNote2/src/main/res/values/styles.xml +++ b/ImapNote2/src/main/res/values/styles.xml @@ -1,16 +1,17 @@ - - - #e8d731 - - - + + + #e8d731 + + + diff --git a/ImapNote2/src/main/res/xml/authenticator.xml b/ImapNote2/src/main/res/xml/authenticator.xml index 0fa7a20b..c213191d 100644 --- a/ImapNote2/src/main/res/xml/authenticator.xml +++ b/ImapNote2/src/main/res/xml/authenticator.xml @@ -1,8 +1,7 @@ - - + android:smallIcon="@drawable/ic_launcher" /> + diff --git a/ImapNote2/src/main/res/xml/searchable.xml b/ImapNote2/src/main/res/xml/searchable.xml index 50e59933..55c76496 100644 --- a/ImapNote2/src/main/res/xml/searchable.xml +++ b/ImapNote2/src/main/res/xml/searchable.xml @@ -1,5 +1,5 @@ + android:hint="Search notes titles" + android:label="@string/app_name" /> diff --git a/ImapNote2/src/main/res/xml/syncadapter.xml b/ImapNote2/src/main/res/xml/syncadapter.xml index 4664064c..50f570c6 100644 --- a/ImapNote2/src/main/res/xml/syncadapter.xml +++ b/ImapNote2/src/main/res/xml/syncadapter.xml @@ -1,8 +1,7 @@ + android:supportsUploading="true" + android:userVisible="true" /> diff --git a/README.md b/README.md index fce6964a..241d9f21 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,40 @@ -imapnote2 -========= +imapnote2k +========== + +Notes +------------------ +Added travis. Don't really know how it works yet. + +Notes on this fork +------------------ + +This fork exists because I was looking for example code using IMAP and +Android and it is the first that I found that both worked and did +something useful to me. + +Initially the principal changes are the addition of comments and +conversion of many stringly typed variables and fields to specific +types together with the introduction of the Security enum that +encapsulates the specification and naming of the security types (None, +SSL, STARTTLS, etc.). Using the enum means that we no longer have to +use switch statements on the name of the security type and men that it +is harder to cause errors by mistyping. + +The effort to rid the code of magic numbers and literal strings will +continue. + +From now on I will start making breaking changes because I want to be +able to create rich text or html notes with embedded grapics and for +notes to be automatically merged when two or more devices make +simultaneous edits. + + + +Original README from nbenm +-------------------------- + +Below is the readme from nbenm's version: +https://github.com/nbenm/ImapNote2. Sync your notes between Android, iOs devices and different accounts like Gmail, iCloud and others diff --git a/build.gradle b/build.gradle index f4d8c542..2e369803 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f23df6e4..987e8e42 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Oct 21 11:34:03 PDT 2015 +#Mon Oct 24 19:37:51 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/notes.org b/notes.org new file mode 100644 index 00000000..52445038 --- /dev/null +++ b/notes.org @@ -0,0 +1,160 @@ +* To do + +** Consider dropping mime and using attachments directly. + +Consider also using HTML: github: AvinashSKaranth/android-summernotesummernote + +To use attachments we should examine +http://stackoverflow.com/questions/1748183/download-attachments-using-java-mail + +http://www.javatpoint.com/java-mail-api-tutorial + + +We should use the subject line for metadata instead of title so that +we can avoid downloading notes that are not new. + +We should probably use vector clocks to assist in conflict detection +so that we can avoid unnecessary merges. + +For vector clocks to be useful we need to identify the device. We +could identify just the app installation but then when we re-install we +will get a different id which will simply cause the vector clocks to +contain dead ids. + +The clocks should be stored in the subject so we need to know if there +are any limits on subject length. + +According to RFC 2060 there are no limits on the subject except that +it can be a string literal defined as a 32 bit integer followed by +CHAR8 octets. So plenty of space. + +** Subject line definition + +Must contain: + +- ID + +- Each device's vector clock + +** Synchronize when user brings app to foreground + +It's annoying to have to wait. + + +** Add configuration for directory synchronization. + +** Implement general filesynchronization. + +** Implement text file mer ging + +Consider using: + +- jDiff library from http://www.qarks.com/web/en/downloads.html + +- https://mvnrepository.com/open-source/diff-patch + + +** Implement Unix and Windows desktop versions. + +** Implement undo delete + +** Implement Autosave + + +First question is how is the note saved in the first place. + +Then can we do it automatically and then can we start the sync early? + + +* Directory synchronization + +Use the already implemented accounts. + +One sync. definition consists of the following: + +- Account to be used + +- Root directory on the server + +- Root directory on the local system. Use a directory chooser dialog. + +- Include sub-directories or not. Checkbox. + +- A list of glob patterns defining the files to be included. Simple + text field. Use white space as separator. + +- A list of glob patterns defining the files to be excluded. Simple + text field. Use white space as separator. + +- The maximum allowed network transfer rate. + +- The length of time allowed for a complete scan of the directory. + + +Use android.os.PatternMatcher to implement the globbing. + + +* Merge + +Should be a three way merge so that we can automatically resolve as +many conflicts as possible. This means keeping two copies on the +server. + +We can avoid worrying about race conditions by recognizing that this +is essentially a single user system so simultaneous access will be +unlikely (not impossible though). + +https://gist.github.com/stepchowfun/4713315 + +See: http://www.qarks.com/web/en/downloads.html + + +* Sync adapters + +See: + +- https://developer.android.com/training/sync-adapters/creating-sync-adapter.html + +- src/main/res/xml/syncadapter.xml + +- src/main/java/com/Pau/ImapNotes2/Sync/SyncAdapter.java + +- src/main/java/com/Pau/ImapNotes2/Sync/SyncService.java + + + + + +* SQLite database + +This is used to store metadata about the notes. The notes themselves +are stored as files in a sub-directory named after the account to +which they belong. The file names are the IMAP UIDs. + + +* Code analysis + +Run Android lint to discover opportunities to get rid of warnings, +convert fields to local variables, etc. + +** Analyses done + +Ran whole project default profile to get an overview and then picked +one analysis at a time to be fixed, tested and committed. + +| Group | Analysis | Synopsis | Notes | +|-------------------+----------------------------------+--------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------| +| | Field can be local | | Some of these seem to be work in progress so have been left unfixed. | +| | Parameter can be local | | No suspicious code found. | +| Probable bugs | Constant conditions & exceptions | Method invocation 'setDisplayHomeAsUpEnabled' at line 106 may produce 'java.lang.NullPointerException' | Suppressed because result is not used. | +| | ConstantConditions | | Some possible null pointers remain. | +| Data flow issues | Missing return statement | Not all execution paths return a value | Very odd the, the file in question is build.gradle. What should I do? | +| | | | | +| Infer Nullability | Added @Nullable, @NonNull | | | + + +* Intents + +The names of the intent items should be defined as constants in the +receiving class. + diff --git a/requirements.org b/requirements.org new file mode 100644 index 00000000..931ce205 --- /dev/null +++ b/requirements.org @@ -0,0 +1,42 @@ +* Requirements + +This document is meant to record the intended effect of future changes and the tasks that have to be +performed to bring them to fruition. + +* Automatic conflict resolution + +If a given note is changed on two devices while they are offline then +when they go online one of them might discover that it has changes +that are newer than the supposedly new version the server. + +We can reconcile many such changes automatically if we maintain a copy +of the file as it existed before both sets of changes. We can then do +a three way merge on the using the ancestor file the most recent file +uploaded to the server and the local file. The result replaces the +local file. The most recent server file replaces the ancestor and the +new local file replaces the server file. + +To make this happen we need the following: + +- Save the ancestor file, + +- Add a library that can do a three way textual merge. + +In addition we should implement some way of locking the files so that +two devices attempting to synchronize simultaneously will not +interfere with each other. This can probably be left to a later time +on the grounds that such collisions will be rare. + +To connect ancestor files and current files we will need either to +maintain the uid of the ancestor in the database record or abandon the +use of uid and give the files names that can be discovered from the +subject lines. That way we can store the same message in two folders +on the server, one for the ancestors and one for current files. + +** Tasks to do + +*** Find a suitable three way merge + +*** Implement the ancestor file + + diff --git a/synchronization.org b/synchronization.org new file mode 100644 index 00000000..0aef9eb4 --- /dev/null +++ b/synchronization.org @@ -0,0 +1,166 @@ +* Synchronization + +Here is an outline of the proposed new synchronization method that +will allow for merging. + +The process is as follows: + +1. prepare for sync. + +2. download newer files from remote + +3. download and merge conflicting files from remote + +4. upload newer local files + +The steps are detailed as follows: + +** Prepare for Sync + +When a new note is created it is saved in the local notes directory; +there is no new, deleted, etc. Saving does not touch the database. + +To prepare for a sync we enumerate the files in the notes directory +and for each one we check the mtime stored in the database; if the +file is not recorded in the database then the mtime is regarded as +zero. If the stored mtime differs from the file mtime we increment +the vector clock for this device in the database. If the record does +not exist in the database then the vector clock starts at zero and is +incremented to one. + +Then we enumerate all the records in the database and check to see if +each file mentioned still exists, if it does not then we increment the +vector clock. + +*** Can we drop the database? + +We have to enumerate the messages so we can extract the file name from +the title and find the local copy if it exists and check the mtime +directly. + +So we do not need to store the mtime in the database in order to +discover that the file has changed. This leaves the vector clock. + + +** Download newer + +Enumerate the messages in the IMAP current notes directory. Extract +the note id from the subject line (device ID plus creation time +stamp) and look it up in the database. Compare the vector clocks. + +The vector clock for a device that is not mentioned in the vector is +zero. Each remote note that is unambiguously newer (remote vector clock +elements are all equal to or greater than the corresponding local +element and at least one is greater) is downloaded and the vector +clock in the database updated. If the message has no attachment then +the note has been deleted so it should be deleted from the local; in +this case we also delete the database record. + +** Download conflicting files + +If the remote vector clock is not unambiguously newer and is not equal +to the local vector clock of the corresponding file then we have a +conflict. In this case we must merge the two files. The result of +the merge is a new local file where each element of the vector clock +is the maximum of the corresponding remote and local elements and the +element belong to this device is incremented once more. See the +section on merging for details. + +** Upload newer local files + +Enumerate the records in the local database and compare the vector +clocks with the remote vector clocks. If this is done with the same +set of data as the previous steps then there cannot be any conflicts. +If we rescan the IMAP folder it is possible that we will discover +conflicts; conflicts are ignored and will be fixed in the next sync +session. + +If the local vector clock is unambiguously newer than the remote then +the remote file is moved to the ancestors folder on the remote and the +local file uploaded to the current folder on the remote with a subject +line that records the note id and the vector clocks. + +** Merging + +A three way merge is performed. The ancestor file is the +corresponding file from the ancestors folder that has the greatest +vector clock that is unambiguously older than both the remote and +local files. + +If there are conflicts then both texts survive. + +Initially + +** Device IDs + +Each device must have a unique id. That is all files on a given +device belong to that id. + +So how is this id defined? One way is for the device id to be the +same as the account name but then we have to name the accounts +differently on each device. Another is for the device id to be simply +a field in the account and have the user fill it in. We could search +the device for some kind of id or we could create a random id when we +install the application or we could create a random id when we create +an account. Each of these has pros and cons: + +- Same as account name: + + - Pro: + Automatic + - Con: + Need different account names on each machine even though they + refer to the same IMAP account. + +- Device ID is a field in the account: + + - Con: + + User can carelessly use the same name for several devices + +- Find a device id somewhere in the device: + + - Pro: + + User isn't involved and hence cannot make mistakes. + + - Con: + + Most devices do not have a canonical id. Forces the device id to + be the same for all accounts on that device, this makes testing + inconvenient because it is now necessary to have two devices + whereas having separate ids in each account allows us to + synchronize two accounts on the same machine. + +- Application creates a random id when installed + + - Pro: + + No user involvement. + + - Con: + + As for other solutions that have one id per device this makes + testing inconvenient. If an account has to be recreated, perhaps + because the device has been replaced, it is hard to recover the + device id so that the files will be fetched from the repository. + + + +- Application creates a random id when creating an account + + - Pro: + + No user involvement, makes testing convenient with multiple + accounts on the same device but different device ids. + + - Con: + + As for the application wide device id this makes recovery tedious. + +The objections to random ids can mostly be overcome if they are short +so that they can easily be noted down or simply searched for inside +the IMAP account using a mail program. The question is how short can +they be before we risk collisions? + +We could use the Unix time converted to Base64.