mirror of https://github.com/etesync/android
parent
c26ae4fba6
commit
f77063ff1a
@ -1,190 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.HttpClient;
|
||||
import com.etesync.syncadapter.InvalidAccountException;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.Crypto;
|
||||
import com.etesync.syncadapter.journalmanager.JournalManager;
|
||||
import com.etesync.syncadapter.journalmanager.UserInfoManager;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class AddMemberFragment extends DialogFragment {
|
||||
final static private String KEY_MEMBER = "memberEmail";
|
||||
private Account account;
|
||||
private AccountSettings settings;
|
||||
private Context ctx;
|
||||
private HttpUrl remote;
|
||||
private CollectionInfo info;
|
||||
private String memberEmail;
|
||||
private byte[] memberPubKey;
|
||||
|
||||
public static AddMemberFragment newInstance(Account account, CollectionInfo info, String email) {
|
||||
AddMemberFragment frag = new AddMemberFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(Constants.KEY_ACCOUNT, account);
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||
args.putString(KEY_MEMBER, email);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
account = getArguments().getParcelable(Constants.KEY_ACCOUNT);
|
||||
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||
memberEmail = getArguments().getString(KEY_MEMBER);
|
||||
ctx = getContext();
|
||||
try {
|
||||
settings = new AccountSettings(ctx, account);
|
||||
} catch (InvalidAccountException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
remote = HttpUrl.get(settings.getUri());
|
||||
|
||||
new MemberAdd().execute();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progress = new ProgressDialog(getContext());
|
||||
progress.setTitle(R.string.collection_members_adding);
|
||||
progress.setMessage(getString(R.string.please_wait));
|
||||
progress.setIndeterminate(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
setCancelable(false);
|
||||
return progress;
|
||||
}
|
||||
|
||||
private class MemberAdd extends AsyncTask<Void, Void, MemberAdd.AddResult> {
|
||||
@Override
|
||||
protected AddResult doInBackground(Void... voids) {
|
||||
try {
|
||||
OkHttpClient httpClient = HttpClient.create(ctx, settings);
|
||||
UserInfoManager userInfoManager = new UserInfoManager(httpClient, remote);
|
||||
|
||||
UserInfoManager.UserInfo userInfo = userInfoManager.get(memberEmail);
|
||||
if (userInfo == null) {
|
||||
throw new Exception(getString(R.string.collection_members_error_user_not_found, memberEmail));
|
||||
}
|
||||
memberPubKey = userInfo.getPubkey();
|
||||
return new AddResult(null);
|
||||
} catch (Exception e) {
|
||||
return new AddResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(AddResult result) {
|
||||
if (result.throwable == null) {
|
||||
String fingerprint = Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(memberPubKey);
|
||||
View view = LayoutInflater.from(getContext()).inflate(R.layout.fingerprint_alertdialog, null);
|
||||
((TextView) view.findViewById(R.id.body)).setText(getString(R.string.trust_fingerprint_body, memberEmail));
|
||||
((TextView) view.findViewById(R.id.fingerprint)).setText(fingerprint);
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_fingerprint_dark)
|
||||
.setTitle(R.string.trust_fingerprint_title)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
new MemberAddSecond().execute();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dismiss();
|
||||
}
|
||||
}).show();
|
||||
} else {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.collection_members_add_error)
|
||||
.setMessage(result.throwable.getMessage())
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
}).show();
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
class AddResult {
|
||||
final Throwable throwable;
|
||||
|
||||
AddResult(final Throwable throwable) {
|
||||
this.throwable = throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MemberAddSecond extends AsyncTask<Void, Void, MemberAddSecond.AddResultSecond> {
|
||||
@Override
|
||||
protected AddResultSecond doInBackground(Void... voids) {
|
||||
try {
|
||||
OkHttpClient httpClient = HttpClient.create(ctx, settings);
|
||||
JournalManager journalsManager = new JournalManager(httpClient, remote);
|
||||
|
||||
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(info.uid);
|
||||
Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid);
|
||||
|
||||
byte[] encryptedKey = crypto.getEncryptedKey(settings.getKeyPair(), memberPubKey);
|
||||
JournalManager.Member member = new JournalManager.Member(memberEmail, encryptedKey);
|
||||
journalsManager.addMember(journal, member);
|
||||
return new AddResultSecond(null);
|
||||
} catch (Exception e) {
|
||||
return new AddResultSecond(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(AddResultSecond result) {
|
||||
if (result.throwable == null) {
|
||||
((Refreshable) getActivity()).refresh();
|
||||
} else {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.collection_members_add_error)
|
||||
.setMessage(result.throwable.getMessage())
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
class AddResultSecond {
|
||||
final Throwable throwable;
|
||||
|
||||
AddResultSecond(final Throwable throwable) {
|
||||
this.throwable = throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Dialog
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.etesync.syncadapter.*
|
||||
import com.etesync.syncadapter.journalmanager.Crypto
|
||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
||||
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class AddMemberFragment : DialogFragment() {
|
||||
private var account: Account? = null
|
||||
private var settings: AccountSettings? = null
|
||||
private var ctx: Context? = null
|
||||
private var remote: HttpUrl? = null
|
||||
private var info: CollectionInfo? = null
|
||||
private var memberEmail: String? = null
|
||||
private var memberPubKey: ByteArray? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
account = arguments!!.getParcelable(Constants.KEY_ACCOUNT)
|
||||
info = arguments!!.getSerializable(Constants.KEY_COLLECTION_INFO) as CollectionInfo
|
||||
memberEmail = arguments!!.getString(KEY_MEMBER)
|
||||
ctx = context
|
||||
try {
|
||||
settings = AccountSettings(ctx!!, account!!)
|
||||
} catch (e: InvalidAccountException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
remote = HttpUrl.get(settings!!.uri!!)
|
||||
|
||||
MemberAdd().execute()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(context)
|
||||
progress.setTitle(R.string.collection_members_adding)
|
||||
progress.setMessage(getString(R.string.please_wait))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
}
|
||||
|
||||
private inner class MemberAdd : AsyncTask<Void, Void, MemberAdd.AddResult>() {
|
||||
override fun doInBackground(vararg voids: Void): AddResult {
|
||||
try {
|
||||
val httpClient = HttpClient.create(ctx!!, settings!!)
|
||||
val userInfoManager = UserInfoManager(httpClient, remote!!)
|
||||
|
||||
val userInfo = userInfoManager[memberEmail!!]
|
||||
?: throw Exception(getString(R.string.collection_members_error_user_not_found, memberEmail))
|
||||
memberPubKey = userInfo.pubkey
|
||||
return AddResult(null)
|
||||
} catch (e: Exception) {
|
||||
return AddResult(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: AddResult) {
|
||||
if (result.throwable == null) {
|
||||
val fingerprint = Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(memberPubKey!!)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.fingerprint_alertdialog, null)
|
||||
(view.findViewById<View>(R.id.body) as TextView).text = getString(R.string.trust_fingerprint_body, memberEmail)
|
||||
(view.findViewById<View>(R.id.fingerprint) as TextView).text = fingerprint
|
||||
AlertDialog.Builder(activity!!)
|
||||
.setIcon(R.drawable.ic_fingerprint_dark)
|
||||
.setTitle(R.string.trust_fingerprint_title)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, which -> MemberAddSecond().execute() }
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, which -> dismiss() }.show()
|
||||
} else {
|
||||
AlertDialog.Builder(activity!!)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.collection_members_add_error)
|
||||
.setMessage(result.throwable.message)
|
||||
.setPositiveButton(android.R.string.yes) { dialog, which -> }.show()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class AddResult(val throwable: Throwable?)
|
||||
}
|
||||
|
||||
private inner class MemberAddSecond : AsyncTask<Void, Void, MemberAddSecond.AddResultSecond>() {
|
||||
override fun doInBackground(vararg voids: Void): AddResultSecond {
|
||||
try {
|
||||
val httpClient = HttpClient.create(ctx!!, settings!!)
|
||||
val journalsManager = JournalManager(httpClient, remote!!)
|
||||
|
||||
val journal = JournalManager.Journal.fakeWithUid(info!!.uid)
|
||||
val crypto = Crypto.CryptoManager(info!!.version, settings!!.password(), info!!.uid)
|
||||
|
||||
val encryptedKey = crypto.getEncryptedKey(settings!!.keyPair!!, memberPubKey!!)
|
||||
val member = JournalManager.Member(memberEmail!!, encryptedKey!!)
|
||||
journalsManager.addMember(journal, member)
|
||||
return AddResultSecond(null)
|
||||
} catch (e: Exception) {
|
||||
return AddResultSecond(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: AddResultSecond) {
|
||||
if (result.throwable == null) {
|
||||
(activity as Refreshable).refresh()
|
||||
} else {
|
||||
AlertDialog.Builder(activity!!)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.collection_members_add_error)
|
||||
.setMessage(result.throwable.message)
|
||||
.setPositiveButton(android.R.string.yes) { dialog, which -> }.show()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
internal inner class AddResultSecond(val throwable: Throwable?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY_MEMBER = "memberEmail"
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo, email: String): AddMemberFragment {
|
||||
val frag = AddMemberFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(Constants.KEY_ACCOUNT, account)
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info)
|
||||
args.putString(KEY_MEMBER, email)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
if (!getSupportFragmentManager().popBackStackImmediate()) {
|
||||
finish();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
App app = (App) getApplicationContext();
|
||||
if (app.getCertManager() != null)
|
||||
app.getCertManager().appInForeground = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
App app = (App) getApplicationContext();
|
||||
if (app.getCertManager() != null)
|
||||
app.getCertManager().appInForeground = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.view.MenuItem
|
||||
|
||||
import com.etesync.syncadapter.App
|
||||
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
if (!supportFragmentManager.popBackStackImmediate()) {
|
||||
finish()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val app = applicationContext as App
|
||||
if (app.certManager != null)
|
||||
app.certManager.appInForeground = true
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
val app = applicationContext as App
|
||||
if (app.certManager != null)
|
||||
app.certManager.appInForeground = false
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.InputType;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.model.JournalEntity;
|
||||
import com.etesync.syncadapter.resource.LocalCalendar;
|
||||
|
||||
import io.requery.Persistable;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
|
||||
public class CollectionMembersActivity extends BaseActivity implements Refreshable {
|
||||
public final static String EXTRA_ACCOUNT = "account",
|
||||
EXTRA_COLLECTION_INFO = "collectionInfo";
|
||||
|
||||
private Account account;
|
||||
private JournalEntity journalEntity;
|
||||
private CollectionMembersListFragment listFragment;
|
||||
protected CollectionInfo info;
|
||||
|
||||
public static Intent newIntent(Context context, Account account, CollectionInfo info) {
|
||||
Intent intent = new Intent(context, CollectionMembersActivity.class);
|
||||
intent.putExtra(CollectionMembersActivity.EXTRA_ACCOUNT, account);
|
||||
intent.putExtra(CollectionMembersActivity.EXTRA_COLLECTION_INFO, info);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
EntityDataStore<Persistable> data = ((App) getApplicationContext()).getData();
|
||||
|
||||
journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
|
||||
if ((journalEntity == null) || journalEntity.isDeleted()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
info = journalEntity.getInfo();
|
||||
|
||||
setTitle(R.string.collection_members_title);
|
||||
|
||||
final View colorSquare = findViewById(R.id.color);
|
||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||
if (info.color != null) {
|
||||
colorSquare.setBackgroundColor(info.color);
|
||||
} else {
|
||||
colorSquare.setBackgroundColor(LocalCalendar.defaultColor);
|
||||
}
|
||||
} else {
|
||||
colorSquare.setVisibility(View.GONE);
|
||||
}
|
||||
findViewById(R.id.progressBar).setVisibility(View.GONE);
|
||||
|
||||
final TextView title = (TextView) findViewById(R.id.display_name);
|
||||
title.setText(info.displayName);
|
||||
|
||||
final TextView desc = (TextView) findViewById(R.id.description);
|
||||
desc.setText(info.description);
|
||||
|
||||
if (listFragment != null) {
|
||||
listFragment.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.view_collection_members);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
|
||||
info = (CollectionInfo) getIntent().getExtras().getSerializable(EXTRA_COLLECTION_INFO);
|
||||
|
||||
refresh();
|
||||
|
||||
// We refresh before this, so we don't refresh the list before it was fully created.
|
||||
if (savedInstanceState == null) {
|
||||
listFragment = CollectionMembersListFragment.newInstance(account, info);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.list_entries_container, listFragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void onAddMemberClicked(View v) {
|
||||
final EditText input = new EditText(this);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.collection_members_add)
|
||||
.setIcon(R.drawable.ic_account_add_dark)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
DialogFragment frag = AddMemberFragment.newInstance(account, info, input.getText().toString());
|
||||
frag.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
dialog.setView(input);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import com.etesync.syncadapter.App
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.model.JournalEntity
|
||||
import com.etesync.syncadapter.resource.LocalCalendar
|
||||
|
||||
class CollectionMembersActivity : BaseActivity(), Refreshable {
|
||||
|
||||
private lateinit var account: Account
|
||||
private var journalEntity: JournalEntity? = null
|
||||
private var listFragment: CollectionMembersListFragment? = null
|
||||
protected lateinit var info: CollectionInfo
|
||||
|
||||
override fun refresh() {
|
||||
val data = (applicationContext as App).data
|
||||
|
||||
journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid)
|
||||
if (journalEntity == null || journalEntity!!.isDeleted) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
info = journalEntity!!.info
|
||||
|
||||
setTitle(R.string.collection_members_title)
|
||||
|
||||
val colorSquare = findViewById<View>(R.id.color)
|
||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||
if (info.color != null) {
|
||||
colorSquare.setBackgroundColor(info.color)
|
||||
} else {
|
||||
colorSquare.setBackgroundColor(LocalCalendar.defaultColor)
|
||||
}
|
||||
} else {
|
||||
colorSquare.visibility = View.GONE
|
||||
}
|
||||
findViewById<View>(R.id.progressBar).visibility = View.GONE
|
||||
|
||||
val title = findViewById<View>(R.id.display_name) as TextView
|
||||
title.text = info.displayName
|
||||
|
||||
val desc = findViewById<View>(R.id.description) as TextView
|
||||
desc.text = info.description
|
||||
|
||||
if (listFragment != null) {
|
||||
listFragment!!.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.view_collection_members)
|
||||
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)
|
||||
info = intent.extras!!.getSerializable(EXTRA_COLLECTION_INFO) as CollectionInfo
|
||||
|
||||
refresh()
|
||||
|
||||
// We refresh before this, so we don't refresh the list before it was fully created.
|
||||
if (savedInstanceState == null) {
|
||||
listFragment = CollectionMembersListFragment.newInstance(account, info)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.list_entries_container, listFragment)
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun onAddMemberClicked(v: View) {
|
||||
val input = EditText(this)
|
||||
input.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setTitle(R.string.collection_members_add)
|
||||
.setIcon(R.drawable.ic_account_add_dark)
|
||||
.setPositiveButton(android.R.string.yes) { dialog, which ->
|
||||
val frag = AddMemberFragment.newInstance(account, info, input.text.toString())
|
||||
frag.show(supportFragmentManager, null)
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { dialog, which -> }
|
||||
dialog.setView(input)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refresh()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EXTRA_ACCOUNT = "account"
|
||||
val EXTRA_COLLECTION_INFO = "collectionInfo"
|
||||
|
||||
fun newIntent(context: Context, account: Account, info: CollectionInfo): Intent {
|
||||
val intent = Intent(context, CollectionMembersActivity::class.java)
|
||||
intent.putExtra(CollectionMembersActivity.EXTRA_ACCOUNT, account)
|
||||
intent.putExtra(CollectionMembersActivity.EXTRA_COLLECTION_INFO, info)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.HttpClient;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.JournalManager;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.model.JournalEntity;
|
||||
import com.etesync.syncadapter.model.JournalModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.requery.Persistable;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class CollectionMembersListFragment extends ListFragment implements AdapterView.OnItemClickListener, Refreshable {
|
||||
private EntityDataStore<Persistable> data;
|
||||
private Account account;
|
||||
private CollectionInfo info;
|
||||
private JournalEntity journalEntity;
|
||||
private AsyncTask asyncTask;
|
||||
|
||||
private TextView emptyTextView;
|
||||
|
||||
public static CollectionMembersListFragment newInstance(Account account, CollectionInfo info) {
|
||||
CollectionMembersListFragment frag = new CollectionMembersListFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(Constants.KEY_ACCOUNT, account);
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
data = ((App) getContext().getApplicationContext()).getData();
|
||||
account = getArguments().getParcelable(Constants.KEY_ACCOUNT);
|
||||
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||
journalEntity = JournalModel.Journal.fetch(data, info.getServiceEntity(data), info.uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.collection_members_list, container, false);
|
||||
|
||||
//This is instead of setEmptyText() function because of Google bug
|
||||
//See: https://code.google.com/p/android/issues/detail?id=21742
|
||||
emptyTextView = (TextView) view.findViewById(android.R.id.empty);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
asyncTask = new JournalMembersFetch().execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
refresh();
|
||||
|
||||
getListView().setOnItemClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (asyncTask != null)
|
||||
asyncTask.cancel(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final JournalManager.Member member = (JournalManager.Member) getListAdapter().getItem(position);
|
||||
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.collection_members_remove_title)
|
||||
.setMessage(getString(R.string.collection_members_remove, member.getUser()))
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
DialogFragment frag = RemoveMemberFragment.newInstance(account, info, member.getUser());
|
||||
frag.show(getFragmentManager(), null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
class MembersListAdapter extends ArrayAdapter<JournalManager.Member> {
|
||||
MembersListAdapter(Context context) {
|
||||
super(context, R.layout.collection_members_list_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public View getView(int position, View v, @NonNull ViewGroup parent) {
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(getContext()).inflate(R.layout.collection_members_list_item, parent, false);
|
||||
|
||||
JournalManager.Member member = getItem(position);
|
||||
|
||||
TextView tv = (TextView) v.findViewById(R.id.title);
|
||||
tv.setText(member.getUser());
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
private class JournalMembersFetch extends AsyncTask<Void, Void, JournalMembersFetch.MembersResult> {
|
||||
@Override
|
||||
protected MembersResult doInBackground(Void... voids) {
|
||||
try {
|
||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||
OkHttpClient httpClient = HttpClient.create(getContext(), settings);
|
||||
JournalManager journalsManager = new JournalManager(httpClient, HttpUrl.get(settings.getUri()));
|
||||
|
||||
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(journalEntity.getUid());
|
||||
return new MembersResult(journalsManager.listMembers(journal), null);
|
||||
} catch (Exception e) {
|
||||
return new MembersResult(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(MembersResult result) {
|
||||
if (result.throwable == null) {
|
||||
MembersListAdapter listAdapter = new MembersListAdapter(getContext());
|
||||
setListAdapter(listAdapter);
|
||||
|
||||
listAdapter.addAll(result.members);
|
||||
|
||||
emptyTextView.setText(R.string.collection_members_list_empty);
|
||||
} else {
|
||||
emptyTextView.setText(result.throwable.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
class MembersResult {
|
||||
final List<JournalManager.Member> members;
|
||||
final Throwable throwable;
|
||||
|
||||
MembersResult(final List<JournalManager.Member> members, final Throwable throwable) {
|
||||
this.members = members;
|
||||
this.throwable = throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.ListFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.TextView
|
||||
import com.etesync.syncadapter.*
|
||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.model.JournalEntity
|
||||
import com.etesync.syncadapter.model.JournalModel
|
||||
import io.requery.Persistable
|
||||
import io.requery.sql.EntityDataStore
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class CollectionMembersListFragment : ListFragment(), AdapterView.OnItemClickListener, Refreshable {
|
||||
private lateinit var data: EntityDataStore<Persistable>
|
||||
private lateinit var account: Account
|
||||
private lateinit var info: CollectionInfo
|
||||
private lateinit var journalEntity: JournalEntity
|
||||
private var asyncTask: AsyncTask<*, *, *>? = null
|
||||
|
||||
private var emptyTextView: TextView? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
data = (context!!.applicationContext as App).data
|
||||
account = arguments!!.getParcelable(Constants.KEY_ACCOUNT)
|
||||
info = arguments!!.getSerializable(Constants.KEY_COLLECTION_INFO) as CollectionInfo
|
||||
journalEntity = JournalModel.Journal.fetch(data!!, info!!.getServiceEntity(data), info!!.uid)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.collection_members_list, container, false)
|
||||
|
||||
//This is instead of setEmptyText() function because of Google bug
|
||||
//See: https://code.google.com/p/android/issues/detail?id=21742
|
||||
emptyTextView = view.findViewById<View>(android.R.id.empty) as TextView
|
||||
return view
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
asyncTask = JournalMembersFetch().execute()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
refresh()
|
||||
|
||||
listView.onItemClickListener = this
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (asyncTask != null)
|
||||
asyncTask!!.cancel(true)
|
||||
}
|
||||
|
||||
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||
val member = listAdapter.getItem(position) as JournalManager.Member
|
||||
|
||||
AlertDialog.Builder(activity!!)
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.collection_members_remove_title)
|
||||
.setMessage(getString(R.string.collection_members_remove, member.user))
|
||||
.setPositiveButton(android.R.string.yes) { dialog, which ->
|
||||
val frag = RemoveMemberFragment.newInstance(account, info, member.user!!)
|
||||
frag.show(fragmentManager!!, null)
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { dialog, which -> }.show()
|
||||
}
|
||||
|
||||
internal inner class MembersListAdapter(context: Context) : ArrayAdapter<JournalManager.Member>(context, R.layout.collection_members_list_item) {
|
||||
|
||||
override fun getView(position: Int, v: View?, parent: ViewGroup): View {
|
||||
var v = v
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(context).inflate(R.layout.collection_members_list_item, parent, false)
|
||||
|
||||
val member = getItem(position)
|
||||
|
||||
val tv = v!!.findViewById<View>(R.id.title) as TextView
|
||||
tv.text = member!!.user
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
private inner class JournalMembersFetch : AsyncTask<Void, Void, JournalMembersFetch.MembersResult>() {
|
||||
override fun doInBackground(vararg voids: Void): MembersResult {
|
||||
try {
|
||||
val settings = AccountSettings(context!!, account!!)
|
||||
val httpClient = HttpClient.create(context!!, settings)
|
||||
val journalsManager = JournalManager(httpClient, HttpUrl.get(settings.uri!!)!!)
|
||||
|
||||
val journal = JournalManager.Journal.fakeWithUid(journalEntity!!.uid)
|
||||
return MembersResult(journalsManager.listMembers(journal), null)
|
||||
} catch (e: Exception) {
|
||||
return MembersResult(null, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: MembersResult) {
|
||||
if (result.throwable == null) {
|
||||
val listAdapter = MembersListAdapter(context!!)
|
||||
setListAdapter(listAdapter)
|
||||
|
||||
listAdapter.addAll(result.members)
|
||||
|
||||
emptyTextView!!.setText(R.string.collection_members_list_empty)
|
||||
} else {
|
||||
emptyTextView!!.text = result.throwable.localizedMessage
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class MembersResult(val members: List<JournalManager.Member>?, val throwable: Throwable?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): CollectionMembersListFragment {
|
||||
val frag = CollectionMembersListFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(Constants.KEY_ACCOUNT, account)
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -1,475 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.format.Time;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.model.JournalEntity;
|
||||
import com.etesync.syncadapter.model.SyncEntry;
|
||||
|
||||
import net.fortuna.ical4j.model.component.VAlarm;
|
||||
import net.fortuna.ical4j.model.property.Attendee;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Formatter;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import at.bitfire.ical4android.Event;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.LabeledProperty;
|
||||
import ezvcard.parameter.AddressType;
|
||||
import ezvcard.parameter.EmailType;
|
||||
import ezvcard.parameter.RelatedType;
|
||||
import ezvcard.parameter.TelephoneType;
|
||||
import ezvcard.property.Address;
|
||||
import ezvcard.property.Email;
|
||||
import ezvcard.property.Impp;
|
||||
import ezvcard.property.Related;
|
||||
import ezvcard.property.Telephone;
|
||||
import ezvcard.property.Url;
|
||||
import ezvcard.util.PartialDate;
|
||||
import io.requery.Persistable;
|
||||
import io.requery.sql.EntityDataStore;
|
||||
|
||||
import static com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment.setJournalEntryView;
|
||||
|
||||
public class JournalItemActivity extends BaseActivity implements Refreshable {
|
||||
private static final String KEY_SYNC_ENTRY = "syncEntry";
|
||||
private JournalEntity journalEntity;
|
||||
protected CollectionInfo info;
|
||||
private SyncEntry syncEntry;
|
||||
|
||||
public static Intent newIntent(Context context, CollectionInfo info, SyncEntry syncEntry) {
|
||||
Intent intent = new Intent(context, JournalItemActivity.class);
|
||||
intent.putExtra(Constants.KEY_COLLECTION_INFO, info);
|
||||
intent.putExtra(KEY_SYNC_ENTRY, syncEntry);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
EntityDataStore<Persistable> data = ((App) getApplicationContext()).getData();
|
||||
|
||||
journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
|
||||
if ((journalEntity == null) || journalEntity.isDeleted()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
info = journalEntity.getInfo();
|
||||
|
||||
setTitle(info.displayName);
|
||||
|
||||
setJournalEntryView(findViewById(R.id.journal_list_item), info, syncEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.journal_item_activity);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
info = (CollectionInfo) getIntent().getExtras().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||
syncEntry = (SyncEntry) getIntent().getExtras().getSerializable(KEY_SYNC_ENTRY);
|
||||
|
||||
refresh();
|
||||
|
||||
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
||||
viewPager.setAdapter(new TabsAdapter(getSupportFragmentManager(), this, info, syncEntry));
|
||||
|
||||
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
}
|
||||
|
||||
private static class TabsAdapter extends FragmentPagerAdapter {
|
||||
private Context context;
|
||||
private CollectionInfo info;
|
||||
private SyncEntry syncEntry;
|
||||
public TabsAdapter(FragmentManager fm, Context context, CollectionInfo info, SyncEntry syncEntry) {
|
||||
super(fm);
|
||||
this.context = context;
|
||||
this.info = info;
|
||||
this.syncEntry = syncEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
// FIXME: Make it depend on info type (only have non-raw for known types)
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
if (position == 0) {
|
||||
return context.getString(R.string.journal_item_tab_main);
|
||||
} else {
|
||||
return context.getString(R.string.journal_item_tab_raw);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
if (position == 0) {
|
||||
return PrettyFragment.newInstance(info, syncEntry);
|
||||
} else {
|
||||
return TextFragment.newInstance(syncEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextFragment extends Fragment {
|
||||
public static TextFragment newInstance(SyncEntry syncEntry) {
|
||||
TextFragment frag = new TextFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putSerializable(KEY_SYNC_ENTRY, syncEntry);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.text_fragment, container, false);
|
||||
|
||||
TextView tv = (TextView) v.findViewById(R.id.content);
|
||||
|
||||
SyncEntry syncEntry = (SyncEntry) getArguments().getSerializable(KEY_SYNC_ENTRY);
|
||||
tv.setText(syncEntry.getContent());
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PrettyFragment extends Fragment {
|
||||
CollectionInfo info;
|
||||
SyncEntry syncEntry;
|
||||
private AsyncTask asyncTask;
|
||||
|
||||
public static PrettyFragment newInstance(CollectionInfo info, SyncEntry syncEntry) {
|
||||
PrettyFragment frag = new PrettyFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||
args.putSerializable(KEY_SYNC_ENTRY, syncEntry);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = null;
|
||||
|
||||
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||
syncEntry = (SyncEntry) getArguments().getSerializable(KEY_SYNC_ENTRY);
|
||||
|
||||
switch (info.type) {
|
||||
case ADDRESS_BOOK:
|
||||
v = inflater.inflate(R.layout.contact_info, container, false);
|
||||
asyncTask = new LoadContactTask(v).execute();
|
||||
break;
|
||||
case CALENDAR:
|
||||
v = inflater.inflate(R.layout.event_info, container, false);
|
||||
asyncTask = new LoadEventTask(v).execute();
|
||||
break;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (asyncTask != null)
|
||||
asyncTask.cancel(true);
|
||||
}
|
||||
|
||||
private class LoadEventTask extends AsyncTask<Void, Void, Event> {
|
||||
View view;
|
||||
LoadEventTask(View v) {
|
||||
super();
|
||||
view = v;
|
||||
}
|
||||
@Override
|
||||
protected Event doInBackground(Void... aVoids) {
|
||||
InputStream is = new ByteArrayInputStream(syncEntry.getContent().getBytes(Charsets.UTF_8));
|
||||
|
||||
try {
|
||||
return Event.fromStream(is, Charsets.UTF_8, null)[0];
|
||||
} catch (InvalidCalendarException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Event event) {
|
||||
final View loader = view.findViewById(R.id.event_info_loading_msg);
|
||||
loader.setVisibility(View.GONE);
|
||||
final View contentContainer = view.findViewById(R.id.event_info_scroll_view);
|
||||
contentContainer.setVisibility(View.VISIBLE);
|
||||
|
||||
setTextViewText(view, R.id.title, event.summary);
|
||||
|
||||
setTextViewText(view, R.id.when_datetime, getDisplayedDatetime(event.dtStart.getDate().getTime(), event.dtEnd.getDate().getTime(), event.isAllDay(), getContext()));
|
||||
|
||||
setTextViewText(view, R.id.where, event.location);
|
||||
|
||||
if (event.organizer != null) {
|
||||
TextView tv = (TextView) view.findViewById(R.id.organizer);
|
||||
tv.setText(event.organizer.getCalAddress().toString().replaceFirst("mailto:", ""));
|
||||
} else {
|
||||
View organizer = view.findViewById(R.id.organizer_container);
|
||||
organizer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setTextViewText(view, R.id.description, event.description);
|
||||
|
||||
boolean first = true;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Attendee attendee : event.attendees) {
|
||||
if (first) {
|
||||
first = false;
|
||||
sb.append(getString(R.string.journal_item_attendees)).append(": ");
|
||||
} else {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(attendee.getCalAddress().toString().replaceFirst("mailto:", ""));
|
||||
}
|
||||
setTextViewText(view, R.id.attendees, sb.toString());
|
||||
|
||||
first = true;
|
||||
sb = new StringBuilder();
|
||||
for (VAlarm alarm : event.alarms) {
|
||||
if (first) {
|
||||
first = false;
|
||||
sb.append(getString(R.string.journal_item_reminders)).append(": ");
|
||||
} else {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(alarm.getTrigger().getValue());
|
||||
}
|
||||
setTextViewText(view, R.id.reminders, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadContactTask extends AsyncTask<Void, Void, Contact> {
|
||||
View view;
|
||||
|
||||
LoadContactTask(View v) {
|
||||
super();
|
||||
view = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Contact doInBackground(Void... aVoids) {
|
||||
InputStream is = new ByteArrayInputStream(syncEntry.getContent().getBytes(Charsets.UTF_8));
|
||||
|
||||
try {
|
||||
return Contact.fromStream(is, Charsets.UTF_8, null)[0];
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Contact contact) {
|
||||
final View loader = view.findViewById(R.id.loading_msg);
|
||||
loader.setVisibility(View.GONE);
|
||||
final View contentContainer = view.findViewById(R.id.content_container);
|
||||
contentContainer.setVisibility(View.VISIBLE);
|
||||
|
||||
TextView tv = (TextView) view.findViewById(R.id.display_name);
|
||||
tv.setText(contact.displayName);
|
||||
|
||||
if (contact.group) {
|
||||
showGroup(contact);
|
||||
} else {
|
||||
showContact(contact);
|
||||
}
|
||||
}
|
||||
|
||||
private void showGroup(Contact contact) {
|
||||
final ViewGroup mainCard = (ViewGroup) view.findViewById(R.id.main_card);
|
||||
|
||||
addInfoItem(view.getContext(), mainCard, getString(R.string.journal_item_member_count), null, String.valueOf(contact.members.size()));
|
||||
|
||||
for (String member : contact.members) {
|
||||
addInfoItem(view.getContext(), mainCard, getString(R.string.journal_item_member), null, member);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showContact(Contact contact) {
|
||||
final ViewGroup mainCard = (ViewGroup) view.findViewById(R.id.main_card);
|
||||
final ViewGroup aboutCard = (ViewGroup) view.findViewById(R.id.about_card);
|
||||
aboutCard.findViewById(R.id.title_container).setVisibility(View.VISIBLE);
|
||||
|
||||
// TEL
|
||||
for (LabeledProperty<Telephone> labeledPhone : contact.phoneNumbers) {
|
||||
List<TelephoneType> types = labeledPhone.property.getTypes();
|
||||
String type = (types.size() > 0) ? types.get(0).getValue() : null;
|
||||
addInfoItem(view.getContext(), mainCard, getString(R.string.journal_item_phone), type, labeledPhone.property.getText());
|
||||
}
|
||||
|
||||
// EMAIL
|
||||
for (LabeledProperty<Email> labeledEmail : contact.emails) {
|
||||
List<EmailType> types = labeledEmail.property.getTypes();
|
||||
String type = (types.size() > 0) ? types.get(0).getValue() : null;
|
||||
addInfoItem(view.getContext(), mainCard, getString(R.string.journal_item_email), type, labeledEmail.property.getValue());
|
||||
}
|
||||
|
||||
// ORG, TITLE, ROLE
|
||||
if (contact.organization != null) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_organization), contact.jobTitle, contact.organization.getValues().get(0));
|
||||
}
|
||||
if (contact.jobDescription != null) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_job_description), null, contact.jobTitle);
|
||||
}
|
||||
|
||||
// IMPP
|
||||
for (LabeledProperty<Impp> labeledImpp : contact.impps) {
|
||||
addInfoItem(view.getContext(), mainCard, getString(R.string.journal_item_impp), labeledImpp.property.getProtocol(), labeledImpp.property.getHandle());
|
||||
}
|
||||
|
||||
// NICKNAME
|
||||
if (contact.nickName != null && contact.nickName.getValues().size() > 0) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_nickname), null, contact.nickName.getValues().get(0));
|
||||
}
|
||||
|
||||
// ADR
|
||||
for (LabeledProperty<Address> labeledAddress : contact.addresses) {
|
||||
List<AddressType> types = labeledAddress.property.getTypes();
|
||||
String type = (types.size() > 0) ? types.get(0).getValue() : null;
|
||||
addInfoItem(view.getContext(), mainCard, getString(R.string.journal_item_address), type, labeledAddress.property.getLabel());
|
||||
}
|
||||
|
||||
// NOTE
|
||||
if (contact.note != null) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_note), null, contact.note);
|
||||
}
|
||||
|
||||
// URL
|
||||
for (LabeledProperty<Url> labeledUrl : contact.urls) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_website), null, labeledUrl.property.getValue());
|
||||
}
|
||||
|
||||
// ANNIVERSARY
|
||||
if (contact.anniversary != null) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_anniversary), null, getDisplayedDate(contact.anniversary.getDate(), contact.anniversary.getPartialDate()));
|
||||
}
|
||||
// BDAY
|
||||
if (contact.birthDay != null) {
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_birthday), null, getDisplayedDate(contact.birthDay.getDate(), contact.birthDay.getPartialDate()));
|
||||
}
|
||||
|
||||
// RELATED
|
||||
for (Related related : contact.relations) {
|
||||
List<RelatedType> types = related.getTypes();
|
||||
String type = (types.size() > 0) ? types.get(0).getValue() : null;
|
||||
addInfoItem(view.getContext(), aboutCard, getString(R.string.journal_item_relation), type, related.getText());
|
||||
}
|
||||
|
||||
// PHOTO
|
||||
// if (contact.photo != null)
|
||||
}
|
||||
}
|
||||
|
||||
private String getDisplayedDate(Date date, PartialDate partialDate) {
|
||||
if (date != null) {
|
||||
long epochDate = date.getTime();
|
||||
return getDisplayedDatetime(epochDate, epochDate, true, getContext());
|
||||
} else {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("d MMMM", Locale.getDefault());
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
calendar.set(Calendar.DAY_OF_MONTH, partialDate.getDate());
|
||||
calendar.set(Calendar.MONTH, partialDate.getMonth() - 1);
|
||||
return formatter.format(calendar.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
private static View addInfoItem(Context context, ViewGroup parent, String type, String label, String value) {
|
||||
ViewGroup layout = (ViewGroup) parent.findViewById(R.id.container);
|
||||
View infoItem = LayoutInflater.from(context).inflate(R.layout.contact_info_item, layout, false);
|
||||
layout.addView(infoItem);
|
||||
setTextViewText(infoItem, R.id.type, type);
|
||||
setTextViewText(infoItem, R.id.title, label);
|
||||
setTextViewText(infoItem, R.id.content, value);
|
||||
parent.setVisibility(View.VISIBLE);
|
||||
|
||||
return infoItem;
|
||||
}
|
||||
|
||||
private static void setTextViewText(View parent, int id, String text) {
|
||||
TextView tv = (TextView) parent.findViewById(id);
|
||||
if (text == null) {
|
||||
tv.setVisibility(View.GONE);
|
||||
} else {
|
||||
tv.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDisplayedDatetime(long startMillis, long endMillis, boolean allDay, Context context) {
|
||||
// Configure date/time formatting.
|
||||
int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
|
||||
int flagsTime = DateUtils.FORMAT_SHOW_TIME;
|
||||
if (DateFormat.is24HourFormat(context)) {
|
||||
flagsTime |= DateUtils.FORMAT_24HOUR;
|
||||
}
|
||||
|
||||
String datetimeString = null;
|
||||
if (allDay) {
|
||||
// For multi-day allday events or single-day all-day events that are not
|
||||
// today or tomorrow, use framework formatter.
|
||||
Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
|
||||
datetimeString = DateUtils.formatDateRange(context, f, startMillis,
|
||||
endMillis, flagsDate, Time.TIMEZONE_UTC).toString();
|
||||
} else {
|
||||
// For multiday events, shorten day/month names.
|
||||
// Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
|
||||
int flagsDatetime = flagsDate | flagsTime | DateUtils.FORMAT_ABBREV_MONTH |
|
||||
DateUtils.FORMAT_ABBREV_WEEKDAY;
|
||||
datetimeString = DateUtils.formatDateRange(context, startMillis, endMillis,
|
||||
flagsDatetime);
|
||||
}
|
||||
return datetimeString;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
refresh();
|
||||
}
|
||||
}
|
@ -0,0 +1,424 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.TabLayout
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.app.FragmentPagerAdapter
|
||||
import android.support.v4.view.ViewPager
|
||||
import android.text.format.DateFormat
|
||||
import android.text.format.DateUtils
|
||||
import android.text.format.Time
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import at.bitfire.ical4android.Event
|
||||
import at.bitfire.ical4android.InvalidCalendarException
|
||||
import at.bitfire.vcard4android.Contact
|
||||
import com.etesync.syncadapter.App
|
||||
import com.etesync.syncadapter.Constants
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.model.JournalEntity
|
||||
import com.etesync.syncadapter.model.SyncEntry
|
||||
import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment.Companion.setJournalEntryView
|
||||
import ezvcard.util.PartialDate
|
||||
import org.apache.commons.codec.Charsets
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class JournalItemActivity : BaseActivity(), Refreshable {
|
||||
private var journalEntity: JournalEntity? = null
|
||||
protected lateinit var info: CollectionInfo
|
||||
private lateinit var syncEntry: SyncEntry
|
||||
|
||||
override fun refresh() {
|
||||
val data = (applicationContext as App).data
|
||||
|
||||
journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid)
|
||||
if (journalEntity == null || journalEntity!!.isDeleted) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
info = journalEntity!!.info
|
||||
|
||||
title = info.displayName
|
||||
|
||||
setJournalEntryView(findViewById(R.id.journal_list_item), info, syncEntry)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.journal_item_activity)
|
||||
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
info = intent.extras!!.getSerializable(Constants.KEY_COLLECTION_INFO) as CollectionInfo
|
||||
syncEntry = intent.extras!!.getSerializable(KEY_SYNC_ENTRY) as SyncEntry
|
||||
|
||||
refresh()
|
||||
|
||||
val viewPager = findViewById<View>(R.id.viewpager) as ViewPager
|
||||
viewPager.adapter = TabsAdapter(supportFragmentManager, this, info, syncEntry)
|
||||
|
||||
val tabLayout = findViewById<View>(R.id.tabs) as TabLayout
|
||||
tabLayout.setupWithViewPager(viewPager)
|
||||
}
|
||||
|
||||
private class TabsAdapter(fm: FragmentManager, private val context: Context, private val info: CollectionInfo, private val syncEntry: SyncEntry) : FragmentPagerAdapter(fm) {
|
||||
|
||||
override fun getCount(): Int {
|
||||
// FIXME: Make it depend on info type (only have non-raw for known types)
|
||||
return 2
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence? {
|
||||
return if (position == 0) {
|
||||
context.getString(R.string.journal_item_tab_main)
|
||||
} else {
|
||||
context.getString(R.string.journal_item_tab_raw)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
return if (position == 0) {
|
||||
PrettyFragment.newInstance(info, syncEntry)
|
||||
} else {
|
||||
TextFragment.newInstance(syncEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val v = inflater.inflate(R.layout.text_fragment, container, false)
|
||||
|
||||
val tv = v.findViewById<View>(R.id.content) as TextView
|
||||
|
||||
val syncEntry = arguments!!.getSerializable(KEY_SYNC_ENTRY) as SyncEntry
|
||||
tv.text = syncEntry.content
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(syncEntry: SyncEntry): TextFragment {
|
||||
val frag = TextFragment()
|
||||
val args = Bundle(1)
|
||||
args.putSerializable(KEY_SYNC_ENTRY, syncEntry)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PrettyFragment : Fragment() {
|
||||
internal lateinit var info: CollectionInfo
|
||||
internal lateinit var syncEntry: SyncEntry
|
||||
private var asyncTask: AsyncTask<*, *, *>? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
var v: View? = null
|
||||
|
||||
info = arguments!!.getSerializable(Constants.KEY_COLLECTION_INFO) as CollectionInfo
|
||||
syncEntry = arguments!!.getSerializable(KEY_SYNC_ENTRY) as SyncEntry
|
||||
|
||||
when (info.type) {
|
||||
CollectionInfo.Type.ADDRESS_BOOK -> {
|
||||
v = inflater.inflate(R.layout.contact_info, container, false)
|
||||
asyncTask = LoadContactTask(v).execute()
|
||||
}
|
||||
CollectionInfo.Type.CALENDAR -> {
|
||||
v = inflater.inflate(R.layout.event_info, container, false)
|
||||
asyncTask = LoadEventTask(v).execute()
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (asyncTask != null)
|
||||
asyncTask!!.cancel(true)
|
||||
}
|
||||
|
||||
private inner class LoadEventTask internal constructor(internal var view: View) : AsyncTask<Void, Void, Event>() {
|
||||
override fun doInBackground(vararg aVoids: Void): Event? {
|
||||
val `is` = ByteArrayInputStream(syncEntry.content.toByteArray(Charsets.UTF_8))
|
||||
|
||||
try {
|
||||
return Event.fromStream(`is`, Charsets.UTF_8, null)[0]
|
||||
} catch (e: InvalidCalendarException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onPostExecute(event: Event) {
|
||||
val loader = view.findViewById<View>(R.id.event_info_loading_msg)
|
||||
loader.visibility = View.GONE
|
||||
val contentContainer = view.findViewById<View>(R.id.event_info_scroll_view)
|
||||
contentContainer.visibility = View.VISIBLE
|
||||
|
||||
setTextViewText(view, R.id.title, event.summary)
|
||||
|
||||
setTextViewText(view, R.id.when_datetime, getDisplayedDatetime(event.dtStart.date.time, event.dtEnd.date.time, event.isAllDay, context))
|
||||
|
||||
setTextViewText(view, R.id.where, event.location)
|
||||
|
||||
if (event.organizer != null) {
|
||||
val tv = view.findViewById<View>(R.id.organizer) as TextView
|
||||
tv.text = event.organizer.calAddress.toString().replaceFirst("mailto:".toRegex(), "")
|
||||
} else {
|
||||
val organizer = view.findViewById<View>(R.id.organizer_container)
|
||||
organizer.visibility = View.GONE
|
||||
}
|
||||
|
||||
setTextViewText(view, R.id.description, event.description)
|
||||
|
||||
var first = true
|
||||
var sb = StringBuilder()
|
||||
for (attendee in event.attendees) {
|
||||
if (first) {
|
||||
first = false
|
||||
sb.append(getString(R.string.journal_item_attendees)).append(": ")
|
||||
} else {
|
||||
sb.append(", ")
|
||||
}
|
||||
sb.append(attendee.calAddress.toString().replaceFirst("mailto:".toRegex(), ""))
|
||||
}
|
||||
setTextViewText(view, R.id.attendees, sb.toString())
|
||||
|
||||
first = true
|
||||
sb = StringBuilder()
|
||||
for (alarm in event.alarms) {
|
||||
if (first) {
|
||||
first = false
|
||||
sb.append(getString(R.string.journal_item_reminders)).append(": ")
|
||||
} else {
|
||||
sb.append(", ")
|
||||
}
|
||||
sb.append(alarm.trigger.value)
|
||||
}
|
||||
setTextViewText(view, R.id.reminders, sb.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LoadContactTask internal constructor(internal var view: View) : AsyncTask<Void, Void, Contact>() {
|
||||
|
||||
override fun doInBackground(vararg aVoids: Void): Contact? {
|
||||
val `is` = ByteArrayInputStream(syncEntry.content.toByteArray(Charsets.UTF_8))
|
||||
|
||||
try {
|
||||
return Contact.fromStream(`is`, Charsets.UTF_8, null)[0]
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onPostExecute(contact: Contact) {
|
||||
val loader = view.findViewById<View>(R.id.loading_msg)
|
||||
loader.visibility = View.GONE
|
||||
val contentContainer = view.findViewById<View>(R.id.content_container)
|
||||
contentContainer.visibility = View.VISIBLE
|
||||
|
||||
val tv = view.findViewById<View>(R.id.display_name) as TextView
|
||||
tv.text = contact.displayName
|
||||
|
||||
if (contact.group) {
|
||||
showGroup(contact)
|
||||
} else {
|
||||
showContact(contact)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showGroup(contact: Contact) {
|
||||
val mainCard = view.findViewById<View>(R.id.main_card) as ViewGroup
|
||||
|
||||
addInfoItem(view.context, mainCard, getString(R.string.journal_item_member_count), null, contact.members.size.toString())
|
||||
|
||||
for (member in contact.members) {
|
||||
addInfoItem(view.context, mainCard, getString(R.string.journal_item_member), null, member)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun showContact(contact: Contact) {
|
||||
val mainCard = view.findViewById<View>(R.id.main_card) as ViewGroup
|
||||
val aboutCard = view.findViewById<View>(R.id.about_card) as ViewGroup
|
||||
aboutCard.findViewById<View>(R.id.title_container).visibility = View.VISIBLE
|
||||
|
||||
// TEL
|
||||
for (labeledPhone in contact.phoneNumbers) {
|
||||
val types = labeledPhone.property.types
|
||||
val type = if (types.size > 0) types[0].value else null
|
||||
addInfoItem(view.context, mainCard, getString(R.string.journal_item_phone), type, labeledPhone.property.text)
|
||||
}
|
||||
|
||||
// EMAIL
|
||||
for (labeledEmail in contact.emails) {
|
||||
val types = labeledEmail.property.types
|
||||
val type = if (types.size > 0) types[0].value else null
|
||||
addInfoItem(view.context, mainCard, getString(R.string.journal_item_email), type, labeledEmail.property.value)
|
||||
}
|
||||
|
||||
// ORG, TITLE, ROLE
|
||||
if (contact.organization != null) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_organization), contact.jobTitle, contact.organization.values[0])
|
||||
}
|
||||
if (contact.jobDescription != null) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_job_description), null, contact.jobTitle)
|
||||
}
|
||||
|
||||
// IMPP
|
||||
for (labeledImpp in contact.impps) {
|
||||
addInfoItem(view.context, mainCard, getString(R.string.journal_item_impp), labeledImpp.property.protocol, labeledImpp.property.handle)
|
||||
}
|
||||
|
||||
// NICKNAME
|
||||
if (contact.nickName != null && contact.nickName.values.size > 0) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_nickname), null, contact.nickName.values[0])
|
||||
}
|
||||
|
||||
// ADR
|
||||
for (labeledAddress in contact.addresses) {
|
||||
val types = labeledAddress.property.types
|
||||
val type = if (types.size > 0) types[0].value else null
|
||||
addInfoItem(view.context, mainCard, getString(R.string.journal_item_address), type, labeledAddress.property.label)
|
||||
}
|
||||
|
||||
// NOTE
|
||||
if (contact.note != null) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_note), null, contact.note)
|
||||
}
|
||||
|
||||
// URL
|
||||
for (labeledUrl in contact.urls) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_website), null, labeledUrl.property.value)
|
||||
}
|
||||
|
||||
// ANNIVERSARY
|
||||
if (contact.anniversary != null) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_anniversary), null, getDisplayedDate(contact.anniversary.date, contact.anniversary.partialDate))
|
||||
}
|
||||
// BDAY
|
||||
if (contact.birthDay != null) {
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_birthday), null, getDisplayedDate(contact.birthDay.date, contact.birthDay.partialDate))
|
||||
}
|
||||
|
||||
// RELATED
|
||||
for (related in contact.relations) {
|
||||
val types = related.types
|
||||
val type = if (types.size > 0) types[0].value else null
|
||||
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_relation), type, related.text)
|
||||
}
|
||||
|
||||
// PHOTO
|
||||
// if (contact.photo != null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDisplayedDate(date: Date?, partialDate: PartialDate): String? {
|
||||
if (date != null) {
|
||||
val epochDate = date.time
|
||||
return getDisplayedDatetime(epochDate, epochDate, true, context)
|
||||
} else {
|
||||
val formatter = SimpleDateFormat("d MMMM", Locale.getDefault())
|
||||
val calendar = GregorianCalendar()
|
||||
calendar.set(Calendar.DAY_OF_MONTH, partialDate.date!!)
|
||||
calendar.set(Calendar.MONTH, partialDate.month!! - 1)
|
||||
return formatter.format(calendar.time)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(info: CollectionInfo, syncEntry: SyncEntry): PrettyFragment {
|
||||
val frag = PrettyFragment()
|
||||
val args = Bundle(1)
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info)
|
||||
args.putSerializable(KEY_SYNC_ENTRY, syncEntry)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
|
||||
private fun addInfoItem(context: Context, parent: ViewGroup, type: String, label: String?, value: String?): View {
|
||||
val layout = parent.findViewById<View>(R.id.container) as ViewGroup
|
||||
val infoItem = LayoutInflater.from(context).inflate(R.layout.contact_info_item, layout, false)
|
||||
layout.addView(infoItem)
|
||||
setTextViewText(infoItem, R.id.type, type)
|
||||
setTextViewText(infoItem, R.id.title, label)
|
||||
setTextViewText(infoItem, R.id.content, value)
|
||||
parent.visibility = View.VISIBLE
|
||||
|
||||
return infoItem
|
||||
}
|
||||
|
||||
private fun setTextViewText(parent: View, id: Int, text: String?) {
|
||||
val tv = parent.findViewById<View>(id) as TextView
|
||||
if (text == null) {
|
||||
tv.visibility = View.GONE
|
||||
} else {
|
||||
tv.text = text
|
||||
}
|
||||
}
|
||||
|
||||
fun getDisplayedDatetime(startMillis: Long, endMillis: Long, allDay: Boolean, context: Context?): String? {
|
||||
// Configure date/time formatting.
|
||||
val flagsDate = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY
|
||||
var flagsTime = DateUtils.FORMAT_SHOW_TIME
|
||||
if (DateFormat.is24HourFormat(context)) {
|
||||
flagsTime = flagsTime or DateUtils.FORMAT_24HOUR
|
||||
}
|
||||
|
||||
var datetimeString: String? = null
|
||||
if (allDay) {
|
||||
// For multi-day allday events or single-day all-day events that are not
|
||||
// today or tomorrow, use framework formatter.
|
||||
val f = Formatter(StringBuilder(50), Locale.getDefault())
|
||||
datetimeString = DateUtils.formatDateRange(context, f, startMillis,
|
||||
endMillis, flagsDate, Time.TIMEZONE_UTC).toString()
|
||||
} else {
|
||||
// For multiday events, shorten day/month names.
|
||||
// Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
|
||||
val flagsDatetime = flagsDate or flagsTime or DateUtils.FORMAT_ABBREV_MONTH or
|
||||
DateUtils.FORMAT_ABBREV_WEEKDAY
|
||||
datetimeString = DateUtils.formatDateRange(context, startMillis, endMillis,
|
||||
flagsDatetime)
|
||||
}
|
||||
return datetimeString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refresh()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY_SYNC_ENTRY = "syncEntry"
|
||||
|
||||
fun newIntent(context: Context, info: CollectionInfo, syncEntry: SyncEntry): Intent {
|
||||
val intent = Intent(context, JournalItemActivity::class.java)
|
||||
intent.putExtra(Constants.KEY_COLLECTION_INFO, info)
|
||||
intent.putExtra(KEY_SYNC_ENTRY, syncEntry)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
public interface Refreshable {
|
||||
public void refresh();
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
interface Refreshable {
|
||||
fun refresh()
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.HttpClient;
|
||||
import com.etesync.syncadapter.InvalidAccountException;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.JournalManager;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class RemoveMemberFragment extends DialogFragment {
|
||||
final static private String KEY_MEMBER = "memberEmail";
|
||||
private AccountSettings settings;
|
||||
private OkHttpClient httpClient;
|
||||
private HttpUrl remote;
|
||||
private CollectionInfo info;
|
||||
private String memberEmail;
|
||||
|
||||
public static RemoveMemberFragment newInstance(Account account, CollectionInfo info, String email) {
|
||||
RemoveMemberFragment frag = new RemoveMemberFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(Constants.KEY_ACCOUNT, account);
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||
args.putString(KEY_MEMBER, email);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Account account = getArguments().getParcelable(Constants.KEY_ACCOUNT);
|
||||
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||
memberEmail = getArguments().getString(KEY_MEMBER);
|
||||
try {
|
||||
settings = new AccountSettings(getContext(), account);
|
||||
httpClient = HttpClient.create(getContext(), settings);
|
||||
} catch (InvalidAccountException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
remote = HttpUrl.get(settings.getUri());
|
||||
|
||||
new MemberRemove().execute();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progress = new ProgressDialog(getContext());
|
||||
progress.setTitle(R.string.collection_members_removing);
|
||||
progress.setMessage(getString(R.string.please_wait));
|
||||
progress.setIndeterminate(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
setCancelable(false);
|
||||
return progress;
|
||||
}
|
||||
|
||||
private class MemberRemove extends AsyncTask<Void, Void, MemberRemove.RemoveResult> {
|
||||
@Override
|
||||
protected RemoveResult doInBackground(Void... voids) {
|
||||
try {
|
||||
JournalManager journalsManager = new JournalManager(httpClient, remote);
|
||||
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(info.uid);
|
||||
|
||||
JournalManager.Member member = new JournalManager.Member(memberEmail, "placeholder".getBytes(Charsets.UTF_8));
|
||||
journalsManager.deleteMember(journal, member);
|
||||
|
||||
return new RemoveResult(null);
|
||||
} catch (Exception e) {
|
||||
return new RemoveResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(RemoveResult result) {
|
||||
if (result.throwable == null) {
|
||||
((Refreshable) getActivity()).refresh();
|
||||
} else {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.collection_members_remove_error)
|
||||
.setMessage(result.throwable.getMessage())
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
class RemoveResult {
|
||||
final Throwable throwable;
|
||||
|
||||
RemoveResult(final Throwable throwable) {
|
||||
this.throwable = throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Dialog
|
||||
import android.app.ProgressDialog
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.etesync.syncadapter.*
|
||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import org.apache.commons.codec.Charsets
|
||||
|
||||
class RemoveMemberFragment : DialogFragment() {
|
||||
private var settings: AccountSettings? = null
|
||||
private var httpClient: OkHttpClient? = null
|
||||
private var remote: HttpUrl? = null
|
||||
private var info: CollectionInfo? = null
|
||||
private var memberEmail: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val account = arguments!!.getParcelable<Account>(Constants.KEY_ACCOUNT)
|
||||
info = arguments!!.getSerializable(Constants.KEY_COLLECTION_INFO) as CollectionInfo
|
||||
memberEmail = arguments!!.getString(KEY_MEMBER)
|
||||
try {
|
||||
settings = AccountSettings(context!!, account!!)
|
||||
httpClient = HttpClient.create(context!!, settings!!)
|
||||
} catch (e: InvalidAccountException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
remote = HttpUrl.get(settings!!.uri!!)
|
||||
|
||||
MemberRemove().execute()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(context)
|
||||
progress.setTitle(R.string.collection_members_removing)
|
||||
progress.setMessage(getString(R.string.please_wait))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
}
|
||||
|
||||
private inner class MemberRemove : AsyncTask<Void, Void, MemberRemove.RemoveResult>() {
|
||||
override fun doInBackground(vararg voids: Void): RemoveResult {
|
||||
try {
|
||||
val journalsManager = JournalManager(httpClient!!, remote!!)
|
||||
val journal = JournalManager.Journal.fakeWithUid(info!!.uid)
|
||||
|
||||
val member = JournalManager.Member(memberEmail!!, "placeholder".toByteArray(Charsets.UTF_8))
|
||||
journalsManager.deleteMember(journal, member)
|
||||
|
||||
return RemoveResult(null)
|
||||
} catch (e: Exception) {
|
||||
return RemoveResult(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: RemoveResult) {
|
||||
if (result.throwable == null) {
|
||||
(activity as Refreshable).refresh()
|
||||
} else {
|
||||
AlertDialog.Builder(activity!!)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setTitle(R.string.collection_members_remove_error)
|
||||
.setMessage(result.throwable.message)
|
||||
.setPositiveButton(android.R.string.yes) { dialog, which -> }.show()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
internal inner class RemoveResult(val throwable: Throwable?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY_MEMBER = "memberEmail"
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo, email: String): RemoveMemberFragment {
|
||||
val frag = RemoveMemberFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(Constants.KEY_ACCOUNT, account)
|
||||
args.putSerializable(Constants.KEY_COLLECTION_INFO, info)
|
||||
args.putString(KEY_MEMBER, email)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
package com.etesync.syncadapter.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceError;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.etesync.syncadapter.Constants;
|
||||
import com.etesync.syncadapter.R;
|
||||
|
||||
public class WebViewActivity extends BaseActivity {
|
||||
|
||||
private static final String KEY_URL = "url";
|
||||
private static final String QUERY_KEY_EMBEDDED = "embedded";
|
||||
|
||||
private WebView mWebView;
|
||||
private ProgressBar mProgressBar;
|
||||
private ActionBar mToolbar;
|
||||
|
||||
public static void openUrl(Context context, Uri uri) {
|
||||
if (isAllowedUrl(uri)) {
|
||||
Intent intent = new Intent(context, WebViewActivity.class);
|
||||
intent.putExtra(WebViewActivity.KEY_URL, uri);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_webview);
|
||||
|
||||
mToolbar = getSupportActionBar();
|
||||
mToolbar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
Uri uri = getIntent().getParcelableExtra(KEY_URL);
|
||||
uri = addQueryParams(uri);
|
||||
mWebView = (WebView) findViewById(R.id.webView);
|
||||
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
|
||||
mWebView.getSettings().setJavaScriptEnabled(true);
|
||||
if (savedInstanceState == null) {
|
||||
mWebView.loadUrl(uri.toString());
|
||||
}
|
||||
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
setTitle(view.getTitle());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return shouldOverrideUrl(Uri.parse(url));
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
return shouldOverrideUrl(request.getUrl());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||
loadErrorPage(failingUrl);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
loadErrorPage(request.getUrl().toString());
|
||||
}
|
||||
});
|
||||
|
||||
mWebView.setWebChromeClient(new WebChromeClient() {
|
||||
|
||||
public void onProgressChanged(WebView view, int progress) {
|
||||
if (progress == 100) {
|
||||
mToolbar.setTitle(view.getTitle());
|
||||
mProgressBar.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
mToolbar.setTitle(R.string.loading);
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressBar.setProgress(progress);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Uri addQueryParams(Uri uri) {
|
||||
return uri.buildUpon().appendQueryParameter(QUERY_KEY_EMBEDDED, "1").build();
|
||||
}
|
||||
|
||||
private void loadErrorPage(String failingUrl) {
|
||||
String htmlData = "<html><title>" +
|
||||
getString(R.string.loading_error_title) +
|
||||
"</title>" +
|
||||
"<style>" +
|
||||
".btn {" +
|
||||
" display: inline-block;" +
|
||||
" padding: 6px 12px;" +
|
||||
" font-size: 20px;" +
|
||||
" font-weight: 400;" +
|
||||
" line-height: 1.42857143;" +
|
||||
" text-align: center;" +
|
||||
" white-space: nowrap;" +
|
||||
" vertical-align: middle;" +
|
||||
" touch-action: manipulation;" +
|
||||
" cursor: pointer;" +
|
||||
" user-select: none;" +
|
||||
" border: 1px solid #ccc;" +
|
||||
" border-radius: 4px;" +
|
||||
" color: #333;" +
|
||||
" text-decoration: none;" +
|
||||
" margin-top: 50px;" +
|
||||
"}" +
|
||||
"</style>" +
|
||||
"<body>" +
|
||||
"<div align=\"center\">" +
|
||||
"<a class=\"btn\" href=\"" + failingUrl + "\">" + getString(R.string.loading_error_content) +
|
||||
"</a>" +
|
||||
"</form></body></html>";
|
||||
|
||||
mWebView.loadDataWithBaseURL("about:blank", htmlData, "text/html", "UTF-8", null);
|
||||
mWebView.invalidate();
|
||||
}
|
||||
|
||||
private static boolean uriEqual(Uri uri1, Uri uri2) {
|
||||
return uri1.getHost().equals(uri2.getHost()) &&
|
||||
uri1.getPath().equals(uri2.getPath());
|
||||
}
|
||||
|
||||
private static boolean allowedUris(Uri allowedUris[], Uri uri2) {
|
||||
for (Uri uri : allowedUris) {
|
||||
if (uriEqual(uri, uri2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isAllowedUrl(Uri uri) {
|
||||
final Uri allowedUris[] = new Uri[]{
|
||||
Constants.faqUri,
|
||||
Constants.helpUri,
|
||||
Constants.registrationUrl,
|
||||
Constants.webUri.buildUpon().appendEncodedPath("tos/").build(),
|
||||
Constants.webUri.buildUpon().appendEncodedPath("about/").build(),
|
||||
};
|
||||
final Uri accountsUri = Constants.webUri.buildUpon().appendEncodedPath("accounts/").build();
|
||||
|
||||
return (allowedUris(allowedUris, uri) ||
|
||||
(uri.getHost().equals(accountsUri.getHost()) &&
|
||||
(uri.getPath().startsWith(accountsUri.getPath())))
|
||||
);
|
||||
}
|
||||
|
||||
private boolean shouldOverrideUrl(Uri uri) {
|
||||
if (isAllowedUrl(uri)) {
|
||||
if (uri.getQueryParameter(QUERY_KEY_EMBEDDED) != null) {
|
||||
return false;
|
||||
} else {
|
||||
uri = addQueryParams(uri);
|
||||
mWebView.loadUrl(uri.toString());
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, uri));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
mWebView.saveState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
mWebView.restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (mWebView.canGoBack()) {
|
||||
mWebView.goBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
package com.etesync.syncadapter.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.ActionBar
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import android.widget.ProgressBar
|
||||
import com.etesync.syncadapter.Constants
|
||||
import com.etesync.syncadapter.R
|
||||
|
||||
class WebViewActivity : BaseActivity() {
|
||||
|
||||
private var mWebView: WebView? = null
|
||||
private var mProgressBar: ProgressBar? = null
|
||||
private var mToolbar: ActionBar? = null
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_webview)
|
||||
|
||||
mToolbar = supportActionBar
|
||||
mToolbar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
var uri = intent.getParcelableExtra<Uri>(KEY_URL)
|
||||
uri = addQueryParams(uri)
|
||||
mWebView = findViewById<View>(R.id.webView) as WebView
|
||||
mProgressBar = findViewById<View>(R.id.progressBar) as ProgressBar
|
||||
|
||||
mWebView!!.settings.javaScriptEnabled = true
|
||||
if (savedInstanceState == null) {
|
||||
mWebView!!.loadUrl(uri.toString())
|
||||
}
|
||||
|
||||
mWebView!!.webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
title = view.title
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
return shouldOverrideUrl(Uri.parse(url))
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
|
||||
return shouldOverrideUrl(request.url)
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
||||
loadErrorPage(failingUrl)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
|
||||
loadErrorPage(request.url.toString())
|
||||
}
|
||||
}
|
||||
|
||||
mWebView!!.webChromeClient = object : WebChromeClient() {
|
||||
|
||||
override fun onProgressChanged(view: WebView, progress: Int) {
|
||||
if (progress == 100) {
|
||||
mToolbar!!.title = view.title
|
||||
mProgressBar!!.visibility = View.INVISIBLE
|
||||
} else {
|
||||
mToolbar!!.setTitle(R.string.loading)
|
||||
mProgressBar!!.visibility = View.VISIBLE
|
||||
mProgressBar!!.progress = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addQueryParams(uri: Uri): Uri {
|
||||
return uri.buildUpon().appendQueryParameter(QUERY_KEY_EMBEDDED, "1").build()
|
||||
}
|
||||
|
||||
private fun loadErrorPage(failingUrl: String) {
|
||||
val htmlData = "<html><title>" +
|
||||
getString(R.string.loading_error_title) +
|
||||
"</title>" +
|
||||
"<style>" +
|
||||
".btn {" +
|
||||
" display: inline-block;" +
|
||||
" padding: 6px 12px;" +
|
||||
" font-size: 20px;" +
|
||||
" font-weight: 400;" +
|
||||
" line-height: 1.42857143;" +
|
||||
" text-align: center;" +
|
||||
" white-space: nowrap;" +
|
||||
" vertical-align: middle;" +
|
||||
" touch-action: manipulation;" +
|
||||
" cursor: pointer;" +
|
||||
" user-select: none;" +
|
||||
" border: 1px solid #ccc;" +
|
||||
" border-radius: 4px;" +
|
||||
" color: #333;" +
|
||||
" text-decoration: none;" +
|
||||
" margin-top: 50px;" +
|
||||
"}" +
|
||||
"</style>" +
|
||||
"<body>" +
|
||||
"<div align=\"center\">" +
|
||||
"<a class=\"btn\" href=\"" + failingUrl + "\">" + getString(R.string.loading_error_content) +
|
||||
"</a>" +
|
||||
"</form></body></html>"
|
||||
|
||||
mWebView!!.loadDataWithBaseURL("about:blank", htmlData, "text/html", "UTF-8", null)
|
||||
mWebView!!.invalidate()
|
||||
}
|
||||
|
||||
private fun shouldOverrideUrl(uri: Uri): Boolean {
|
||||
var uri = uri
|
||||
if (isAllowedUrl(uri)) {
|
||||
if (uri.getQueryParameter(QUERY_KEY_EMBEDDED) != null) {
|
||||
return false
|
||||
} else {
|
||||
uri = addQueryParams(uri)
|
||||
mWebView!!.loadUrl(uri.toString())
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, uri))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
mWebView!!.saveState(outState)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
mWebView!!.restoreState(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (mWebView!!.canGoBack()) {
|
||||
mWebView!!.goBack()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val KEY_URL = "url"
|
||||
private val QUERY_KEY_EMBEDDED = "embedded"
|
||||
|
||||
fun openUrl(context: Context, uri: Uri) {
|
||||
if (isAllowedUrl(uri)) {
|
||||
val intent = Intent(context, WebViewActivity::class.java)
|
||||
intent.putExtra(WebViewActivity.KEY_URL, uri)
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
|
||||
}
|
||||
}
|
||||
|
||||
private fun uriEqual(uri1: Uri, uri2: Uri): Boolean {
|
||||
return uri1.host == uri2.host && uri1.path == uri2.path
|
||||
}
|
||||
|
||||
private fun allowedUris(allowedUris: Array<Uri>, uri2: Uri): Boolean {
|
||||
for (uri in allowedUris) {
|
||||
if (uriEqual(uri, uri2)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isAllowedUrl(uri: Uri): Boolean {
|
||||
val allowedUris = arrayOf(Constants.faqUri, Constants.helpUri, Constants.registrationUrl, Constants.webUri.buildUpon().appendEncodedPath("tos/").build(), Constants.webUri.buildUpon().appendEncodedPath("about/").build())
|
||||
val accountsUri = Constants.webUri.buildUpon().appendEncodedPath("accounts/").build()
|
||||
|
||||
return allowedUris(allowedUris, uri) || uri.host == accountsUri.host && uri.path!!.startsWith(accountsUri.path!!)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
class AccountResolver {
|
||||
private Context context;
|
||||
private HashMap<String, AccountInfo> cache;
|
||||
|
||||
public AccountResolver(Context context) {
|
||||
this.context = context;
|
||||
this.cache = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public AccountInfo resolve(String accountName) {
|
||||
// Hardcoded swaps for known accounts:
|
||||
if (accountName.equals("com.google")) {
|
||||
accountName = "com.google.android.googlequicksearchbox";
|
||||
} else if (accountName.equals(App.getAddressBookAccountType())) {
|
||||
accountName = App.getAccountType();
|
||||
} else if (accountName.equals("at.bitfire.davdroid.address_book")) {
|
||||
accountName = "at.bitfire.davdroid";
|
||||
}
|
||||
|
||||
AccountInfo ret = cache.get(accountName);
|
||||
if (ret == null) {
|
||||
try {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(accountName, 0);
|
||||
String name = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo).toString() : accountName);
|
||||
Drawable icon = context.getPackageManager().getApplicationIcon(accountName);
|
||||
ret = new AccountInfo(name, icon);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
ret = new AccountInfo(accountName, ContextCompat.getDrawable(context, R.drawable.ic_account_dark));
|
||||
}
|
||||
cache.put(accountName, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static class AccountInfo {
|
||||
final String name;
|
||||
final Drawable icon;
|
||||
|
||||
AccountInfo(String name, Drawable icon) {
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.support.v4.content.ContextCompat
|
||||
import com.etesync.syncadapter.App
|
||||
import com.etesync.syncadapter.R
|
||||
import java.util.*
|
||||
|
||||
internal class AccountResolver(private val context: Context) {
|
||||
private val cache: HashMap<String, AccountInfo>
|
||||
|
||||
init {
|
||||
this.cache = LinkedHashMap()
|
||||
}
|
||||
|
||||
fun resolve(accountName: String): AccountInfo {
|
||||
var accountName = accountName
|
||||
// Hardcoded swaps for known accounts:
|
||||
if (accountName == "com.google") {
|
||||
accountName = "com.google.android.googlequicksearchbox"
|
||||
} else if (accountName == App.getAddressBookAccountType()) {
|
||||
accountName = App.getAccountType()
|
||||
} else if (accountName == "at.bitfire.davdroid.address_book") {
|
||||
accountName = "at.bitfire.davdroid"
|
||||
}
|
||||
|
||||
var ret: AccountInfo? = cache[accountName]
|
||||
if (ret == null) {
|
||||
try {
|
||||
val packageManager = context.packageManager
|
||||
val applicationInfo = packageManager.getApplicationInfo(accountName, 0)
|
||||
val name = if (applicationInfo != null) packageManager.getApplicationLabel(applicationInfo).toString() else accountName
|
||||
val icon = context.packageManager.getApplicationIcon(accountName)
|
||||
ret = AccountInfo(name, icon)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
ret = AccountInfo(accountName, ContextCompat.getDrawable(context, R.drawable.ic_account_dark)!!)
|
||||
}
|
||||
|
||||
cache[accountName] = ret!!
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
class AccountInfo internal constructor(internal val name: String, internal val icon: Drawable)
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Calendars;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.provider.ContactsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.resource.LocalCalendar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by tal on 27/03/17.
|
||||
*/
|
||||
|
||||
public class CalendarAccount {
|
||||
private Account account;
|
||||
private List<LocalCalendar> calendars = new ArrayList<>();
|
||||
|
||||
private static final String[] CAL_COLS = new String[]{
|
||||
Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE,
|
||||
Calendars.DELETED, Calendars.NAME};
|
||||
|
||||
protected CalendarAccount(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
// Load all available calendars.
|
||||
// If an empty list is returned the caller probably needs to enable calendar
|
||||
// read permissions in App Ops/XPrivacy etc.
|
||||
public static List<CalendarAccount> loadAll(ContentResolver resolver) {
|
||||
|
||||
if (missing(resolver, Calendars.CONTENT_URI) || missing(resolver, Events.CONTENT_URI))
|
||||
return new ArrayList<>();
|
||||
|
||||
Cursor cur;
|
||||
try {
|
||||
cur = resolver.query(Calendars.CONTENT_URI,
|
||||
CAL_COLS, null, null,
|
||||
ContactsContract.RawContacts.ACCOUNT_NAME + " ASC, " + ContactsContract.RawContacts.ACCOUNT_TYPE);
|
||||
} catch (Exception except) {
|
||||
App.log.warning("Calendar provider is missing columns, continuing anyway");
|
||||
cur = resolver.query(Calendars.CONTENT_URI, null, null, null, null);
|
||||
except.printStackTrace();
|
||||
}
|
||||
List<CalendarAccount> calendarAccounts = new ArrayList<>(cur.getCount());
|
||||
|
||||
CalendarAccount calendarAccount = null;
|
||||
|
||||
ContentProviderClient contentProviderClient = resolver.acquireContentProviderClient(CalendarContract.CONTENT_URI);
|
||||
while (cur.moveToNext()) {
|
||||
if (getLong(cur, Calendars.DELETED) != 0)
|
||||
continue;
|
||||
|
||||
String accountName = getString(cur, Calendars.ACCOUNT_NAME);
|
||||
String accountType = getString(cur, Calendars.ACCOUNT_TYPE);
|
||||
if (calendarAccount == null ||
|
||||
!calendarAccount.getAccountName().equals(accountName) ||
|
||||
!calendarAccount.getAccountType().equals(accountType)) {
|
||||
calendarAccount = new CalendarAccount(new Account(accountName, accountType));
|
||||
calendarAccounts.add(calendarAccount);
|
||||
}
|
||||
|
||||
try {
|
||||
LocalCalendar localCalendar = LocalCalendar.findByName(calendarAccount.getAccount(),
|
||||
contentProviderClient,
|
||||
LocalCalendar.Factory.INSTANCE, getString(cur, Calendars.NAME));
|
||||
if (localCalendar != null) calendarAccount.calendars.add(localCalendar);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
contentProviderClient.release();
|
||||
cur.close();
|
||||
return calendarAccounts;
|
||||
}
|
||||
|
||||
private static int getColumnIndex(Cursor cur, String dbName) {
|
||||
return dbName == null ? -1 : cur.getColumnIndex(dbName);
|
||||
}
|
||||
|
||||
private static long getLong(Cursor cur, String dbName) {
|
||||
int i = getColumnIndex(cur, dbName);
|
||||
return i == -1 ? -1 : cur.getLong(i);
|
||||
}
|
||||
|
||||
private static String getString(Cursor cur, String dbName) {
|
||||
int i = getColumnIndex(cur, dbName);
|
||||
return i == -1 ? null : cur.getString(i);
|
||||
}
|
||||
|
||||
private static boolean missing(ContentResolver resolver, Uri uri) {
|
||||
// Determine if a provider is missing
|
||||
ContentProviderClient provider = resolver.acquireContentProviderClient(uri);
|
||||
if (provider != null)
|
||||
provider.release();
|
||||
return provider == null;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return account.name;
|
||||
}
|
||||
|
||||
public String getAccountType() {
|
||||
return account.type;
|
||||
}
|
||||
|
||||
public List<LocalCalendar> getCalendars() {
|
||||
return calendars;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return account.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.ContentResolver
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.CalendarContract
|
||||
import android.provider.CalendarContract.Calendars
|
||||
import android.provider.CalendarContract.Events
|
||||
import android.provider.ContactsContract
|
||||
import com.etesync.syncadapter.App
|
||||
import com.etesync.syncadapter.resource.LocalCalendar
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by tal on 27/03/17.
|
||||
*/
|
||||
|
||||
class CalendarAccount protected constructor(val account: Account) {
|
||||
private val calendars = ArrayList<LocalCalendar>()
|
||||
|
||||
val accountName: String
|
||||
get() = account.name
|
||||
|
||||
val accountType: String
|
||||
get() = account.type
|
||||
|
||||
fun getCalendars(): List<LocalCalendar> {
|
||||
return calendars
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return account.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val CAL_COLS = arrayOf(Calendars.ACCOUNT_NAME, Calendars.ACCOUNT_TYPE, Calendars.DELETED, Calendars.NAME)
|
||||
|
||||
// Load all available calendars.
|
||||
// If an empty list is returned the caller probably needs to enable calendar
|
||||
// read permissions in App Ops/XPrivacy etc.
|
||||
fun loadAll(resolver: ContentResolver): List<CalendarAccount> {
|
||||
|
||||
if (missing(resolver, Calendars.CONTENT_URI) || missing(resolver, Events.CONTENT_URI))
|
||||
return ArrayList()
|
||||
|
||||
var cur: Cursor?
|
||||
try {
|
||||
cur = resolver.query(Calendars.CONTENT_URI,
|
||||
CAL_COLS, null, null,
|
||||
ContactsContract.RawContacts.ACCOUNT_NAME + " ASC, " + ContactsContract.RawContacts.ACCOUNT_TYPE)
|
||||
} catch (except: Exception) {
|
||||
App.log.warning("Calendar provider is missing columns, continuing anyway")
|
||||
cur = resolver.query(Calendars.CONTENT_URI, null, null, null, null)
|
||||
except.printStackTrace()
|
||||
}
|
||||
|
||||
val calendarAccounts = ArrayList<CalendarAccount>(cur!!.count)
|
||||
|
||||
var calendarAccount: CalendarAccount? = null
|
||||
|
||||
val contentProviderClient = resolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)
|
||||
while (cur.moveToNext()) {
|
||||
if (getLong(cur, Calendars.DELETED) != 0L)
|
||||
continue
|
||||
|
||||
val accountName = getString(cur, Calendars.ACCOUNT_NAME)
|
||||
val accountType = getString(cur, Calendars.ACCOUNT_TYPE)
|
||||
if (calendarAccount == null ||
|
||||
calendarAccount.accountName != accountName ||
|
||||
calendarAccount.accountType != accountType) {
|
||||
calendarAccount = CalendarAccount(Account(accountName, accountType))
|
||||
calendarAccounts.add(calendarAccount)
|
||||
}
|
||||
|
||||
try {
|
||||
val localCalendar = LocalCalendar.findByName(calendarAccount.account,
|
||||
contentProviderClient,
|
||||
LocalCalendar.Factory.INSTANCE, getString(cur, Calendars.NAME))
|
||||
if (localCalendar != null) calendarAccount.calendars.add(localCalendar)
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
contentProviderClient!!.release()
|
||||
cur.close()
|
||||
return calendarAccounts
|
||||
}
|
||||
|
||||
private fun getColumnIndex(cur: Cursor?, dbName: String?): Int {
|
||||
return if (dbName == null) -1 else cur!!.getColumnIndex(dbName)
|
||||
}
|
||||
|
||||
private fun getLong(cur: Cursor?, dbName: String): Long {
|
||||
val i = getColumnIndex(cur, dbName)
|
||||
return if (i == -1) -1 else cur!!.getLong(i)
|
||||
}
|
||||
|
||||
private fun getString(cur: Cursor?, dbName: String): String? {
|
||||
val i = getColumnIndex(cur, dbName)
|
||||
return if (i == -1) null else cur!!.getString(i)
|
||||
}
|
||||
|
||||
private fun missing(resolver: ContentResolver, uri: Uri): Boolean {
|
||||
// Determine if a provider is missing
|
||||
val provider = resolver.acquireContentProviderClient(uri)
|
||||
provider?.release()
|
||||
return provider == null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.ui.BaseActivity;
|
||||
|
||||
public class ImportActivity extends BaseActivity implements SelectImportMethod, ResultFragment.OnImportCallback, DialogInterface {
|
||||
public final static String EXTRA_ACCOUNT = "account",
|
||||
EXTRA_COLLECTION_INFO = "collectionInfo";
|
||||
|
||||
private Account account;
|
||||
protected CollectionInfo info;
|
||||
|
||||
public static Intent newIntent(Context context, Account account, CollectionInfo info) {
|
||||
Intent intent = new Intent(context, ImportActivity.class);
|
||||
intent.putExtra(ImportActivity.EXTRA_ACCOUNT, account);
|
||||
intent.putExtra(ImportActivity.EXTRA_COLLECTION_INFO, info);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
setTitle(getString(R.string.import_dialog_title));
|
||||
|
||||
account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
|
||||
info = (CollectionInfo) getIntent().getExtras().getSerializable(EXTRA_COLLECTION_INFO);
|
||||
|
||||
if (savedInstanceState == null)
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(android.R.id.content, new ImportActivity.SelectImportFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importFile() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(ImportFragment.newInstance(account, info), null)
|
||||
.commit();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importAccount() {
|
||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content,
|
||||
LocalCalendarImportFragment.newInstance(account, info))
|
||||
.addToBackStack(LocalCalendarImportFragment.class.getName())
|
||||
.commit();
|
||||
} else if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content,
|
||||
LocalContactImportFragment.newInstance(account, info))
|
||||
.addToBackStack(LocalContactImportFragment.class.getName())
|
||||
.commit();
|
||||
}
|
||||
setTitle(getString(R.string.import_select_account));
|
||||
}
|
||||
|
||||
private void popBackStack() {
|
||||
if (!getSupportFragmentManager().popBackStackImmediate()) {
|
||||
finish();
|
||||
} else {
|
||||
setTitle(getString(R.string.import_dialog_title));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
popBackStack();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
popBackStack();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImportResult(ResultFragment.ImportResult importResult) {
|
||||
ResultFragment fragment = ResultFragment.newInstance(importResult);
|
||||
fragment.show(getSupportFragmentManager(), "importResult");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
public static class SelectImportFragment extends Fragment {
|
||||
|
||||
private SelectImportMethod mSelectImportMethod;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mSelectImportMethod = (SelectImportMethod) getActivity();
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(getActivity().toString()
|
||||
+ " must implement MyInterface ");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mSelectImportMethod = (SelectImportMethod) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString()
|
||||
+ " must implement MyInterface ");
|
||||
}
|
||||
}
|
||||
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.import_actions_list, container, false);
|
||||
|
||||
View card = v.findViewById(R.id.import_file);
|
||||
ImageView img = (ImageView) card.findViewById(R.id.action_icon);
|
||||
TextView text = (TextView) card.findViewById(R.id.action_text);
|
||||
img.setImageResource(R.drawable.ic_file_white);
|
||||
text.setText(R.string.import_button_file);
|
||||
card.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View aView) {
|
||||
mSelectImportMethod.importFile();
|
||||
}
|
||||
});
|
||||
|
||||
card = v.findViewById(R.id.import_account);
|
||||
img = (ImageView) card.findViewById(R.id.action_icon);
|
||||
text = (TextView) card.findViewById(R.id.action_text);
|
||||
img.setImageResource(R.drawable.ic_account_circle_white);
|
||||
text.setText(R.string.import_button_local);
|
||||
card.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View aView) {
|
||||
mSelectImportMethod.importAccount();
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.ui.BaseActivity
|
||||
|
||||
class ImportActivity : BaseActivity(), SelectImportMethod, ResultFragment.OnImportCallback, DialogInterface {
|
||||
|
||||
private lateinit var account: Account
|
||||
protected lateinit var info: CollectionInfo
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
title = getString(R.string.import_dialog_title)
|
||||
|
||||
account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)
|
||||
info = intent.extras!!.getSerializable(EXTRA_COLLECTION_INFO) as CollectionInfo
|
||||
|
||||
if (savedInstanceState == null)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(android.R.id.content, ImportActivity.SelectImportFragment())
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun importFile() {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(ImportFragment.newInstance(account, info), null)
|
||||
.commit()
|
||||
|
||||
}
|
||||
|
||||
override fun importAccount() {
|
||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content,
|
||||
LocalCalendarImportFragment.newInstance(account, info))
|
||||
.addToBackStack(LocalCalendarImportFragment::class.java.name)
|
||||
.commit()
|
||||
} else if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content,
|
||||
LocalContactImportFragment.newInstance(account, info))
|
||||
.addToBackStack(LocalContactImportFragment::class.java.name)
|
||||
.commit()
|
||||
}
|
||||
title = getString(R.string.import_select_account)
|
||||
}
|
||||
|
||||
private fun popBackStack() {
|
||||
if (!supportFragmentManager.popBackStackImmediate()) {
|
||||
finish()
|
||||
} else {
|
||||
title = getString(R.string.import_dialog_title)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
popBackStack()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
popBackStack()
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
override fun onImportResult(importResult: ResultFragment.ImportResult) {
|
||||
val fragment = ResultFragment.newInstance(importResult)
|
||||
fragment.show(supportFragmentManager, "importResult")
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
finish()
|
||||
}
|
||||
|
||||
|
||||
class SelectImportFragment : Fragment() {
|
||||
|
||||
private var mSelectImportMethod: SelectImportMethod? = null
|
||||
|
||||
override fun onAttach(context: Context?) {
|
||||
super.onAttach(context)
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mSelectImportMethod = activity as SelectImportMethod?
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(activity!!.toString() + " must implement MyInterface ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onAttach(activity: Activity?) {
|
||||
super.onAttach(activity)
|
||||
// This makes sure that the container activity has implemented
|
||||
// the callback interface. If not, it throws an exception
|
||||
try {
|
||||
mSelectImportMethod = activity as SelectImportMethod?
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(activity!!.toString() + " must implement MyInterface ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val v = inflater.inflate(R.layout.import_actions_list, container, false)
|
||||
|
||||
var card = v.findViewById<View>(R.id.import_file)
|
||||
var img = card.findViewById<View>(R.id.action_icon) as ImageView
|
||||
var text = card.findViewById<View>(R.id.action_text) as TextView
|
||||
img.setImageResource(R.drawable.ic_file_white)
|
||||
text.setText(R.string.import_button_file)
|
||||
card.setOnClickListener { mSelectImportMethod!!.importFile() }
|
||||
|
||||
card = v.findViewById(R.id.import_account)
|
||||
img = card.findViewById<View>(R.id.action_icon) as ImageView
|
||||
text = card.findViewById<View>(R.id.action_text) as TextView
|
||||
img.setImageResource(R.drawable.ic_account_circle_white)
|
||||
text.setText(R.string.import_button_local)
|
||||
card.setOnClickListener { mSelectImportMethod!!.importAccount() }
|
||||
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EXTRA_ACCOUNT = "account"
|
||||
val EXTRA_COLLECTION_INFO = "collectionInfo"
|
||||
|
||||
fun newIntent(context: Context, account: Account, info: CollectionInfo): Intent {
|
||||
val intent = Intent(context, ImportActivity::class.java)
|
||||
intent.putExtra(ImportActivity.EXTRA_ACCOUNT, account)
|
||||
intent.putExtra(ImportActivity.EXTRA_COLLECTION_INFO, info)
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.resource.LocalAddressBook;
|
||||
import com.etesync.syncadapter.resource.LocalCalendar;
|
||||
import com.etesync.syncadapter.resource.LocalContact;
|
||||
import com.etesync.syncadapter.resource.LocalEvent;
|
||||
import com.etesync.syncadapter.syncadapter.ContactsSyncManager;
|
||||
import com.etesync.syncadapter.ui.Refreshable;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Event;
|
||||
import at.bitfire.ical4android.InvalidCalendarException;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
|
||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
||||
import static com.etesync.syncadapter.Constants.KEY_COLLECTION_INFO;
|
||||
import static com.etesync.syncadapter.ui.importlocal.ResultFragment.ImportResult;
|
||||
|
||||
public class ImportFragment extends DialogFragment {
|
||||
private static final int REQUEST_CODE = 6384; // onActivityResult request
|
||||
|
||||
private static final String TAG_PROGRESS_MAX = "progressMax";
|
||||
|
||||
private Account account;
|
||||
private CollectionInfo info;
|
||||
private File importFile;
|
||||
|
||||
public static ImportFragment newInstance(Account account, CollectionInfo info) {
|
||||
ImportFragment frag = new ImportFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(KEY_ACCOUNT, account);
|
||||
args.putSerializable(KEY_COLLECTION_INFO, info);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setCancelable(false);
|
||||
setRetainInstance(true);
|
||||
|
||||
account = getArguments().getParcelable(KEY_ACCOUNT);
|
||||
info = (CollectionInfo) getArguments().getSerializable(KEY_COLLECTION_INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
chooseFile();
|
||||
} else {
|
||||
ImportResult data = new ImportResult();
|
||||
data.e = new Exception(getString(R.string.import_permission_required));
|
||||
((ResultFragment.OnImportCallback) getActivity()).onImportResult(data);
|
||||
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void requestPermissions() {
|
||||
requestPermissions(new String[]{
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
super.onCreateDialog(savedInstanceState);
|
||||
ProgressDialog progress = new ProgressDialog(getActivity());
|
||||
progress.setTitle(R.string.import_dialog_title);
|
||||
progress.setMessage(getString(R.string.import_dialog_loading_file));
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
progress.setIndeterminate(false);
|
||||
progress.setIcon(R.drawable.ic_import_export_black);
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
requestPermissions();
|
||||
} else {
|
||||
chooseFile();
|
||||
}
|
||||
} else {
|
||||
setDialogAddEntries(progress, savedInstanceState.getInt(TAG_PROGRESS_MAX));
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
private void setDialogAddEntries(ProgressDialog dialog, int length) {
|
||||
dialog.setMax(length);
|
||||
dialog.setMessage(getString(R.string.import_dialog_adding_entries));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
final ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
outState.putInt(TAG_PROGRESS_MAX, dialog.getMax());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
Dialog dialog = getDialog();
|
||||
// handles https://code.google.com/p/android/issues/detail?id=17423
|
||||
if (dialog != null && getRetainInstance()) {
|
||||
dialog.setDismissMessage(null);
|
||||
}
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
public void chooseFile() {
|
||||
Intent intent = new Intent();
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||
|
||||
if (info.type.equals(CollectionInfo.Type.CALENDAR)) {
|
||||
intent.setType("text/calendar");
|
||||
} else if (info.type.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
|
||||
intent.setType("text/x-vcard");
|
||||
}
|
||||
|
||||
Intent chooser = Intent.createChooser(
|
||||
intent, getString(R.string.choose_file));
|
||||
try {
|
||||
startActivityForResult(chooser, REQUEST_CODE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
ImportResult data = new ImportResult();
|
||||
data.e = new Exception("Failed to open file chooser.\nPlease install one.");
|
||||
|
||||
((ResultFragment.OnImportCallback) getActivity()).onImportResult(data);
|
||||
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (data != null) {
|
||||
// Get the URI of the selected file
|
||||
final Uri uri = data.getData();
|
||||
App.log.info("Importing uri = " + uri.toString());
|
||||
try {
|
||||
importFile = new File(com.etesync.syncadapter.utils.FileUtils.getPath(getContext(), uri));
|
||||
|
||||
new Thread(new ImportCalendarsLoader()).start();
|
||||
} catch (Exception e) {
|
||||
App.log.severe("File select error: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
break;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public void loadFinished(ImportResult data) {
|
||||
((ResultFragment.OnImportCallback) getActivity()).onImportResult(data);
|
||||
|
||||
dismissAllowingStateLoss();
|
||||
|
||||
if (getActivity() instanceof Refreshable) {
|
||||
((Refreshable) getActivity()).refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private class ImportCalendarsLoader implements Runnable {
|
||||
private void finishParsingFile(final int length) {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setDialogAddEntries((ProgressDialog) getDialog(), length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void entryProcessed() {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
dialog.incrementProgressBy(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final ImportResult result = loadInBackground();
|
||||
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadFinished(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ImportResult loadInBackground() {
|
||||
ImportResult result = new ImportResult();
|
||||
|
||||
try {
|
||||
FileInputStream importStream = new FileInputStream(importFile);
|
||||
|
||||
if (info.type.equals(CollectionInfo.Type.CALENDAR)) {
|
||||
final Event[] events = Event.fromStream(importStream, Charsets.UTF_8);
|
||||
importStream.close();
|
||||
|
||||
if (events.length == 0) {
|
||||
App.log.warning("Empty/invalid file.");
|
||||
result.e = new Exception("Empty/invalid file.");
|
||||
return result;
|
||||
}
|
||||
|
||||
result.total = events.length;
|
||||
|
||||
finishParsingFile(events.length);
|
||||
|
||||
ContentProviderClient provider = getContext().getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI);
|
||||
LocalCalendar localCalendar;
|
||||
try {
|
||||
localCalendar = LocalCalendar.findByName(account, provider, LocalCalendar.Factory.INSTANCE, info.uid);
|
||||
if (localCalendar == null) {
|
||||
throw new FileNotFoundException("Failed to load local resource.");
|
||||
}
|
||||
} catch (CalendarStorageException | FileNotFoundException e) {
|
||||
App.log.info("Fail" + e.getLocalizedMessage());
|
||||
result.e = e;
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Event event : events) {
|
||||
try {
|
||||
LocalEvent localEvent = new LocalEvent(localCalendar, event, event.uid, null);
|
||||
localEvent.addAsDirty();
|
||||
result.added++;
|
||||
} catch (CalendarStorageException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
entryProcessed();
|
||||
}
|
||||
} else if (info.type.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
|
||||
// FIXME: Handle groups and download icon?
|
||||
Contact.Downloader downloader = new ContactsSyncManager.ResourceDownloader(getContext());
|
||||
final Contact[] contacts = Contact.fromStream(importStream, Charsets.UTF_8, downloader);
|
||||
|
||||
if (contacts.length == 0) {
|
||||
App.log.warning("Empty/invalid file.");
|
||||
result.e = new Exception("Empty/invalid file.");
|
||||
return result;
|
||||
}
|
||||
|
||||
result.total = contacts.length;
|
||||
|
||||
finishParsingFile(contacts.length);
|
||||
|
||||
ContentProviderClient provider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI);
|
||||
LocalAddressBook localAddressBook = LocalAddressBook.findByUid(getContext(), provider, account, info.uid);
|
||||
|
||||
for (Contact contact : contacts) {
|
||||
try {
|
||||
LocalContact localContact = new LocalContact(localAddressBook, contact, null, null);
|
||||
localContact.createAsDirty();
|
||||
result.added++;
|
||||
} catch (ContactsStorageException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
entryProcessed();
|
||||
}
|
||||
provider.release();
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (FileNotFoundException e) {
|
||||
result.e = e;
|
||||
return result;
|
||||
} catch (InvalidCalendarException | IOException | ContactsStorageException e) {
|
||||
result.e = e;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.Manifest
|
||||
import android.accounts.Account
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.CalendarContract
|
||||
import android.provider.ContactsContract
|
||||
import android.support.v4.app.DialogFragment
|
||||
import at.bitfire.ical4android.CalendarStorageException
|
||||
import at.bitfire.ical4android.Event
|
||||
import at.bitfire.ical4android.InvalidCalendarException
|
||||
import at.bitfire.vcard4android.Contact
|
||||
import at.bitfire.vcard4android.ContactsStorageException
|
||||
import com.etesync.syncadapter.App
|
||||
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||
import com.etesync.syncadapter.Constants.KEY_COLLECTION_INFO
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.resource.LocalAddressBook
|
||||
import com.etesync.syncadapter.resource.LocalCalendar
|
||||
import com.etesync.syncadapter.resource.LocalContact
|
||||
import com.etesync.syncadapter.resource.LocalEvent
|
||||
import com.etesync.syncadapter.syncadapter.ContactsSyncManager
|
||||
import com.etesync.syncadapter.ui.Refreshable
|
||||
import com.etesync.syncadapter.ui.importlocal.ResultFragment.ImportResult
|
||||
import org.apache.commons.codec.Charsets
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
|
||||
class ImportFragment : DialogFragment() {
|
||||
|
||||
private var account: Account? = null
|
||||
private var info: CollectionInfo? = null
|
||||
private var importFile: File? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
isCancelable = false
|
||||
retainInstance = true
|
||||
|
||||
account = arguments!!.getParcelable(KEY_ACCOUNT)
|
||||
info = arguments!!.getSerializable(KEY_COLLECTION_INFO) as CollectionInfo
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
chooseFile()
|
||||
} else {
|
||||
val data = ImportResult()
|
||||
data.e = Exception(getString(R.string.import_permission_required))
|
||||
(activity as ResultFragment.OnImportCallback).onImportResult(data)
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private fun requestPermissions() {
|
||||
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 0)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
super.onCreateDialog(savedInstanceState)
|
||||
val progress = ProgressDialog(activity)
|
||||
progress.setTitle(R.string.import_dialog_title)
|
||||
progress.setMessage(getString(R.string.import_dialog_loading_file))
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
progress.isIndeterminate = false
|
||||
progress.setIcon(R.drawable.ic_import_export_black)
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
requestPermissions()
|
||||
} else {
|
||||
chooseFile()
|
||||
}
|
||||
} else {
|
||||
setDialogAddEntries(progress, savedInstanceState.getInt(TAG_PROGRESS_MAX))
|
||||
}
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
private fun setDialogAddEntries(dialog: ProgressDialog, length: Int) {
|
||||
dialog.max = length
|
||||
dialog.setMessage(getString(R.string.import_dialog_adding_entries))
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
val dialog = dialog as ProgressDialog
|
||||
|
||||
outState.putInt(TAG_PROGRESS_MAX, dialog.max)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
val dialog = dialog
|
||||
// handles https://code.google.com/p/android/issues/detail?id=17423
|
||||
if (dialog != null && retainInstance) {
|
||||
dialog.setDismissMessage(null)
|
||||
}
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
fun chooseFile() {
|
||||
val intent = Intent()
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.action = Intent.ACTION_GET_CONTENT
|
||||
|
||||
if (info!!.type == CollectionInfo.Type.CALENDAR) {
|
||||
intent.type = "text/calendar"
|
||||
} else if (info!!.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
intent.type = "text/x-vcard"
|
||||
}
|
||||
|
||||
val chooser = Intent.createChooser(
|
||||
intent, getString(R.string.choose_file))
|
||||
try {
|
||||
startActivityForResult(chooser, REQUEST_CODE)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
val data = ImportResult()
|
||||
data.e = Exception("Failed to open file chooser.\nPlease install one.")
|
||||
|
||||
(activity as ResultFragment.OnImportCallback).onImportResult(data)
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) {
|
||||
if (data != null) {
|
||||
// Get the URI of the selected file
|
||||
val uri = data.data
|
||||
App.log.info("Importing uri = " + uri!!.toString())
|
||||
try {
|
||||
importFile = File(com.etesync.syncadapter.utils.FileUtils.getPath(context, uri))
|
||||
|
||||
Thread(ImportCalendarsLoader()).start()
|
||||
} catch (e: Exception) {
|
||||
App.log.severe("File select error: " + e.localizedMessage)
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
fun loadFinished(data: ImportResult) {
|
||||
(activity as ResultFragment.OnImportCallback).onImportResult(data)
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
|
||||
if (activity is Refreshable) {
|
||||
(activity as Refreshable).refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ImportCalendarsLoader : Runnable {
|
||||
private fun finishParsingFile(length: Int) {
|
||||
if (activity == null) {
|
||||
return
|
||||
}
|
||||
|
||||
activity!!.runOnUiThread { setDialogAddEntries(dialog as ProgressDialog, length) }
|
||||
}
|
||||
|
||||
private fun entryProcessed() {
|
||||
if (activity == null) {
|
||||
return
|
||||
}
|
||||
|
||||
activity!!.runOnUiThread {
|
||||
val dialog = dialog as ProgressDialog
|
||||
|
||||
dialog.incrementProgressBy(1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
val result = loadInBackground()
|
||||
|
||||
activity!!.runOnUiThread { loadFinished(result) }
|
||||
}
|
||||
|
||||
fun loadInBackground(): ImportResult {
|
||||
val result = ImportResult()
|
||||
|
||||
try {
|
||||
val importStream = FileInputStream(importFile!!)
|
||||
|
||||
if (info!!.type == CollectionInfo.Type.CALENDAR) {
|
||||
val events = Event.fromStream(importStream, Charsets.UTF_8)
|
||||
importStream.close()
|
||||
|
||||
if (events.size == 0) {
|
||||
App.log.warning("Empty/invalid file.")
|
||||
result.e = Exception("Empty/invalid file.")
|
||||
return result
|
||||
}
|
||||
|
||||
result.total = events.size.toLong()
|
||||
|
||||
finishParsingFile(events.size)
|
||||
|
||||
val provider = context!!.contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)
|
||||
val localCalendar: LocalCalendar?
|
||||
try {
|
||||
localCalendar = LocalCalendar.findByName(account, provider, LocalCalendar.Factory.INSTANCE, info!!.uid)
|
||||
if (localCalendar == null) {
|
||||
throw FileNotFoundException("Failed to load local resource.")
|
||||
}
|
||||
} catch (e: CalendarStorageException) {
|
||||
App.log.info("Fail" + e.localizedMessage)
|
||||
result.e = e
|
||||
return result
|
||||
} catch (e: FileNotFoundException) {
|
||||
App.log.info("Fail" + e.localizedMessage)
|
||||
result.e = e
|
||||
return result
|
||||
}
|
||||
|
||||
for (event in events) {
|
||||
try {
|
||||
val localEvent = LocalEvent(localCalendar, event, event.uid, null)
|
||||
localEvent.addAsDirty()
|
||||
result.added++
|
||||
} catch (e: CalendarStorageException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
entryProcessed()
|
||||
}
|
||||
} else if (info!!.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
// FIXME: Handle groups and download icon?
|
||||
val downloader = ContactsSyncManager.ResourceDownloader(context!!)
|
||||
val contacts = Contact.fromStream(importStream, Charsets.UTF_8, downloader)
|
||||
|
||||
if (contacts.size == 0) {
|
||||
App.log.warning("Empty/invalid file.")
|
||||
result.e = Exception("Empty/invalid file.")
|
||||
return result
|
||||
}
|
||||
|
||||
result.total = contacts.size.toLong()
|
||||
|
||||
finishParsingFile(contacts.size)
|
||||
|
||||
val provider = context!!.contentResolver.acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI)
|
||||
val localAddressBook = LocalAddressBook.findByUid(context!!, provider!!, account, info!!.uid)
|
||||
|
||||
for (contact in contacts) {
|
||||
try {
|
||||
val localContact = LocalContact(localAddressBook, contact, null, null)
|
||||
localContact.createAsDirty()
|
||||
result.added++
|
||||
} catch (e: ContactsStorageException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
entryProcessed()
|
||||
}
|
||||
provider.release()
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e: FileNotFoundException) {
|
||||
result.e = e
|
||||
return result
|
||||
} catch (e: InvalidCalendarException) {
|
||||
result.e = e
|
||||
return result
|
||||
} catch (e: IOException) {
|
||||
result.e = e
|
||||
return result
|
||||
} catch (e: ContactsStorageException) {
|
||||
result.e = e
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val REQUEST_CODE = 6384 // onActivityResult request
|
||||
|
||||
private val TAG_PROGRESS_MAX = "progressMax"
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): ImportFragment {
|
||||
val frag = ImportFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(KEY_ACCOUNT, account)
|
||||
args.putSerializable(KEY_COLLECTION_INFO, info)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseExpandableListAdapter;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.resource.LocalCalendar;
|
||||
import com.etesync.syncadapter.resource.LocalEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import at.bitfire.ical4android.CalendarStorageException;
|
||||
import at.bitfire.ical4android.Event;
|
||||
|
||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
||||
import static com.etesync.syncadapter.Constants.KEY_COLLECTION_INFO;
|
||||
|
||||
public class LocalCalendarImportFragment extends ListFragment {
|
||||
|
||||
private Account account;
|
||||
private CollectionInfo info;
|
||||
|
||||
public static LocalCalendarImportFragment newInstance(Account account, CollectionInfo info) {
|
||||
LocalCalendarImportFragment frag = new LocalCalendarImportFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(KEY_ACCOUNT, account);
|
||||
args.putSerializable(KEY_COLLECTION_INFO, info);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
|
||||
account = getArguments().getParcelable(KEY_ACCOUNT);
|
||||
info = (CollectionInfo) getArguments().getSerializable(KEY_COLLECTION_INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_local_calendar_import, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
importAccount();
|
||||
}
|
||||
|
||||
protected void importAccount() {
|
||||
final List<CalendarAccount> calendarAccountList = CalendarAccount.loadAll(getContext().getContentResolver());
|
||||
|
||||
ExpandableListView listCalendar = (ExpandableListView) getListView();
|
||||
|
||||
final LocalCalendarImportFragment.ExpandableListAdapter adapter =
|
||||
new LocalCalendarImportFragment.ExpandableListAdapter(getContext(), calendarAccountList);
|
||||
listCalendar.setAdapter(adapter);
|
||||
|
||||
listCalendar.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
|
||||
@Override
|
||||
public boolean onChildClick(ExpandableListView aExpandableListView, View aView, int groupPosition, int childPosition, long aL) {
|
||||
new ImportEvents().execute(calendarAccountList.get(groupPosition).getCalendars().get(childPosition));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private class ExpandableListAdapter extends BaseExpandableListAdapter {
|
||||
|
||||
private Context context;
|
||||
private List<CalendarAccount> calendarAccounts;
|
||||
private AccountResolver accountResolver;
|
||||
|
||||
public ExpandableListAdapter(Context context, List<CalendarAccount> calendarAccounts) {
|
||||
this.context = context;
|
||||
this.calendarAccounts = calendarAccounts;
|
||||
this.accountResolver = new AccountResolver(context);
|
||||
}
|
||||
|
||||
private class ChildViewHolder {
|
||||
TextView textView;
|
||||
}
|
||||
|
||||
private class GroupViewHolder {
|
||||
TextView titleTextView;
|
||||
TextView descriptionTextView;
|
||||
ImageView iconImageView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getChild(int groupPosition, int childPosititon) {
|
||||
return calendarAccounts.get(groupPosition).getCalendars()
|
||||
.get(childPosititon).getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getChildId(int groupPosition, int childPosition) {
|
||||
return childPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getChildView(int groupPosition, final int childPosition,
|
||||
boolean isLastChild, View convertView, ViewGroup parent) {
|
||||
|
||||
final String childText = (String) getChild(groupPosition, childPosition);
|
||||
ChildViewHolder viewHolder;
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.import_calendars_list_item, null);
|
||||
}
|
||||
|
||||
if (convertView.getTag() != null) {
|
||||
viewHolder = (ChildViewHolder) convertView.getTag();
|
||||
} else {
|
||||
viewHolder = new ChildViewHolder();
|
||||
viewHolder.textView = (TextView) convertView
|
||||
.findViewById(R.id.listItemText);
|
||||
convertView.setTag(viewHolder);
|
||||
}
|
||||
viewHolder.textView.setText(childText);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildrenCount(int groupPosition) {
|
||||
return calendarAccounts.get(groupPosition).getCalendars()
|
||||
.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGroup(int groupPosition) {
|
||||
return calendarAccounts.get(groupPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGroupCount() {
|
||||
return calendarAccounts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getGroupId(int groupPosition) {
|
||||
return groupPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getGroupView(int groupPosition, boolean isExpanded,
|
||||
View convertView, ViewGroup parent) {
|
||||
CalendarAccount calendarAccount = (CalendarAccount) getGroup(groupPosition);
|
||||
GroupViewHolder viewHolder;
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.import_content_list_header, null);
|
||||
}
|
||||
if (convertView.getTag() != null) {
|
||||
viewHolder = (GroupViewHolder) convertView.getTag();
|
||||
} else {
|
||||
viewHolder = new GroupViewHolder();
|
||||
viewHolder.titleTextView = (TextView) convertView
|
||||
.findViewById(R.id.title);
|
||||
viewHolder.descriptionTextView = (TextView) convertView
|
||||
.findViewById(R.id.description);
|
||||
viewHolder.iconImageView = (ImageView) convertView.findViewById(R.id.icon);
|
||||
convertView.setTag(viewHolder);
|
||||
}
|
||||
|
||||
viewHolder.titleTextView.setText(calendarAccount.getAccountName());
|
||||
AccountResolver.AccountInfo accountInfo = accountResolver.resolve(calendarAccount.getAccountType());
|
||||
viewHolder.descriptionTextView.setText(accountInfo.name);
|
||||
viewHolder.iconImageView.setImageDrawable(accountInfo.icon);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildSelectable(int groupPosition, int childPosition) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected class ImportEvents extends AsyncTask<LocalCalendar, Integer, ResultFragment.ImportResult> {
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = new ProgressDialog(getActivity());
|
||||
progressDialog.setTitle(R.string.import_dialog_title);
|
||||
progressDialog.setMessage(getString(R.string.import_dialog_adding_entries));
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setIcon(R.drawable.ic_import_export_black);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResultFragment.ImportResult doInBackground(LocalCalendar... calendars) {
|
||||
return importEvents(calendars[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
if (progressDialog != null)
|
||||
progressDialog.setProgress(progress[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ResultFragment.ImportResult result) {
|
||||
progressDialog.dismiss();
|
||||
((ResultFragment.OnImportCallback) getActivity()).onImportResult(result);
|
||||
}
|
||||
|
||||
private ResultFragment.ImportResult importEvents(LocalCalendar fromCalendar) {
|
||||
ResultFragment.ImportResult result = new ResultFragment.ImportResult();
|
||||
try {
|
||||
LocalCalendar localCalendar = LocalCalendar.findByName(account,
|
||||
getContext().getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI),
|
||||
LocalCalendar.Factory.INSTANCE, info.uid);
|
||||
LocalEvent[] localEvents = fromCalendar.getAll();
|
||||
int total = localEvents.length;
|
||||
progressDialog.setMax(total);
|
||||
result.total = total;
|
||||
int progress = 0;
|
||||
for (LocalEvent currentLocalEvent : localEvents) {
|
||||
Event event = currentLocalEvent.getEvent();
|
||||
try {
|
||||
LocalEvent localEvent = new LocalEvent(localCalendar, event, null, null);
|
||||
localEvent.addAsDirty();
|
||||
result.added++;
|
||||
} catch (CalendarStorageException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
publishProgress(++progress);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
result.e = e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.provider.CalendarContract
|
||||
import android.support.v4.app.ListFragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseExpandableListAdapter
|
||||
import android.widget.ExpandableListView
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import at.bitfire.ical4android.CalendarStorageException
|
||||
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||
import com.etesync.syncadapter.Constants.KEY_COLLECTION_INFO
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.resource.LocalCalendar
|
||||
import com.etesync.syncadapter.resource.LocalEvent
|
||||
|
||||
class LocalCalendarImportFragment : ListFragment() {
|
||||
|
||||
private var account: Account? = null
|
||||
private var info: CollectionInfo? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
retainInstance = true
|
||||
|
||||
account = arguments!!.getParcelable(KEY_ACCOUNT)
|
||||
info = arguments!!.getSerializable(KEY_COLLECTION_INFO) as CollectionInfo
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_local_calendar_import, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
importAccount()
|
||||
}
|
||||
|
||||
protected fun importAccount() {
|
||||
val calendarAccountList = CalendarAccount.loadAll(context!!.contentResolver)
|
||||
|
||||
val listCalendar = listView as ExpandableListView
|
||||
|
||||
val adapter = ExpandableListAdapter(context!!, calendarAccountList)
|
||||
listCalendar.setAdapter(adapter)
|
||||
|
||||
listCalendar.setOnChildClickListener { aExpandableListView, aView, groupPosition, childPosition, aL ->
|
||||
ImportEvents().execute(calendarAccountList[groupPosition].getCalendars()[childPosition])
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private inner class ExpandableListAdapter(private val context: Context, private val calendarAccounts: List<CalendarAccount>) : BaseExpandableListAdapter() {
|
||||
private val accountResolver: AccountResolver
|
||||
|
||||
init {
|
||||
this.accountResolver = AccountResolver(context)
|
||||
}
|
||||
|
||||
private inner class ChildViewHolder {
|
||||
internal var textView: TextView? = null
|
||||
}
|
||||
|
||||
private inner class GroupViewHolder {
|
||||
internal var titleTextView: TextView? = null
|
||||
internal var descriptionTextView: TextView? = null
|
||||
internal var iconImageView: ImageView? = null
|
||||
}
|
||||
|
||||
override fun getChild(groupPosition: Int, childPosititon: Int): Any {
|
||||
return calendarAccounts[groupPosition].getCalendars()[childPosititon].displayName
|
||||
}
|
||||
|
||||
override fun getChildId(groupPosition: Int, childPosition: Int): Long {
|
||||
return childPosition.toLong()
|
||||
}
|
||||
|
||||
override fun getChildView(groupPosition: Int, childPosition: Int,
|
||||
isLastChild: Boolean, convertView: View?, parent: ViewGroup): View {
|
||||
var convertView = convertView
|
||||
|
||||
val childText = getChild(groupPosition, childPosition) as String
|
||||
val viewHolder: ChildViewHolder
|
||||
if (convertView == null) {
|
||||
val inflater = context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
convertView = inflater.inflate(R.layout.import_calendars_list_item, null)
|
||||
}
|
||||
|
||||
if (convertView!!.tag != null) {
|
||||
viewHolder = convertView.tag as ChildViewHolder
|
||||
} else {
|
||||
viewHolder = ChildViewHolder()
|
||||
viewHolder.textView = convertView
|
||||
.findViewById<View>(R.id.listItemText) as TextView
|
||||
convertView.tag = viewHolder
|
||||
}
|
||||
viewHolder.textView!!.text = childText
|
||||
return convertView
|
||||
}
|
||||
|
||||
override fun getChildrenCount(groupPosition: Int): Int {
|
||||
return calendarAccounts[groupPosition].getCalendars()
|
||||
.size
|
||||
}
|
||||
|
||||
override fun getGroup(groupPosition: Int): Any {
|
||||
return calendarAccounts[groupPosition]
|
||||
}
|
||||
|
||||
override fun getGroupCount(): Int {
|
||||
return calendarAccounts.size
|
||||
}
|
||||
|
||||
override fun getGroupId(groupPosition: Int): Long {
|
||||
return groupPosition.toLong()
|
||||
}
|
||||
|
||||
override fun getGroupView(groupPosition: Int, isExpanded: Boolean,
|
||||
convertView: View?, parent: ViewGroup): View {
|
||||
var convertView = convertView
|
||||
val calendarAccount = getGroup(groupPosition) as CalendarAccount
|
||||
val viewHolder: GroupViewHolder
|
||||
if (convertView == null) {
|
||||
val inflater = context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
convertView = inflater.inflate(R.layout.import_content_list_header, null)
|
||||
}
|
||||
if (convertView!!.tag != null) {
|
||||
viewHolder = convertView.tag as GroupViewHolder
|
||||
} else {
|
||||
viewHolder = GroupViewHolder()
|
||||
viewHolder.titleTextView = convertView
|
||||
.findViewById<View>(R.id.title) as TextView
|
||||
viewHolder.descriptionTextView = convertView
|
||||
.findViewById<View>(R.id.description) as TextView
|
||||
viewHolder.iconImageView = convertView.findViewById<View>(R.id.icon) as ImageView
|
||||
convertView.tag = viewHolder
|
||||
}
|
||||
|
||||
viewHolder.titleTextView!!.text = calendarAccount.accountName
|
||||
val accountInfo = accountResolver.resolve(calendarAccount.accountType)
|
||||
viewHolder.descriptionTextView!!.text = accountInfo.name
|
||||
viewHolder.iconImageView!!.setImageDrawable(accountInfo.icon)
|
||||
|
||||
return convertView
|
||||
}
|
||||
|
||||
override fun hasStableIds(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
protected inner class ImportEvents : AsyncTask<LocalCalendar, Int, ResultFragment.ImportResult>() {
|
||||
internal var progressDialog: ProgressDialog? = null
|
||||
|
||||
override fun onPreExecute() {
|
||||
progressDialog = ProgressDialog(activity)
|
||||
progressDialog!!.setTitle(R.string.import_dialog_title)
|
||||
progressDialog!!.setMessage(getString(R.string.import_dialog_adding_entries))
|
||||
progressDialog!!.setCanceledOnTouchOutside(false)
|
||||
progressDialog!!.setCancelable(false)
|
||||
progressDialog!!.isIndeterminate = false
|
||||
progressDialog!!.setIcon(R.drawable.ic_import_export_black)
|
||||
progressDialog!!.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
progressDialog!!.show()
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg calendars: LocalCalendar): ResultFragment.ImportResult {
|
||||
return importEvents(calendars[0])
|
||||
}
|
||||
|
||||
override fun onProgressUpdate(vararg progress: Int?) {
|
||||
if (progressDialog != null)
|
||||
progressDialog!!.progress = progress[0]!!
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: ResultFragment.ImportResult) {
|
||||
progressDialog!!.dismiss()
|
||||
(activity as ResultFragment.OnImportCallback).onImportResult(result)
|
||||
}
|
||||
|
||||
private fun importEvents(fromCalendar: LocalCalendar): ResultFragment.ImportResult {
|
||||
val result = ResultFragment.ImportResult()
|
||||
try {
|
||||
val localCalendar = LocalCalendar.findByName(account,
|
||||
context!!.contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI),
|
||||
LocalCalendar.Factory.INSTANCE, info!!.uid)
|
||||
val localEvents = fromCalendar.all
|
||||
val total = localEvents.size
|
||||
progressDialog!!.max = total
|
||||
result.total = total.toLong()
|
||||
var progress = 0
|
||||
for (currentLocalEvent in localEvents) {
|
||||
val event = currentLocalEvent.event
|
||||
try {
|
||||
val localEvent = LocalEvent(localCalendar!!, event, null, null)
|
||||
localEvent.addAsDirty()
|
||||
result.added++
|
||||
} catch (e: CalendarStorageException) {
|
||||
e.printStackTrace()
|
||||
|
||||
}
|
||||
|
||||
publishProgress(++progress)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
result.e = e
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): LocalCalendarImportFragment {
|
||||
val frag = LocalCalendarImportFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(KEY_ACCOUNT, account)
|
||||
args.putSerializable(KEY_COLLECTION_INFO, info)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -1,297 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.model.CollectionInfo;
|
||||
import com.etesync.syncadapter.resource.LocalAddressBook;
|
||||
import com.etesync.syncadapter.resource.LocalContact;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
|
||||
import static android.content.ContentValues.TAG;
|
||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
||||
import static com.etesync.syncadapter.Constants.KEY_COLLECTION_INFO;
|
||||
|
||||
public class LocalContactImportFragment extends Fragment {
|
||||
|
||||
private Account account;
|
||||
private CollectionInfo info;
|
||||
private RecyclerView recyclerView;
|
||||
|
||||
public static LocalContactImportFragment newInstance(Account account, CollectionInfo info) {
|
||||
LocalContactImportFragment frag = new LocalContactImportFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(KEY_ACCOUNT, account);
|
||||
args.putSerializable(KEY_COLLECTION_INFO, info);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
|
||||
account = getArguments().getParcelable(KEY_ACCOUNT);
|
||||
info = (CollectionInfo) getArguments().getSerializable(KEY_COLLECTION_INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_local_contact_import, container, false);
|
||||
|
||||
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
|
||||
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity()));
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
importAccount();
|
||||
}
|
||||
|
||||
protected void importAccount() {
|
||||
ContentProviderClient provider = getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI);
|
||||
Cursor cursor;
|
||||
try {
|
||||
cursor = provider.query(ContactsContract.RawContacts.CONTENT_URI,
|
||||
new String[]{ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE}
|
||||
, null, null,
|
||||
ContactsContract.RawContacts.ACCOUNT_NAME + " ASC, " + ContactsContract.RawContacts.ACCOUNT_TYPE);
|
||||
} catch (Exception except) {
|
||||
Log.w(TAG, "Addressbook provider is missing columns, continuing anyway");
|
||||
|
||||
except.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
final List<LocalAddressBook> localAddressBooks = new ArrayList<>();
|
||||
Account account = null;
|
||||
int accountNameIndex = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME);
|
||||
int accountTypeIndex = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE);
|
||||
while (cursor.moveToNext()) {
|
||||
String accountName = cursor.getString(accountNameIndex);
|
||||
String accountType = cursor.getString(accountTypeIndex);
|
||||
if (account == null || !(account.name.equals(accountName) && account.type.equals(accountType))) {
|
||||
if ((accountName != null) && (accountType != null)) {
|
||||
account = new Account(accountName, accountType);
|
||||
localAddressBooks.add(new LocalAddressBook(getContext(), account, provider));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
provider.release();
|
||||
|
||||
recyclerView.setAdapter(new ImportContactAdapter(getContext(), localAddressBooks, new OnAccountSelected() {
|
||||
@Override
|
||||
public void accountSelected(int index) {
|
||||
new ImportContacts().execute(localAddressBooks.get(index));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected class ImportContacts extends AsyncTask<LocalAddressBook, Integer, ResultFragment.ImportResult> {
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = new ProgressDialog(getActivity());
|
||||
progressDialog.setTitle(R.string.import_dialog_title);
|
||||
progressDialog.setMessage(getString(R.string.import_dialog_adding_entries));
|
||||
progressDialog.setCanceledOnTouchOutside(false);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setIcon(R.drawable.ic_import_export_black);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResultFragment.ImportResult doInBackground(LocalAddressBook... addressBooks) {
|
||||
return importContacts(addressBooks[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
if (progressDialog != null)
|
||||
progressDialog.setProgress(progress[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ResultFragment.ImportResult result) {
|
||||
progressDialog.dismiss();
|
||||
((ResultFragment.OnImportCallback) getActivity()).onImportResult(result);
|
||||
}
|
||||
|
||||
private ResultFragment.ImportResult importContacts(LocalAddressBook localAddressBook) {
|
||||
ResultFragment.ImportResult result = new ResultFragment.ImportResult();
|
||||
try {
|
||||
LocalAddressBook addressBook = LocalAddressBook.findByUid(getContext(),
|
||||
getContext().getContentResolver().acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI),
|
||||
account, info.uid);
|
||||
LocalContact[] localContacts = localAddressBook.getAll();
|
||||
int total = localContacts.length;
|
||||
progressDialog.setMax(total);
|
||||
result.total = total;
|
||||
int progress = 0;
|
||||
for (LocalContact currentLocalContact : localContacts) {
|
||||
Contact contact = currentLocalContact.getContact();
|
||||
|
||||
try {
|
||||
LocalContact localContact = new LocalContact(addressBook, contact, null, null);
|
||||
localContact.createAsDirty();
|
||||
result.added++;
|
||||
} catch (ContactsStorageException e) {
|
||||
e.printStackTrace();
|
||||
result.e = e;
|
||||
}
|
||||
publishProgress(++progress);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.e = e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImportContactAdapter extends RecyclerView.Adapter<ImportContactAdapter.ViewHolder> {
|
||||
private static final String TAG = "ImportContactAdapter";
|
||||
|
||||
private List<LocalAddressBook> mAddressBooks;
|
||||
private OnAccountSelected mOnAccountSelected;
|
||||
private AccountResolver accountResolver;
|
||||
|
||||
/**
|
||||
* Provide a reference to the type of views that you are using (custom ViewHolder)
|
||||
*/
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView titleTextView;
|
||||
private final TextView descTextView;
|
||||
private final ImageView iconImageView;
|
||||
|
||||
public ViewHolder(View v, final OnAccountSelected onAccountSelected) {
|
||||
super(v);
|
||||
// Define click listener for the ViewHolder's View.
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onAccountSelected.accountSelected(getAdapterPosition());
|
||||
}
|
||||
});
|
||||
titleTextView = (TextView) v.findViewById(R.id.title);
|
||||
descTextView = (TextView) v.findViewById(R.id.description);
|
||||
iconImageView = (ImageView) v.findViewById(R.id.icon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the dataset of the Adapter.
|
||||
*
|
||||
* @param addressBooks containing the data to populate views to be used by RecyclerView.
|
||||
*/
|
||||
public ImportContactAdapter(Context context, List<LocalAddressBook> addressBooks, OnAccountSelected onAccountSelected) {
|
||||
mAddressBooks = addressBooks;
|
||||
mOnAccountSelected = onAccountSelected;
|
||||
accountResolver = new AccountResolver(context);
|
||||
}
|
||||
|
||||
// Create new views (invoked by the layout manager)
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||
// Create a new view.
|
||||
View v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.import_content_list_account, viewGroup, false);
|
||||
|
||||
return new ViewHolder(v, mOnAccountSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
|
||||
viewHolder.titleTextView.setText(mAddressBooks.get(position).account.name);
|
||||
AccountResolver.AccountInfo accountInfo = accountResolver.resolve(mAddressBooks.get(position).account.type);
|
||||
viewHolder.descTextView.setText(accountInfo.name);
|
||||
viewHolder.iconImageView.setImageDrawable(accountInfo.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mAddressBooks.size();
|
||||
}
|
||||
}
|
||||
|
||||
private interface OnAccountSelected {
|
||||
void accountSelected(int index);
|
||||
}
|
||||
|
||||
public static class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private static final int[] ATTRS = new int[]{
|
||||
android.R.attr.listDivider
|
||||
};
|
||||
|
||||
private Drawable mDivider;
|
||||
|
||||
public DividerItemDecoration(Context context) {
|
||||
final TypedArray a = context.obtainStyledAttributes(ATTRS);
|
||||
mDivider = a.getDrawable(0);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
drawVertical(c, parent);
|
||||
}
|
||||
|
||||
public void drawVertical(Canvas c, RecyclerView parent) {
|
||||
final int left = parent.getPaddingLeft();
|
||||
final int right = parent.getWidth() - parent.getPaddingRight();
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
|
||||
.getLayoutParams();
|
||||
final int top = child.getBottom() + params.bottomMargin;
|
||||
final int bottom = top + mDivider.getIntrinsicHeight();
|
||||
mDivider.setBounds(left, top, right, bottom);
|
||||
mDivider.draw(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ContentValues.TAG
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import at.bitfire.vcard4android.ContactsStorageException
|
||||
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||
import com.etesync.syncadapter.Constants.KEY_COLLECTION_INFO
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.model.CollectionInfo
|
||||
import com.etesync.syncadapter.resource.LocalAddressBook
|
||||
import com.etesync.syncadapter.resource.LocalContact
|
||||
import java.util.*
|
||||
|
||||
class LocalContactImportFragment : Fragment() {
|
||||
|
||||
private var account: Account? = null
|
||||
private var info: CollectionInfo? = null
|
||||
private var recyclerView: RecyclerView? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
retainInstance = true
|
||||
|
||||
account = arguments!!.getParcelable(KEY_ACCOUNT)
|
||||
info = arguments!!.getSerializable(KEY_COLLECTION_INFO) as CollectionInfo
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_local_contact_import, container, false)
|
||||
|
||||
recyclerView = view.findViewById<View>(R.id.recyclerView) as RecyclerView
|
||||
|
||||
recyclerView!!.layoutManager = LinearLayoutManager(activity)
|
||||
recyclerView!!.addItemDecoration(DividerItemDecoration(activity!!))
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
importAccount()
|
||||
}
|
||||
|
||||
protected fun importAccount() {
|
||||
val provider = context!!.contentResolver.acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI)
|
||||
val cursor: Cursor?
|
||||
try {
|
||||
cursor = provider!!.query(ContactsContract.RawContacts.CONTENT_URI,
|
||||
arrayOf(ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE), null, null,
|
||||
ContactsContract.RawContacts.ACCOUNT_NAME + " ASC, " + ContactsContract.RawContacts.ACCOUNT_TYPE)
|
||||
} catch (except: Exception) {
|
||||
Log.w(TAG, "Addressbook provider is missing columns, continuing anyway")
|
||||
|
||||
except.printStackTrace()
|
||||
return
|
||||
}
|
||||
|
||||
val localAddressBooks = ArrayList<LocalAddressBook>()
|
||||
var account: Account? = null
|
||||
val accountNameIndex = cursor!!.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME)
|
||||
val accountTypeIndex = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE)
|
||||
while (cursor.moveToNext()) {
|
||||
val accountName = cursor.getString(accountNameIndex)
|
||||
val accountType = cursor.getString(accountTypeIndex)
|
||||
if (account == null || !(account.name == accountName && account.type == accountType)) {
|
||||
if (accountName != null && accountType != null) {
|
||||
account = Account(accountName, accountType)
|
||||
localAddressBooks.add(LocalAddressBook(context, account, provider))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
provider.release()
|
||||
|
||||
recyclerView!!.adapter = ImportContactAdapter(context!!, localAddressBooks, object : OnAccountSelected {
|
||||
override fun accountSelected(index: Int) {
|
||||
ImportContacts().execute(localAddressBooks[index])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
protected inner class ImportContacts : AsyncTask<LocalAddressBook, Int, ResultFragment.ImportResult>() {
|
||||
internal var progressDialog: ProgressDialog? = null
|
||||
|
||||
override fun onPreExecute() {
|
||||
progressDialog = ProgressDialog(activity)
|
||||
progressDialog!!.setTitle(R.string.import_dialog_title)
|
||||
progressDialog!!.setMessage(getString(R.string.import_dialog_adding_entries))
|
||||
progressDialog!!.setCanceledOnTouchOutside(false)
|
||||
progressDialog!!.setCancelable(false)
|
||||
progressDialog!!.isIndeterminate = false
|
||||
progressDialog!!.setIcon(R.drawable.ic_import_export_black)
|
||||
progressDialog!!.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
progressDialog!!.show()
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg addressBooks: LocalAddressBook): ResultFragment.ImportResult {
|
||||
return importContacts(addressBooks[0])
|
||||
}
|
||||
|
||||
override fun onProgressUpdate(vararg values: Int?) {
|
||||
if (progressDialog != null)
|
||||
progressDialog!!.progress = values[0]!!
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: ResultFragment.ImportResult) {
|
||||
progressDialog!!.dismiss()
|
||||
(activity as ResultFragment.OnImportCallback).onImportResult(result)
|
||||
}
|
||||
|
||||
private fun importContacts(localAddressBook: LocalAddressBook): ResultFragment.ImportResult {
|
||||
val result = ResultFragment.ImportResult()
|
||||
try {
|
||||
val addressBook = LocalAddressBook.findByUid(context!!,
|
||||
context!!.contentResolver.acquireContentProviderClient(ContactsContract.RawContacts.CONTENT_URI)!!,
|
||||
account, info!!.uid)
|
||||
val localContacts = localAddressBook.all
|
||||
val total = localContacts.size
|
||||
progressDialog!!.max = total
|
||||
result.total = total.toLong()
|
||||
var progress = 0
|
||||
for (currentLocalContact in localContacts) {
|
||||
val contact = currentLocalContact.contact
|
||||
|
||||
try {
|
||||
val localContact = LocalContact(addressBook, contact, null, null)
|
||||
localContact.createAsDirty()
|
||||
result.added++
|
||||
} catch (e: ContactsStorageException) {
|
||||
e.printStackTrace()
|
||||
result.e = e
|
||||
}
|
||||
|
||||
publishProgress(++progress)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
result.e = e
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class ImportContactAdapter
|
||||
/**
|
||||
* Initialize the dataset of the Adapter.
|
||||
*
|
||||
* @param addressBooks containing the data to populate views to be used by RecyclerView.
|
||||
*/
|
||||
(context: Context, private val mAddressBooks: List<LocalAddressBook>, private val mOnAccountSelected: OnAccountSelected) : RecyclerView.Adapter<ImportContactAdapter.ViewHolder>() {
|
||||
private val accountResolver: AccountResolver
|
||||
|
||||
/**
|
||||
* Provide a reference to the type of views that you are using (custom ViewHolder)
|
||||
*/
|
||||
class ViewHolder(v: View, onAccountSelected: OnAccountSelected) : RecyclerView.ViewHolder(v) {
|
||||
internal val titleTextView: TextView
|
||||
internal val descTextView: TextView
|
||||
internal val iconImageView: ImageView
|
||||
|
||||
init {
|
||||
// Define click listener for the ViewHolder's View.
|
||||
v.setOnClickListener { onAccountSelected.accountSelected(adapterPosition) }
|
||||
titleTextView = v.findViewById<View>(R.id.title) as TextView
|
||||
descTextView = v.findViewById<View>(R.id.description) as TextView
|
||||
iconImageView = v.findViewById<View>(R.id.icon) as ImageView
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
accountResolver = AccountResolver(context)
|
||||
}
|
||||
|
||||
// Create new views (invoked by the layout manager)
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
|
||||
// Create a new view.
|
||||
val v = LayoutInflater.from(viewGroup.context)
|
||||
.inflate(R.layout.import_content_list_account, viewGroup, false)
|
||||
|
||||
return ViewHolder(v, mOnAccountSelected)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
|
||||
viewHolder.titleTextView.text = mAddressBooks[position].account.name
|
||||
val accountInfo = accountResolver.resolve(mAddressBooks[position].account.type)
|
||||
viewHolder.descTextView.text = accountInfo.name
|
||||
viewHolder.iconImageView.setImageDrawable(accountInfo.icon)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mAddressBooks.size
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "ImportContactAdapter"
|
||||
}
|
||||
}
|
||||
|
||||
interface OnAccountSelected {
|
||||
fun accountSelected(index: Int)
|
||||
}
|
||||
|
||||
class DividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private val mDivider: Drawable?
|
||||
|
||||
init {
|
||||
val a = context.obtainStyledAttributes(ATTRS)
|
||||
mDivider = a.getDrawable(0)
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
|
||||
drawVertical(c, parent)
|
||||
}
|
||||
|
||||
fun drawVertical(c: Canvas, parent: RecyclerView) {
|
||||
val left = parent.paddingLeft
|
||||
val right = parent.width - parent.paddingRight
|
||||
|
||||
val childCount = parent.childCount
|
||||
for (i in 0 until childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
val params = child
|
||||
.layoutParams as RecyclerView.LayoutParams
|
||||
val top = child.bottom + params.bottomMargin
|
||||
val bottom = top + mDivider!!.intrinsicHeight
|
||||
mDivider.setBounds(left, top, right, bottom)
|
||||
mDivider.draw(c)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
|
||||
outRect.set(0, 0, 0, mDivider!!.intrinsicHeight)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val ATTRS = intArrayOf(android.R.attr.listDivider)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): LocalContactImportFragment {
|
||||
val frag = LocalContactImportFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(KEY_ACCOUNT, account)
|
||||
args.putSerializable(KEY_COLLECTION_INFO, info)
|
||||
frag.arguments = args
|
||||
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import com.etesync.syncadapter.R;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Created by tal on 30/03/17.
|
||||
*/
|
||||
|
||||
public class ResultFragment extends DialogFragment {
|
||||
private static final String KEY_RESULT = "result";
|
||||
private ImportResult result;
|
||||
|
||||
public static ResultFragment newInstance(ImportResult result) {
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(KEY_RESULT, result);
|
||||
ResultFragment fragment = new ResultFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
result = (ImportResult) getArguments().getSerializable(KEY_RESULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
Activity activity = getActivity();
|
||||
if (activity instanceof DialogInterface) {
|
||||
((DialogInterface)activity).dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
int icon;
|
||||
int title;
|
||||
String msg;
|
||||
if (result.isFailed()) {
|
||||
icon = R.drawable.ic_error_dark;
|
||||
title = R.string.import_dialog_failed_title;
|
||||
msg = result.e.getLocalizedMessage();
|
||||
} else {
|
||||
icon = R.drawable.ic_import_export_black;
|
||||
title = R.string.import_dialog_title;
|
||||
msg = getString(R.string.import_dialog_success, result.total, result.added, result.updated, result.getSkipped());
|
||||
}
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(title)
|
||||
.setIcon(icon)
|
||||
.setMessage(msg)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// dismiss
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
public static class ImportResult implements Serializable {
|
||||
public long total;
|
||||
public long added;
|
||||
public long updated;
|
||||
public Exception e;
|
||||
|
||||
public boolean isFailed() {
|
||||
return (e != null);
|
||||
}
|
||||
|
||||
public long getSkipped() {
|
||||
return total - (added + updated);
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public java.lang.String toString() {
|
||||
return "ResultFragment.ImportResult(total=" + this.total + ", added=" + this.added + ", updated=" + this.updated + ", e=" + this.e + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnImportCallback {
|
||||
void onImportResult(ImportResult importResult);
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.etesync.syncadapter.R
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Created by tal on 30/03/17.
|
||||
*/
|
||||
|
||||
class ResultFragment : DialogFragment() {
|
||||
private var result: ImportResult? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
result = arguments!!.getSerializable(KEY_RESULT) as ImportResult
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface?) {
|
||||
super.onDismiss(dialog)
|
||||
val activity = activity
|
||||
if (activity is DialogInterface) {
|
||||
(activity as DialogInterface).dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val icon: Int
|
||||
val title: Int
|
||||
val msg: String
|
||||
if (result!!.isFailed) {
|
||||
icon = R.drawable.ic_error_dark
|
||||
title = R.string.import_dialog_failed_title
|
||||
msg = result!!.e!!.localizedMessage
|
||||
} else {
|
||||
icon = R.drawable.ic_import_export_black
|
||||
title = R.string.import_dialog_title
|
||||
msg = getString(R.string.import_dialog_success, result!!.total, result!!.added, result!!.updated, result!!.skipped)
|
||||
}
|
||||
return AlertDialog.Builder(activity!!)
|
||||
.setTitle(title)
|
||||
.setIcon(icon)
|
||||
.setMessage(msg)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||
// dismiss
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
class ImportResult : Serializable {
|
||||
var total: Long = 0
|
||||
var added: Long = 0
|
||||
var updated: Long = 0
|
||||
var e: Exception? = null
|
||||
|
||||
val isFailed: Boolean
|
||||
get() = e != null
|
||||
|
||||
val skipped: Long
|
||||
get() = total - (added + updated)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ResultFragment.ImportResult(total=" + this.total + ", added=" + this.added + ", updated=" + this.updated + ", e=" + this.e + ")"
|
||||
}
|
||||
}
|
||||
|
||||
interface OnImportCallback {
|
||||
fun onImportResult(importResult: ImportResult)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY_RESULT = "result"
|
||||
|
||||
fun newInstance(result: ImportResult): ResultFragment {
|
||||
val args = Bundle()
|
||||
args.putSerializable(KEY_RESULT, result)
|
||||
val fragment = ResultFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.importlocal;
|
||||
|
||||
/**
|
||||
* Created by tal on 30/03/17.
|
||||
*/
|
||||
|
||||
public interface SelectImportMethod {
|
||||
void importFile();
|
||||
|
||||
void importAccount();
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.etesync.syncadapter.ui.importlocal
|
||||
|
||||
/**
|
||||
* Created by tal on 30/03/17.
|
||||
*/
|
||||
|
||||
interface SelectImportMethod {
|
||||
fun importFile()
|
||||
|
||||
fun importAccount()
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package com.etesync.syncadapter.ui.setup;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import com.etesync.syncadapter.AccountSettings;
|
||||
import com.etesync.syncadapter.App;
|
||||
import com.etesync.syncadapter.HttpClient;
|
||||
import com.etesync.syncadapter.InvalidAccountException;
|
||||
import com.etesync.syncadapter.R;
|
||||
import com.etesync.syncadapter.journalmanager.Constants;
|
||||
import com.etesync.syncadapter.journalmanager.Crypto;
|
||||
import com.etesync.syncadapter.journalmanager.UserInfoManager;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
||||
|
||||
public class SetupUserInfoFragment extends DialogFragment {
|
||||
private Account account;
|
||||
private AccountSettings settings;
|
||||
|
||||
public static SetupUserInfoFragment newInstance(Account account) {
|
||||
SetupUserInfoFragment frag = new SetupUserInfoFragment();
|
||||
Bundle args = new Bundle(1);
|
||||
args.putParcelable(KEY_ACCOUNT, account);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog progress = new ProgressDialog(getActivity());
|
||||
progress.setTitle(R.string.login_encryption_setup_title);
|
||||
progress.setMessage(getString(R.string.login_encryption_setup));
|
||||
progress.setIndeterminate(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
setCancelable(false);
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
account = getArguments().getParcelable(KEY_ACCOUNT);
|
||||
|
||||
try {
|
||||
settings = new AccountSettings(getContext(), account);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
new SetupUserInfo().execute(account);
|
||||
}
|
||||
|
||||
public static boolean hasUserInfo(Context context, Account account) {
|
||||
AccountSettings settings;
|
||||
try {
|
||||
settings = new AccountSettings(context, account);
|
||||
} catch (InvalidAccountException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return settings.getKeyPair() != null;
|
||||
}
|
||||
|
||||
protected class SetupUserInfo extends AsyncTask<Account, Integer, SetupUserInfo.SetupUserInfoResult> {
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = (ProgressDialog) getDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SetupUserInfo.SetupUserInfoResult doInBackground(Account... accounts) {
|
||||
try {
|
||||
Crypto.CryptoManager cryptoManager;
|
||||
OkHttpClient httpClient = HttpClient.create(getContext(), settings);
|
||||
|
||||
UserInfoManager userInfoManager = new UserInfoManager(httpClient, HttpUrl.get(settings.getUri()));
|
||||
UserInfoManager.UserInfo userInfo = userInfoManager.get(account.name);
|
||||
|
||||
if (userInfo == null) {
|
||||
App.log.info("Creating userInfo for " + account.name);
|
||||
cryptoManager = new Crypto.CryptoManager(Constants.CURRENT_VERSION, settings.password(), "userInfo");
|
||||
userInfo = UserInfoManager.UserInfo.generate(cryptoManager, account.name);
|
||||
userInfoManager.create(userInfo);
|
||||
} else {
|
||||
App.log.info("Fetched userInfo for " + account.name);
|
||||
cryptoManager = new Crypto.CryptoManager(userInfo.getVersion(), settings.password(), "userInfo");
|
||||
userInfo.verify(cryptoManager);
|
||||
}
|
||||
|
||||
Crypto.AsymmetricKeyPair keyPair = new Crypto.AsymmetricKeyPair(userInfo.getContent(cryptoManager), userInfo.getPubkey());
|
||||
|
||||
return new SetupUserInfoResult(keyPair, null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new SetupUserInfoResult(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SetupUserInfoResult result) {
|
||||
if (result.exception == null) {
|
||||
settings.setKeyPair(result.keyPair);
|
||||
} else {
|
||||
Dialog dialog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.login_user_info_error_title)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setMessage(result.exception.getLocalizedMessage())
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// dismiss
|
||||
}
|
||||
})
|
||||
.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
|
||||
class SetupUserInfoResult {
|
||||
final Crypto.AsymmetricKeyPair keyPair;
|
||||
final Exception exception;
|
||||
|
||||
SetupUserInfoResult(final Crypto.AsymmetricKeyPair keyPair, final Exception exception) {
|
||||
this.keyPair = keyPair;
|
||||
this.exception = exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package com.etesync.syncadapter.ui.setup
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Dialog
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v7.app.AlertDialog
|
||||
import com.etesync.syncadapter.*
|
||||
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||
import com.etesync.syncadapter.journalmanager.Constants
|
||||
import com.etesync.syncadapter.journalmanager.Crypto
|
||||
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class SetupUserInfoFragment : DialogFragment() {
|
||||
private var account: Account? = null
|
||||
private var settings: AccountSettings? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(activity)
|
||||
progress.setTitle(R.string.login_encryption_setup_title)
|
||||
progress.setMessage(getString(R.string.login_encryption_setup))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
account = arguments!!.getParcelable(KEY_ACCOUNT)
|
||||
|
||||
try {
|
||||
settings = AccountSettings(context!!, account!!)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
SetupUserInfo().execute(account)
|
||||
}
|
||||
|
||||
protected inner class SetupUserInfo : AsyncTask<Account, Int, SetupUserInfo.SetupUserInfoResult>() {
|
||||
internal var progressDialog = dialog as ProgressDialog
|
||||
|
||||
override fun onPreExecute() {
|
||||
progressDialog = dialog as ProgressDialog
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg accounts: Account): SetupUserInfo.SetupUserInfoResult {
|
||||
try {
|
||||
val cryptoManager: Crypto.CryptoManager
|
||||
val httpClient = HttpClient.create(context!!, settings!!)
|
||||
|
||||
val userInfoManager = UserInfoManager(httpClient, HttpUrl.get(settings!!.uri!!)!!)
|
||||
var userInfo: UserInfoManager.UserInfo? = userInfoManager[account!!.name]
|
||||
|
||||
if (userInfo == null) {
|
||||
App.log.info("Creating userInfo for " + account!!.name)
|
||||
cryptoManager = Crypto.CryptoManager(Constants.CURRENT_VERSION, settings!!.password(), "userInfo")
|
||||
userInfo = UserInfoManager.UserInfo.generate(cryptoManager, account!!.name)
|
||||
userInfoManager.create(userInfo)
|
||||
} else {
|
||||
App.log.info("Fetched userInfo for " + account!!.name)
|
||||
cryptoManager = Crypto.CryptoManager(userInfo.version!!.toInt(), settings!!.password(), "userInfo")
|
||||
userInfo.verify(cryptoManager)
|
||||
}
|
||||
|
||||
val keyPair = Crypto.AsymmetricKeyPair(userInfo.getContent(cryptoManager)!!, userInfo.pubkey!!)
|
||||
|
||||
return SetupUserInfoResult(keyPair, null)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return SetupUserInfoResult(null, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: SetupUserInfoResult) {
|
||||
if (result.exception == null) {
|
||||
settings!!.keyPair = result.keyPair
|
||||
} else {
|
||||
val dialog = AlertDialog.Builder(activity!!)
|
||||
.setTitle(R.string.login_user_info_error_title)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setMessage(result.exception.localizedMessage)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||
// dismiss
|
||||
}
|
||||
.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
inner class SetupUserInfoResult(val keyPair: Crypto.AsymmetricKeyPair?, val exception: Exception?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account): SetupUserInfoFragment {
|
||||
val frag = SetupUserInfoFragment()
|
||||
val args = Bundle(1)
|
||||
args.putParcelable(KEY_ACCOUNT, account)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
|
||||
fun hasUserInfo(context: Context, account: Account): Boolean {
|
||||
val settings: AccountSettings
|
||||
try {
|
||||
settings = AccountSettings(context, account)
|
||||
} catch (e: InvalidAccountException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
|
||||
return settings.keyPair != null
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue