mirror of
https://github.com/etesync/android
synced 2025-05-22 16:58:52 +00:00
Kotlin: more kotlin migration.
This commit is contained in:
parent
c26ae4fba6
commit
f77063ff1a
@ -395,7 +395,7 @@ public class App extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fromVersion < 10) {
|
if (fromVersion < 10) {
|
||||||
HintManager.setHintSeen(this, AccountsActivity.HINT_ACCOUNT_ADD, true);
|
HintManager.setHintSeen(this, AccountsActivity.Companion.getHINT_ACCOUNT_ADD(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromVersion < 11) {
|
if (fromVersion < 11) {
|
||||||
|
@ -72,7 +72,7 @@ public class NotificationHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
detailsIntent = new Intent(context, NotificationHandlerActivity.class);
|
detailsIntent = new Intent(context, NotificationHandlerActivity.class);
|
||||||
detailsIntent.putExtra(DebugInfoActivity.KEY_THROWABLE, e);
|
detailsIntent.putExtra(DebugInfoActivity.Companion.getKEY_THROWABLE(), e);
|
||||||
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + notificationTag));
|
detailsIntent.setData(Uri.parse("uri://" + getClass().getName() + "/" + notificationTag));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,16 +127,16 @@ public class NotificationHelper {
|
|||||||
public void onCreate(Bundle savedBundle) {
|
public void onCreate(Bundle savedBundle) {
|
||||||
super.onCreate(savedBundle);
|
super.onCreate(savedBundle);
|
||||||
Bundle extras = getIntent().getExtras();
|
Bundle extras = getIntent().getExtras();
|
||||||
Exception e = (Exception) extras.get(DebugInfoActivity.KEY_THROWABLE);
|
Exception e = (Exception) extras.get(DebugInfoActivity.Companion.getKEY_THROWABLE());
|
||||||
|
|
||||||
Intent detailsIntent;
|
Intent detailsIntent;
|
||||||
if (e instanceof Exceptions.UnauthorizedException) {
|
if (e instanceof Exceptions.UnauthorizedException) {
|
||||||
detailsIntent = new Intent(this, AccountSettingsActivity.class);
|
detailsIntent = new Intent(this, AccountSettingsActivity.class);
|
||||||
} else if (e instanceof Exceptions.UserInactiveException) {
|
} else if (e instanceof Exceptions.UserInactiveException) {
|
||||||
WebViewActivity.openUrl(this, Constants.dashboard);
|
WebViewActivity.Companion.openUrl(this, Constants.dashboard);
|
||||||
return;
|
return;
|
||||||
} else if (e instanceof AccountSettings.AccountMigrationException) {
|
} else if (e instanceof AccountSettings.AccountMigrationException) {
|
||||||
WebViewActivity.openUrl(this, Constants.faqUri.buildUpon().encodedFragment("account-migration-error").build());
|
WebViewActivity.Companion.openUrl(this, Constants.faqUri.buildUpon().encodedFragment("account-migration-error").build());
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
detailsIntent = new Intent(this, DebugInfoActivity.class);
|
detailsIntent = new Intent(this, DebugInfoActivity.class);
|
||||||
|
@ -1,228 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
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.app.LoaderManager;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v4.view.ViewPager;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.text.Html;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.util.Linkify;
|
|
||||||
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.BuildConfig;
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import ezvcard.Ezvcard;
|
|
||||||
|
|
||||||
public class AboutActivity extends BaseActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_about);
|
|
||||||
|
|
||||||
setSupportActionBar((Toolbar)findViewById(R.id.toolbar));
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
ViewPager viewPager = (ViewPager)findViewById(R.id.viewpager);
|
|
||||||
viewPager.setAdapter(new TabsAdapter(getSupportFragmentManager()));
|
|
||||||
|
|
||||||
TabLayout tabLayout = (TabLayout)findViewById(R.id.tabs);
|
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ComponentInfo {
|
|
||||||
final String title, version, website, copyright;
|
|
||||||
final int licenseInfo;
|
|
||||||
final String licenseTextFile;
|
|
||||||
|
|
||||||
ComponentInfo(final String title, final String version, final String website, final String copyright, final int licenseInfo, final String licenseTextFile) {
|
|
||||||
this.title = title;
|
|
||||||
this.version = version;
|
|
||||||
this.website = website;
|
|
||||||
this.copyright = copyright;
|
|
||||||
this.licenseInfo = licenseInfo;
|
|
||||||
this.licenseTextFile = licenseTextFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static ComponentInfo components[] = {
|
|
||||||
new ComponentInfo(
|
|
||||||
App.getAppName(), BuildConfig.VERSION_NAME, Constants.webUri.toString(),
|
|
||||||
DateFormatUtils.format(BuildConfig.buildTime, "yyyy") + " Tom Hacohen",
|
|
||||||
R.string.about_license_info_no_warranty, "gpl-3.0-standalone.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"DAVdroid", "(forked from)", "https://syncadapter.bitfire.at",
|
|
||||||
"Ricki Hirner, Bernhard Stockmann (bitfire web engineering)",
|
|
||||||
R.string.about_license_info_no_warranty, "gpl-3.0-standalone.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"AmbilWarna", null, "https://github.com/yukuku/ambilwarna",
|
|
||||||
"Yuku", R.string.about_license_info_no_warranty, "apache2.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"Apache Commons", null, "http://commons.apache.org/",
|
|
||||||
"Apache Software Foundation", R.string.about_license_info_no_warranty, "apache2.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"dnsjava", null, "http://dnsjava.org/",
|
|
||||||
"Brian Wellington", R.string.about_license_info_no_warranty, "bsd.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"ez-vcard", Ezvcard.VERSION, "https://github.com/mangstadt/ez-vcard",
|
|
||||||
"Michael Angstadt", R.string.about_license_info_no_warranty, "bsd.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"ical4j", "2.x", "https://ical4j.github.io/",
|
|
||||||
"Ben Fortuna", R.string.about_license_info_no_warranty, "bsd-3clause.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"OkHttp", null, "https://square.github.io/okhttp/",
|
|
||||||
"Square, Inc.", R.string.about_license_info_no_warranty, "apache2.html"
|
|
||||||
), new ComponentInfo(
|
|
||||||
"Project Lombok", null, "https://projectlombok.org/",
|
|
||||||
"The Project Lombok Authors", R.string.about_license_info_no_warranty, "mit.html"
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private static class TabsAdapter extends FragmentPagerAdapter {
|
|
||||||
public TabsAdapter(FragmentManager fm) {
|
|
||||||
super(fm);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return components.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getPageTitle(int position) {
|
|
||||||
return components[position].title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int position) {
|
|
||||||
return ComponentFragment.instantiate(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ComponentFragment extends Fragment implements LoaderManager.LoaderCallbacks<Spanned> {
|
|
||||||
private static final String
|
|
||||||
KEY_POSITION = "position",
|
|
||||||
KEY_FILE_NAME = "fileName";
|
|
||||||
|
|
||||||
public static ComponentFragment instantiate(int position) {
|
|
||||||
ComponentFragment frag = new ComponentFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putInt(KEY_POSITION, position);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
ComponentInfo info = components[getArguments().getInt(KEY_POSITION)];
|
|
||||||
|
|
||||||
View v = inflater.inflate(R.layout.about_component, container, false);
|
|
||||||
|
|
||||||
TextView tv = (TextView)v.findViewById(R.id.title);
|
|
||||||
tv.setText(info.title + (info.version != null ? (" " + info.version) : ""));
|
|
||||||
|
|
||||||
tv = (TextView)v.findViewById(R.id.website);
|
|
||||||
tv.setAutoLinkMask(Linkify.WEB_URLS);
|
|
||||||
tv.setText(info.website);
|
|
||||||
|
|
||||||
tv = (TextView)v.findViewById(R.id.copyright);
|
|
||||||
tv.setText("© " + info.copyright);
|
|
||||||
|
|
||||||
tv = (TextView)v.findViewById(R.id.license_info);
|
|
||||||
tv.setText(info.licenseInfo);
|
|
||||||
|
|
||||||
// load and format license text
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putString(KEY_FILE_NAME, info.licenseTextFile);
|
|
||||||
getLoaderManager().initLoader(0, args, this);
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Spanned> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new LicenseLoader(getContext(), args.getString(KEY_FILE_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Spanned> loader, Spanned license) {
|
|
||||||
if (getView() != null) {
|
|
||||||
TextView tv = (TextView)getView().findViewById(R.id.license_text);
|
|
||||||
if (tv != null) {
|
|
||||||
tv.setAutoLinkMask(Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS);
|
|
||||||
tv.setText(license);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Spanned> loader) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LicenseLoader extends AsyncTaskLoader<Spanned> {
|
|
||||||
final String fileName;
|
|
||||||
Spanned content;
|
|
||||||
|
|
||||||
LicenseLoader(Context context, String fileName) {
|
|
||||||
super(context);
|
|
||||||
this.fileName = fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
if (content == null)
|
|
||||||
forceLoad();
|
|
||||||
else
|
|
||||||
deliverResult(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Spanned loadInBackground() {
|
|
||||||
App.log.fine("Loading license file " + fileName);
|
|
||||||
try {
|
|
||||||
InputStream is = getContext().getResources().getAssets().open(fileName);
|
|
||||||
byte[] raw = IOUtils.toByteArray(is);
|
|
||||||
is.close();
|
|
||||||
return content = Html.fromHtml(new String(raw));
|
|
||||||
} catch (IOException e) {
|
|
||||||
App.log.log(Level.SEVERE, "Couldn't read license file", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
193
app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.kt
Normal file
193
app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.kt
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
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.app.LoaderManager
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v4.view.ViewPager
|
||||||
|
import android.support.v7.widget.Toolbar
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.util.Linkify
|
||||||
|
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.BuildConfig
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import ezvcard.Ezvcard
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
import org.apache.commons.lang3.time.DateFormatUtils
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
class AboutActivity : BaseActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_about)
|
||||||
|
|
||||||
|
setSupportActionBar(findViewById<View>(R.id.toolbar) as Toolbar)
|
||||||
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
val viewPager = findViewById<View>(R.id.viewpager) as ViewPager
|
||||||
|
viewPager.adapter = TabsAdapter(supportFragmentManager)
|
||||||
|
|
||||||
|
val tabLayout = findViewById<View>(R.id.tabs) as TabLayout
|
||||||
|
tabLayout.setupWithViewPager(viewPager)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ComponentInfo internal constructor(internal val title: String, internal val version: String?, internal val website: String, internal val copyright: String, internal val licenseInfo: Int, internal val licenseTextFile: String)
|
||||||
|
|
||||||
|
|
||||||
|
private class TabsAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return components.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPageTitle(position: Int): CharSequence? {
|
||||||
|
return components[position].title
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(position: Int): Fragment {
|
||||||
|
return ComponentFragment.instantiate(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComponentFragment : Fragment(), LoaderManager.LoaderCallbacks<Spanned> {
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val info = components[arguments!!.getInt(KEY_POSITION)]
|
||||||
|
|
||||||
|
val v = inflater.inflate(R.layout.about_component, container, false)
|
||||||
|
|
||||||
|
var tv = v.findViewById<View>(R.id.title) as TextView
|
||||||
|
tv.text = info.title + if (info.version != null) " " + info.version else ""
|
||||||
|
|
||||||
|
tv = v.findViewById<View>(R.id.website) as TextView
|
||||||
|
tv.autoLinkMask = Linkify.WEB_URLS
|
||||||
|
tv.text = info.website
|
||||||
|
|
||||||
|
tv = v.findViewById<View>(R.id.copyright) as TextView
|
||||||
|
tv.text = "© " + info.copyright
|
||||||
|
|
||||||
|
tv = v.findViewById<View>(R.id.license_info) as TextView
|
||||||
|
tv.setText(info.licenseInfo)
|
||||||
|
|
||||||
|
// load and format license text
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putString(KEY_FILE_NAME, info.licenseTextFile)
|
||||||
|
loaderManager.initLoader(0, args, this)
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Spanned> {
|
||||||
|
return LicenseLoader(context!!, args!!.getString(KEY_FILE_NAME))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<Spanned>, license: Spanned) {
|
||||||
|
if (view != null) {
|
||||||
|
val tv = view!!.findViewById<View>(R.id.license_text) as TextView
|
||||||
|
if (tv != null) {
|
||||||
|
tv.autoLinkMask = Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS
|
||||||
|
tv.text = license
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<Spanned>) {}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val KEY_POSITION = "position"
|
||||||
|
private val KEY_FILE_NAME = "fileName"
|
||||||
|
|
||||||
|
fun instantiate(position: Int): ComponentFragment {
|
||||||
|
val frag = ComponentFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putInt(KEY_POSITION, position)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LicenseLoader internal constructor(context: Context, internal val fileName: String) : AsyncTaskLoader<Spanned>(context) {
|
||||||
|
internal var content: Spanned? = null
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
if (content == null)
|
||||||
|
forceLoad()
|
||||||
|
else
|
||||||
|
deliverResult(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): Spanned? {
|
||||||
|
App.log.fine("Loading license file $fileName")
|
||||||
|
try {
|
||||||
|
val `is` = context.resources.assets.open(fileName)
|
||||||
|
val raw = IOUtils.toByteArray(`is`)
|
||||||
|
`is`.close()
|
||||||
|
content = Html.fromHtml(String(raw))
|
||||||
|
return content
|
||||||
|
} catch (e: IOException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't read license file", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val components = arrayOf(ComponentInfo(
|
||||||
|
App.getAppName(), BuildConfig.VERSION_NAME, Constants.webUri.toString(),
|
||||||
|
DateFormatUtils.format(BuildConfig.buildTime, "yyyy") + " Tom Hacohen",
|
||||||
|
R.string.about_license_info_no_warranty, "gpl-3.0-standalone.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"DAVdroid", "(forked from)", "https://syncadapter.bitfire.at",
|
||||||
|
"Ricki Hirner, Bernhard Stockmann (bitfire web engineering)",
|
||||||
|
R.string.about_license_info_no_warranty, "gpl-3.0-standalone.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"AmbilWarna", null, "https://github.com/yukuku/ambilwarna",
|
||||||
|
"Yuku", R.string.about_license_info_no_warranty, "apache2.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"Apache Commons", null, "http://commons.apache.org/",
|
||||||
|
"Apache Software Foundation", R.string.about_license_info_no_warranty, "apache2.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"dnsjava", null, "http://dnsjava.org/",
|
||||||
|
"Brian Wellington", R.string.about_license_info_no_warranty, "bsd.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"ez-vcard", Ezvcard.VERSION, "https://github.com/mangstadt/ez-vcard",
|
||||||
|
"Michael Angstadt", R.string.about_license_info_no_warranty, "bsd.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"ical4j", "2.x", "https://ical4j.github.io/",
|
||||||
|
"Ben Fortuna", R.string.about_license_info_no_warranty, "bsd-3clause.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"OkHttp", null, "https://square.github.io/okhttp/",
|
||||||
|
"Square, Inc.", R.string.about_license_info_no_warranty, "apache2.html"
|
||||||
|
), ComponentInfo(
|
||||||
|
"Project Lombok", null, "https://projectlombok.org/",
|
||||||
|
"The Project Lombok Authors", R.string.about_license_info_no_warranty, "mit.html"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,481 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.accounts.AccountManagerCallback;
|
|
||||||
import android.accounts.AccountManagerFuture;
|
|
||||||
import android.accounts.AuthenticatorException;
|
|
||||||
import android.accounts.OperationCanceledException;
|
|
||||||
import android.app.LoaderManager;
|
|
||||||
import android.content.AsyncTaskLoader;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.Loader;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.content.SyncStatusObserver;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.support.v7.widget.CardView;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.AccountSettings;
|
|
||||||
import com.etesync.syncadapter.AccountUpdateService;
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto;
|
|
||||||
import com.etesync.syncadapter.model.CollectionInfo;
|
|
||||||
import com.etesync.syncadapter.model.JournalEntity;
|
|
||||||
import com.etesync.syncadapter.model.ServiceEntity;
|
|
||||||
import com.etesync.syncadapter.resource.LocalAddressBook;
|
|
||||||
import com.etesync.syncadapter.resource.LocalCalendar;
|
|
||||||
import com.etesync.syncadapter.ui.setup.SetupUserInfoFragment;
|
|
||||||
import com.etesync.syncadapter.utils.HintManager;
|
|
||||||
import com.etesync.syncadapter.utils.ShowcaseBuilder;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import at.bitfire.ical4android.TaskProvider;
|
|
||||||
import at.bitfire.vcard4android.ContactsStorageException;
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
import tourguide.tourguide.ToolTip;
|
|
||||||
|
|
||||||
import static android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
|
|
||||||
|
|
||||||
public class AccountActivity extends BaseActivity implements Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks<AccountActivity.AccountInfo>, Refreshable {
|
|
||||||
public static final String EXTRA_ACCOUNT = "account";
|
|
||||||
private static final String HINT_VIEW_COLLECTION = "ViewCollection";
|
|
||||||
|
|
||||||
private Account account;
|
|
||||||
private AccountInfo accountInfo;
|
|
||||||
|
|
||||||
ListView listCalDAV, listCardDAV;
|
|
||||||
Toolbar tbCardDAV, tbCalDAV;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
account = getIntent().getParcelableExtra(EXTRA_ACCOUNT);
|
|
||||||
setTitle(account.name);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_account);
|
|
||||||
|
|
||||||
Drawable icMenu = ContextCompat.getDrawable(this, R.drawable.ic_menu_light);
|
|
||||||
|
|
||||||
// CardDAV toolbar
|
|
||||||
tbCardDAV = (Toolbar)findViewById(R.id.carddav_menu);
|
|
||||||
tbCardDAV.setOverflowIcon(icMenu);
|
|
||||||
tbCardDAV.inflateMenu(R.menu.carddav_actions);
|
|
||||||
tbCardDAV.setOnMenuItemClickListener(this);
|
|
||||||
tbCardDAV.setTitle(R.string.settings_carddav);
|
|
||||||
|
|
||||||
// CalDAV toolbar
|
|
||||||
tbCalDAV = (Toolbar)findViewById(R.id.caldav_menu);
|
|
||||||
tbCalDAV.setOverflowIcon(icMenu);
|
|
||||||
tbCalDAV.inflateMenu(R.menu.caldav_actions);
|
|
||||||
tbCalDAV.setOnMenuItemClickListener(this);
|
|
||||||
tbCalDAV.setTitle(R.string.settings_caldav);
|
|
||||||
|
|
||||||
// load CardDAV/CalDAV journals
|
|
||||||
getLoaderManager().initLoader(0, getIntent().getExtras(), this);
|
|
||||||
|
|
||||||
if (!HintManager.getHintSeen(this, HINT_VIEW_COLLECTION)) {
|
|
||||||
ShowcaseBuilder.getBuilder(this)
|
|
||||||
.setToolTip(new ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_view_collection)))
|
|
||||||
.playOn(tbCardDAV);
|
|
||||||
HintManager.setHintSeen(this, HINT_VIEW_COLLECTION, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SetupUserInfoFragment.hasUserInfo(this, account)) {
|
|
||||||
SetupUserInfoFragment.newInstance(account).show(getSupportFragmentManager(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.activity_account, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.sync_now:
|
|
||||||
requestSync();
|
|
||||||
break;
|
|
||||||
case R.id.settings:
|
|
||||||
Intent intent = new Intent(this, AccountSettingsActivity.class);
|
|
||||||
intent.putExtra(Constants.KEY_ACCOUNT, account);
|
|
||||||
startActivity(intent);
|
|
||||||
break;
|
|
||||||
case R.id.delete_account:
|
|
||||||
new AlertDialog.Builder(AccountActivity.this)
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setTitle(R.string.account_delete_confirmation_title)
|
|
||||||
.setMessage(R.string.account_delete_confirmation_text)
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
deleteAccount();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
break;
|
|
||||||
case R.id.show_fingerprint:
|
|
||||||
View view = getLayoutInflater().inflate(R.layout.fingerprint_alertdialog, null);
|
|
||||||
view.findViewById(R.id.body).setVisibility(View.GONE);
|
|
||||||
((TextView) view.findViewById(R.id.fingerprint)).setText(getFormattedFingerprint());
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(AccountActivity.this)
|
|
||||||
.setIcon(R.drawable.ic_fingerprint_dark)
|
|
||||||
.setTitle(R.string.show_fingperprint_title)
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}).create();
|
|
||||||
dialog.show();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
|
||||||
CollectionInfo info;
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.create_calendar:
|
|
||||||
info = new CollectionInfo();
|
|
||||||
info.type = CollectionInfo.Type.CALENDAR;
|
|
||||||
startActivity(CreateCollectionActivity.newIntent(AccountActivity.this, account, info));
|
|
||||||
break;
|
|
||||||
case R.id.create_addressbook:
|
|
||||||
info = new CollectionInfo();
|
|
||||||
info.type = CollectionInfo.Type.ADDRESS_BOOK;
|
|
||||||
startActivity(CreateCollectionActivity.newIntent(AccountActivity.this, account, info));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdapterView.OnItemClickListener onItemClickListener = new AdapterView.OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
final ListView list = (ListView)parent;
|
|
||||||
final ArrayAdapter<JournalEntity> adapter = (ArrayAdapter)list.getAdapter();
|
|
||||||
final JournalEntity journalEntity = adapter.getItem(position);
|
|
||||||
final CollectionInfo info = journalEntity.getInfo();
|
|
||||||
|
|
||||||
startActivity(ViewCollectionActivity.newIntent(AccountActivity.this, account, info));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private String getFormattedFingerprint() {
|
|
||||||
AccountSettings settings = null;
|
|
||||||
try {
|
|
||||||
settings = new AccountSettings(this, account);
|
|
||||||
return Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(settings.getKeyPair().getPublicKey());
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LOADERS AND LOADED DATA */
|
|
||||||
|
|
||||||
protected static class AccountInfo {
|
|
||||||
ServiceInfo carddav, caldav;
|
|
||||||
|
|
||||||
public static class ServiceInfo {
|
|
||||||
long id;
|
|
||||||
boolean refreshing;
|
|
||||||
|
|
||||||
List<JournalEntity> journals;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<AccountInfo> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new AccountLoader(this, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refresh() {
|
|
||||||
getLoaderManager().restartLoader(0, getIntent().getExtras(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<AccountInfo> loader, final AccountInfo info) {
|
|
||||||
accountInfo = info;
|
|
||||||
|
|
||||||
CardView card = (CardView)findViewById(R.id.carddav);
|
|
||||||
if (info.carddav != null) {
|
|
||||||
ProgressBar progress = (ProgressBar)findViewById(R.id.carddav_refreshing);
|
|
||||||
progress.setVisibility(info.carddav.refreshing ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
listCardDAV = (ListView)findViewById(R.id.address_books);
|
|
||||||
listCardDAV.setEnabled(!info.carddav.refreshing);
|
|
||||||
listCardDAV.setAlpha(info.carddav.refreshing ? 0.5f : 1);
|
|
||||||
|
|
||||||
final CollectionListAdapter adapter = new CollectionListAdapter(this, account);
|
|
||||||
adapter.addAll(info.carddav.journals);
|
|
||||||
listCardDAV.setAdapter(adapter);
|
|
||||||
listCardDAV.setOnItemClickListener(onItemClickListener);
|
|
||||||
} else
|
|
||||||
card.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
card = (CardView)findViewById(R.id.caldav);
|
|
||||||
if (info.caldav != null) {
|
|
||||||
ProgressBar progress = (ProgressBar)findViewById(R.id.caldav_refreshing);
|
|
||||||
progress.setVisibility(info.caldav.refreshing ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
listCalDAV = (ListView)findViewById(R.id.calendars);
|
|
||||||
listCalDAV.setEnabled(!info.caldav.refreshing);
|
|
||||||
listCalDAV.setAlpha(info.caldav.refreshing ? 0.5f : 1);
|
|
||||||
|
|
||||||
final CollectionListAdapter adapter = new CollectionListAdapter(this, account);
|
|
||||||
adapter.addAll(info.caldav.journals);
|
|
||||||
listCalDAV.setAdapter(adapter);
|
|
||||||
listCalDAV.setOnItemClickListener(onItemClickListener);
|
|
||||||
} else
|
|
||||||
card.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<AccountInfo> loader) {
|
|
||||||
if (listCardDAV != null)
|
|
||||||
listCardDAV.setAdapter(null);
|
|
||||||
|
|
||||||
if (listCalDAV != null)
|
|
||||||
listCalDAV.setAdapter(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class AccountLoader extends AsyncTaskLoader<AccountInfo> implements AccountUpdateService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
|
|
||||||
private final Account account;
|
|
||||||
private AccountUpdateService.InfoBinder davService;
|
|
||||||
private Object syncStatusListener;
|
|
||||||
|
|
||||||
public AccountLoader(Context context, Account account) {
|
|
||||||
super(context);
|
|
||||||
this.account = account;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
syncStatusListener = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE, this);
|
|
||||||
|
|
||||||
getContext().bindService(new Intent(getContext(), AccountUpdateService.class), this, Context.BIND_AUTO_CREATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStopLoading() {
|
|
||||||
davService.removeRefreshingStatusListener(this);
|
|
||||||
getContext().unbindService(this);
|
|
||||||
|
|
||||||
if (syncStatusListener != null)
|
|
||||||
ContentResolver.removeStatusChangeListener(syncStatusListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
||||||
davService = (AccountUpdateService.InfoBinder)service;
|
|
||||||
davService.addRefreshingStatusListener(this, false);
|
|
||||||
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
|
||||||
davService = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDavRefreshStatusChanged(long id, boolean refreshing) {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStatusChanged(int which) {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AccountInfo loadInBackground() {
|
|
||||||
AccountInfo info = new AccountInfo();
|
|
||||||
|
|
||||||
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
|
|
||||||
|
|
||||||
for (ServiceEntity serviceEntity : data.select(ServiceEntity.class).where(ServiceEntity.ACCOUNT.eq(account.name)).get()) {
|
|
||||||
long id = serviceEntity.getId();
|
|
||||||
CollectionInfo.Type service = serviceEntity.getType();
|
|
||||||
if (service.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
|
|
||||||
info.carddav = new AccountInfo.ServiceInfo();
|
|
||||||
info.carddav.id = id;
|
|
||||||
info.carddav.refreshing = (davService != null && davService.isRefreshing(id)) || ContentResolver.isSyncActive(account, App.getAddressBooksAuthority());
|
|
||||||
info.carddav.journals = JournalEntity.getJournals(data, serviceEntity);
|
|
||||||
|
|
||||||
AccountManager accountManager = AccountManager.get(getContext());
|
|
||||||
for (Account addrBookAccount : accountManager.getAccountsByType(App.getAddressBookAccountType())) {
|
|
||||||
LocalAddressBook addressBook = new LocalAddressBook(getContext(), addrBookAccount, null);
|
|
||||||
try {
|
|
||||||
if (account.equals(addressBook.getMainAccount()))
|
|
||||||
info.carddav.refreshing |= ContentResolver.isSyncActive(addrBookAccount, ContactsContract.AUTHORITY);
|
|
||||||
} catch(ContactsStorageException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (service.equals(CollectionInfo.Type.CALENDAR)) {
|
|
||||||
info.caldav = new AccountInfo.ServiceInfo();
|
|
||||||
info.caldav.id = id;
|
|
||||||
info.caldav.refreshing = (davService != null && davService.isRefreshing(id)) ||
|
|
||||||
ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY) ||
|
|
||||||
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority);
|
|
||||||
info.caldav.journals = JournalEntity.getJournals(data, serviceEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* LIST ADAPTERS */
|
|
||||||
|
|
||||||
public static class CollectionListAdapter extends ArrayAdapter<JournalEntity> {
|
|
||||||
private Account account;
|
|
||||||
|
|
||||||
public CollectionListAdapter(Context context, Account account) {
|
|
||||||
super(context, R.layout.account_collection_item);
|
|
||||||
this.account = account;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View v, ViewGroup parent) {
|
|
||||||
if (v == null)
|
|
||||||
v = LayoutInflater.from(getContext()).inflate(R.layout.account_collection_item, parent, false);
|
|
||||||
|
|
||||||
final JournalEntity journalEntity = getItem(position);
|
|
||||||
final CollectionInfo info = journalEntity.getInfo();
|
|
||||||
|
|
||||||
TextView tv = (TextView)v.findViewById(R.id.title);
|
|
||||||
tv.setText(TextUtils.isEmpty(info.displayName) ? info.uid : info.displayName);
|
|
||||||
|
|
||||||
tv = (TextView)v.findViewById(R.id.description);
|
|
||||||
if (TextUtils.isEmpty(info.description))
|
|
||||||
tv.setVisibility(View.GONE);
|
|
||||||
else {
|
|
||||||
tv.setVisibility(View.VISIBLE);
|
|
||||||
tv.setText(info.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
final View vColor = v.findViewById(R.id.color);
|
|
||||||
if (info.type.equals(CollectionInfo.Type.ADDRESS_BOOK)) {
|
|
||||||
vColor.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
if (info.color != null) {
|
|
||||||
vColor.setBackgroundColor(info.color);
|
|
||||||
} else {
|
|
||||||
vColor.setBackgroundColor(LocalCalendar.defaultColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
View readOnly = v.findViewById(R.id.read_only);
|
|
||||||
readOnly.setVisibility(journalEntity.isReadOnly() ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
final View shared = v.findViewById(R.id.shared);
|
|
||||||
boolean isOwner = journalEntity.isOwner(account.name);
|
|
||||||
shared.setVisibility(isOwner ? View.GONE : View.VISIBLE);
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* USER ACTIONS */
|
|
||||||
|
|
||||||
private void deleteAccount() {
|
|
||||||
AccountManager accountManager = AccountManager.get(this);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 22)
|
|
||||||
accountManager.removeAccount(account, this, new AccountManagerCallback<Bundle>() {
|
|
||||||
@Override
|
|
||||||
public void run(AccountManagerFuture<Bundle> future) {
|
|
||||||
try {
|
|
||||||
if (future.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT))
|
|
||||||
finish();
|
|
||||||
} catch(OperationCanceledException|IOException|AuthenticatorException e) {
|
|
||||||
App.log.log(Level.SEVERE, "Couldn't remove account", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
else
|
|
||||||
accountManager.removeAccount(account, new AccountManagerCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void run(AccountManagerFuture<Boolean> future) {
|
|
||||||
try {
|
|
||||||
if (future.getResult())
|
|
||||||
finish();
|
|
||||||
} catch (OperationCanceledException|IOException|AuthenticatorException e) {
|
|
||||||
App.log.log(Level.SEVERE, "Couldn't remove account", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void requestSync(Account account) {
|
|
||||||
String authorities[] = {
|
|
||||||
App.getAddressBooksAuthority(),
|
|
||||||
CalendarContract.AUTHORITY,
|
|
||||||
TaskProvider.ProviderName.OpenTasks.authority
|
|
||||||
};
|
|
||||||
|
|
||||||
for (String authority : authorities) {
|
|
||||||
Bundle extras = new Bundle();
|
|
||||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); // manual sync
|
|
||||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); // run immediately (don't queue)
|
|
||||||
ContentResolver.requestSync(account, authority, extras);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestSync() {
|
|
||||||
requestSync(account);
|
|
||||||
Snackbar.make(findViewById(R.id.parent), R.string.account_synchronizing_now, Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
410
app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt
Normal file
410
app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.accounts.AccountManager
|
||||||
|
import android.accounts.AuthenticatorException
|
||||||
|
import android.accounts.OperationCanceledException
|
||||||
|
import android.app.LoaderManager
|
||||||
|
import android.content.*
|
||||||
|
import android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import android.support.v7.widget.CardView
|
||||||
|
import android.support.v7.widget.Toolbar
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.*
|
||||||
|
import android.widget.*
|
||||||
|
import at.bitfire.ical4android.TaskProvider
|
||||||
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
|
import com.etesync.syncadapter.*
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
import com.etesync.syncadapter.model.ServiceEntity
|
||||||
|
import com.etesync.syncadapter.resource.LocalAddressBook
|
||||||
|
import com.etesync.syncadapter.resource.LocalCalendar
|
||||||
|
import com.etesync.syncadapter.ui.setup.SetupUserInfoFragment
|
||||||
|
import com.etesync.syncadapter.utils.HintManager
|
||||||
|
import com.etesync.syncadapter.utils.ShowcaseBuilder
|
||||||
|
import tourguide.tourguide.ToolTip
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks<AccountActivity.AccountInfo>, Refreshable {
|
||||||
|
|
||||||
|
private var account: Account? = null
|
||||||
|
private var accountInfo: AccountInfo? = null
|
||||||
|
|
||||||
|
internal var listCalDAV: ListView? = null
|
||||||
|
internal var listCardDAV: ListView? = null
|
||||||
|
|
||||||
|
private val onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
|
||||||
|
val list = parent as ListView
|
||||||
|
val adapter = list.adapter as ArrayAdapter<*>
|
||||||
|
val journalEntity = adapter.getItem(position) as JournalEntity
|
||||||
|
val info = journalEntity.getInfo()
|
||||||
|
|
||||||
|
startActivity(ViewCollectionActivity.newIntent(this@AccountActivity, account!!, info))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val formattedFingerprint: String?
|
||||||
|
get() {
|
||||||
|
var settings: AccountSettings? = null
|
||||||
|
try {
|
||||||
|
settings = AccountSettings(this, account!!)
|
||||||
|
return Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(settings.keyPair!!.publicKey)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
account = intent.getParcelableExtra(EXTRA_ACCOUNT)
|
||||||
|
title = account!!.name
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_account)
|
||||||
|
|
||||||
|
val icMenu = ContextCompat.getDrawable(this, R.drawable.ic_menu_light)
|
||||||
|
|
||||||
|
// CardDAV toolbar
|
||||||
|
val tbCardDAV = findViewById<View>(R.id.carddav_menu) as Toolbar
|
||||||
|
tbCardDAV.overflowIcon = icMenu
|
||||||
|
tbCardDAV.inflateMenu(R.menu.carddav_actions)
|
||||||
|
tbCardDAV.setOnMenuItemClickListener(this)
|
||||||
|
tbCardDAV.setTitle(R.string.settings_carddav)
|
||||||
|
|
||||||
|
// CalDAV toolbar
|
||||||
|
val tbCalDAV = findViewById<View>(R.id.caldav_menu) as Toolbar
|
||||||
|
tbCalDAV.overflowIcon = icMenu
|
||||||
|
tbCalDAV.inflateMenu(R.menu.caldav_actions)
|
||||||
|
tbCalDAV.setOnMenuItemClickListener(this)
|
||||||
|
tbCalDAV.setTitle(R.string.settings_caldav)
|
||||||
|
|
||||||
|
// load CardDAV/CalDAV journals
|
||||||
|
loaderManager.initLoader(0, intent.extras, this)
|
||||||
|
|
||||||
|
if (!HintManager.getHintSeen(this, HINT_VIEW_COLLECTION)) {
|
||||||
|
ShowcaseBuilder.getBuilder(this)
|
||||||
|
.setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_view_collection)))
|
||||||
|
.playOn(tbCardDAV)
|
||||||
|
HintManager.setHintSeen(this, HINT_VIEW_COLLECTION, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetupUserInfoFragment.hasUserInfo(this, account!!)) {
|
||||||
|
SetupUserInfoFragment.newInstance(account!!).show(supportFragmentManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.activity_account, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.sync_now -> requestSync()
|
||||||
|
R.id.settings -> {
|
||||||
|
val intent = Intent(this, AccountSettingsActivity::class.java)
|
||||||
|
intent.putExtra(Constants.KEY_ACCOUNT, account)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
R.id.delete_account -> AlertDialog.Builder(this@AccountActivity)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setTitle(R.string.account_delete_confirmation_title)
|
||||||
|
.setMessage(R.string.account_delete_confirmation_text)
|
||||||
|
.setNegativeButton(android.R.string.no, null)
|
||||||
|
.setPositiveButton(android.R.string.yes) { dialog, which -> deleteAccount() }
|
||||||
|
.show()
|
||||||
|
R.id.show_fingerprint -> {
|
||||||
|
val view = layoutInflater.inflate(R.layout.fingerprint_alertdialog, null)
|
||||||
|
view.findViewById<View>(R.id.body).visibility = View.GONE
|
||||||
|
(view.findViewById<View>(R.id.fingerprint) as TextView).text = formattedFingerprint
|
||||||
|
val dialog = AlertDialog.Builder(this@AccountActivity)
|
||||||
|
.setIcon(R.drawable.ic_fingerprint_dark)
|
||||||
|
.setTitle(R.string.show_fingperprint_title)
|
||||||
|
.setView(view)
|
||||||
|
.setPositiveButton(android.R.string.yes) { dialog, which -> }.create()
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
else -> return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||||
|
val info: CollectionInfo
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.create_calendar -> {
|
||||||
|
info = CollectionInfo()
|
||||||
|
info.type = CollectionInfo.Type.CALENDAR
|
||||||
|
startActivity(CreateCollectionActivity.newIntent(this@AccountActivity, account!!, info))
|
||||||
|
}
|
||||||
|
R.id.create_addressbook -> {
|
||||||
|
info = CollectionInfo()
|
||||||
|
info.type = CollectionInfo.Type.ADDRESS_BOOK
|
||||||
|
startActivity(CreateCollectionActivity.newIntent(this@AccountActivity, account!!, info))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LOADERS AND LOADED DATA */
|
||||||
|
|
||||||
|
class AccountInfo {
|
||||||
|
internal var carddav: ServiceInfo? = null
|
||||||
|
internal var caldav: ServiceInfo? = null
|
||||||
|
|
||||||
|
class ServiceInfo {
|
||||||
|
internal var id: Long = 0
|
||||||
|
internal var refreshing: Boolean = false
|
||||||
|
|
||||||
|
internal var journals: List<JournalEntity>? = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle): Loader<AccountInfo> {
|
||||||
|
return AccountLoader(this, account!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refresh() {
|
||||||
|
loaderManager.restartLoader(0, intent.extras, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<AccountInfo>, info: AccountInfo) {
|
||||||
|
accountInfo = info
|
||||||
|
|
||||||
|
var card = findViewById<View>(R.id.carddav) as CardView
|
||||||
|
if (info.carddav != null) {
|
||||||
|
val progress = findViewById<View>(R.id.carddav_refreshing) as ProgressBar
|
||||||
|
progress.visibility = if (info.carddav!!.refreshing) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
listCardDAV = findViewById<View>(R.id.address_books) as ListView
|
||||||
|
listCardDAV!!.isEnabled = !info.carddav!!.refreshing
|
||||||
|
listCardDAV!!.setAlpha(if (info.carddav!!.refreshing) 0.5f else 1f)
|
||||||
|
|
||||||
|
val adapter = CollectionListAdapter(this, account!!)
|
||||||
|
adapter.addAll(info.carddav!!.journals!!)
|
||||||
|
listCardDAV!!.adapter = adapter
|
||||||
|
listCardDAV!!.onItemClickListener = onItemClickListener
|
||||||
|
} else
|
||||||
|
card.visibility = View.GONE
|
||||||
|
|
||||||
|
card = findViewById<View>(R.id.caldav) as CardView
|
||||||
|
if (info.caldav != null) {
|
||||||
|
val progress = findViewById<View>(R.id.caldav_refreshing) as ProgressBar
|
||||||
|
progress.visibility = if (info.caldav!!.refreshing) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
listCalDAV = findViewById<View>(R.id.calendars) as ListView
|
||||||
|
listCalDAV!!.isEnabled = !info.caldav!!.refreshing
|
||||||
|
listCalDAV!!.setAlpha(if (info.caldav!!.refreshing) 0.5f else 1f)
|
||||||
|
|
||||||
|
val adapter = CollectionListAdapter(this, account!!)
|
||||||
|
adapter.addAll(info.caldav!!.journals!!)
|
||||||
|
listCalDAV!!.adapter = adapter
|
||||||
|
listCalDAV!!.onItemClickListener = onItemClickListener
|
||||||
|
} else
|
||||||
|
card.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<AccountInfo>) {
|
||||||
|
if (listCardDAV != null)
|
||||||
|
listCardDAV!!.adapter = null
|
||||||
|
|
||||||
|
if (listCalDAV != null)
|
||||||
|
listCalDAV!!.adapter = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AccountLoader(context: Context, private val account: Account) : AsyncTaskLoader<AccountInfo>(context), AccountUpdateService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
|
||||||
|
private var davService: AccountUpdateService.InfoBinder? = null
|
||||||
|
private var syncStatusListener: Any? = null
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
syncStatusListener = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE, this)
|
||||||
|
|
||||||
|
context.bindService(Intent(context, AccountUpdateService::class.java), this, Context.BIND_AUTO_CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopLoading() {
|
||||||
|
davService!!.removeRefreshingStatusListener(this)
|
||||||
|
context.unbindService(this)
|
||||||
|
|
||||||
|
if (syncStatusListener != null)
|
||||||
|
ContentResolver.removeStatusChangeListener(syncStatusListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
davService = service as AccountUpdateService.InfoBinder
|
||||||
|
davService!!.addRefreshingStatusListener(this, false)
|
||||||
|
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
davService = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean) {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(which: Int) {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): AccountInfo {
|
||||||
|
val info = AccountInfo()
|
||||||
|
|
||||||
|
val data = (context.applicationContext as App).data
|
||||||
|
|
||||||
|
for (serviceEntity in data.select(ServiceEntity::class.java).where(ServiceEntity.ACCOUNT.eq(account.name)).get()) {
|
||||||
|
val id = serviceEntity.id.toLong()
|
||||||
|
val service = serviceEntity.type
|
||||||
|
if (service == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||||
|
info.carddav = AccountInfo.ServiceInfo()
|
||||||
|
info.carddav!!.id = id
|
||||||
|
info.carddav!!.refreshing = davService != null && davService!!.isRefreshing(id) || ContentResolver.isSyncActive(account, App.getAddressBooksAuthority())
|
||||||
|
info.carddav!!.journals = JournalEntity.getJournals(data, serviceEntity)
|
||||||
|
|
||||||
|
val accountManager = AccountManager.get(context)
|
||||||
|
for (addrBookAccount in accountManager.getAccountsByType(App.getAddressBookAccountType())) {
|
||||||
|
val addressBook = LocalAddressBook(context, addrBookAccount, null)
|
||||||
|
try {
|
||||||
|
if (account == addressBook.mainAccount)
|
||||||
|
info.carddav!!.refreshing = info.carddav!!.refreshing or ContentResolver.isSyncActive(addrBookAccount, ContactsContract.AUTHORITY)
|
||||||
|
} catch (e: ContactsStorageException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (service == CollectionInfo.Type.CALENDAR) {
|
||||||
|
info.caldav = AccountInfo.ServiceInfo()
|
||||||
|
info.caldav!!.id = id
|
||||||
|
info.caldav!!.refreshing = davService != null && davService!!.isRefreshing(id) ||
|
||||||
|
ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY) ||
|
||||||
|
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority)
|
||||||
|
info.caldav!!.journals = JournalEntity.getJournals(data, serviceEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* LIST ADAPTERS */
|
||||||
|
|
||||||
|
class CollectionListAdapter(context: Context, private val account: Account) : ArrayAdapter<JournalEntity>(context, R.layout.account_collection_item) {
|
||||||
|
|
||||||
|
override fun getView(position: Int, v: View?, parent: ViewGroup): View {
|
||||||
|
var v = v
|
||||||
|
if (v == null)
|
||||||
|
v = LayoutInflater.from(context).inflate(R.layout.account_collection_item, parent, false)
|
||||||
|
|
||||||
|
val journalEntity = getItem(position)
|
||||||
|
val info = journalEntity!!.info
|
||||||
|
|
||||||
|
var tv = v!!.findViewById<View>(R.id.title) as TextView
|
||||||
|
tv.text = if (TextUtils.isEmpty(info.displayName)) info.uid else info.displayName
|
||||||
|
|
||||||
|
tv = v.findViewById<View>(R.id.description) as TextView
|
||||||
|
if (TextUtils.isEmpty(info.description))
|
||||||
|
tv.visibility = View.GONE
|
||||||
|
else {
|
||||||
|
tv.visibility = View.VISIBLE
|
||||||
|
tv.text = info.description
|
||||||
|
}
|
||||||
|
|
||||||
|
val vColor = v.findViewById<View>(R.id.color)
|
||||||
|
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||||
|
vColor.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
if (info.color != null) {
|
||||||
|
vColor.setBackgroundColor(info.color)
|
||||||
|
} else {
|
||||||
|
vColor.setBackgroundColor(LocalCalendar.defaultColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val readOnly = v.findViewById<View>(R.id.read_only)
|
||||||
|
readOnly.visibility = if (journalEntity.isReadOnly) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
val shared = v.findViewById<View>(R.id.shared)
|
||||||
|
val isOwner = journalEntity.isOwner(account.name)
|
||||||
|
shared.visibility = if (isOwner) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* USER ACTIONS */
|
||||||
|
|
||||||
|
private fun deleteAccount() {
|
||||||
|
val accountManager = AccountManager.get(this)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 22)
|
||||||
|
accountManager.removeAccount(account, this, { future ->
|
||||||
|
try {
|
||||||
|
if (future.result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT))
|
||||||
|
finish()
|
||||||
|
} catch (e: OperationCanceledException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't remove account", e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't remove account", e)
|
||||||
|
} catch (e: AuthenticatorException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't remove account", e)
|
||||||
|
}
|
||||||
|
}, null)
|
||||||
|
else
|
||||||
|
accountManager.removeAccount(account, { future ->
|
||||||
|
try {
|
||||||
|
if (future.result)
|
||||||
|
finish()
|
||||||
|
} catch (e: OperationCanceledException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't remove account", e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't remove account", e)
|
||||||
|
} catch (e: AuthenticatorException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't remove account", e)
|
||||||
|
}
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestSync() {
|
||||||
|
requestSync(account)
|
||||||
|
Snackbar.make(findViewById(R.id.parent), R.string.account_synchronizing_now, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EXTRA_ACCOUNT = "account"
|
||||||
|
private val HINT_VIEW_COLLECTION = "ViewCollection"
|
||||||
|
|
||||||
|
protected fun requestSync(account: Account?) {
|
||||||
|
val authorities = arrayOf(App.getAddressBooksAuthority(), CalendarContract.AUTHORITY, TaskProvider.ProviderName.OpenTasks.authority)
|
||||||
|
|
||||||
|
for (authority in authorities) {
|
||||||
|
val extras = Bundle()
|
||||||
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) // manual sync
|
||||||
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) // run immediately (don't queue)
|
||||||
|
ContentResolver.requestSync(account, authority, extras)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.accounts.OnAccountsUpdateListener;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.ListFragment;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AbsListView;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.AccountsChangedReceiver;
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
|
|
||||||
public class AccountListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Account[]>, AdapterView.OnItemClickListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
setListAdapter(new AccountListAdapter(getContext()));
|
|
||||||
|
|
||||||
return inflater.inflate(R.layout.account_list, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
getLoaderManager().initLoader(0, getArguments(), this);
|
|
||||||
|
|
||||||
ListView list = getListView();
|
|
||||||
list.setOnItemClickListener(this);
|
|
||||||
list.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
Account account = (Account)getListAdapter().getItem(position);
|
|
||||||
|
|
||||||
Intent intent = new Intent(getContext(), AccountActivity.class);
|
|
||||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// loader
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Account[]> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new AccountLoader(getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Account[]> loader, Account[] accounts) {
|
|
||||||
AccountListAdapter adapter = (AccountListAdapter)getListAdapter();
|
|
||||||
adapter.clear();
|
|
||||||
adapter.addAll(accounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Account[]> loader) {
|
|
||||||
((AccountListAdapter)getListAdapter()).clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class AccountLoader extends AsyncTaskLoader<Account[]> implements OnAccountsUpdateListener {
|
|
||||||
private final AccountManager accountManager;
|
|
||||||
|
|
||||||
public AccountLoader(Context context) {
|
|
||||||
super(context);
|
|
||||||
accountManager = AccountManager.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
AccountsChangedReceiver.registerListener(this, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStopLoading() {
|
|
||||||
AccountsChangedReceiver.unregisterListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAccountsUpdated(Account[] accounts) {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
public Account[] loadInBackground() {
|
|
||||||
return accountManager.getAccountsByType(App.getAccountType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// list adapter
|
|
||||||
|
|
||||||
static class AccountListAdapter extends ArrayAdapter<Account> {
|
|
||||||
public AccountListAdapter(Context context) {
|
|
||||||
super(context, R.layout.account_list_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View v, ViewGroup parent) {
|
|
||||||
if (v == null)
|
|
||||||
v = LayoutInflater.from(getContext()).inflate(R.layout.account_list_item, parent, false);
|
|
||||||
|
|
||||||
Account account = getItem(position);
|
|
||||||
|
|
||||||
TextView tv = (TextView)v.findViewById(R.id.account_name);
|
|
||||||
tv.setText(account.name);
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.accounts.AccountManager
|
||||||
|
import android.accounts.OnAccountsUpdateListener
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.ListFragment
|
||||||
|
import android.support.v4.app.LoaderManager
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AbsListView
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.etesync.syncadapter.AccountsChangedReceiver
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
|
||||||
|
class AccountListFragment : ListFragment(), LoaderManager.LoaderCallbacks<Array<Account>>, AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
listAdapter = AccountListAdapter(context!!)
|
||||||
|
|
||||||
|
return inflater.inflate(R.layout.account_list, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
loaderManager.initLoader(0, arguments, this)
|
||||||
|
|
||||||
|
val list = listView
|
||||||
|
list.onItemClickListener = this
|
||||||
|
list.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||||
|
val account = listAdapter.getItem(position) as Account
|
||||||
|
|
||||||
|
val intent = Intent(context, AccountActivity::class.java)
|
||||||
|
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// loader
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Array<Account>> {
|
||||||
|
return AccountLoader(context!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<Array<Account>>, accounts: Array<Account>) {
|
||||||
|
val adapter = listAdapter as AccountListAdapter
|
||||||
|
adapter.clear()
|
||||||
|
adapter.addAll(*accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<Array<Account>>) {
|
||||||
|
(listAdapter as AccountListAdapter).clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AccountLoader(context: Context) : AsyncTaskLoader<Array<Account>>(context), OnAccountsUpdateListener {
|
||||||
|
private val accountManager: AccountManager
|
||||||
|
|
||||||
|
init {
|
||||||
|
accountManager = AccountManager.get(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
AccountsChangedReceiver.registerListener(this, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopLoading() {
|
||||||
|
AccountsChangedReceiver.unregisterListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAccountsUpdated(accounts: Array<Account>) {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override fun loadInBackground(): Array<Account>? {
|
||||||
|
return accountManager.getAccountsByType(App.getAccountType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// list adapter
|
||||||
|
|
||||||
|
internal class AccountListAdapter(context: Context) : ArrayAdapter<Account>(context, R.layout.account_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.account_list_item, parent, false)
|
||||||
|
|
||||||
|
val account = getItem(position)
|
||||||
|
|
||||||
|
val tv = v!!.findViewById<View>(R.id.account_name) as TextView
|
||||||
|
tv.text = account!!.name
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,234 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SyncStatusObserver;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.app.NavUtils;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v7.preference.EditTextPreference;
|
|
||||||
import android.support.v7.preference.ListPreference;
|
|
||||||
import android.support.v7.preference.Preference;
|
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
|
||||||
import android.support.v7.preference.SwitchPreferenceCompat;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.AccountSettings;
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.InvalidAccountException;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.setup.LoginCredentials;
|
|
||||||
import com.etesync.syncadapter.ui.setup.LoginCredentialsChangeFragment;
|
|
||||||
|
|
||||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
|
||||||
|
|
||||||
public class AccountSettingsActivity extends BaseActivity {
|
|
||||||
private Account account;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
account = getIntent().getParcelableExtra(KEY_ACCOUNT);
|
|
||||||
setTitle(getString(R.string.settings_title, account.name));
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
if (savedInstanceState == null)
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(android.R.id.content, AccountSettingsFragment.instantiate(this, AccountSettingsFragment.class.getName(), getIntent().getExtras()))
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) {
|
|
||||||
Intent intent = new Intent(this, AccountActivity.class);
|
|
||||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
|
||||||
NavUtils.navigateUpTo(this, intent);
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class AccountSettingsFragment extends PreferenceFragmentCompat implements LoaderManager.LoaderCallbacks<AccountSettings> {
|
|
||||||
Account account;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
account = getArguments().getParcelable(KEY_ACCOUNT);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, getArguments(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreatePreferences(Bundle bundle, String s) {
|
|
||||||
addPreferencesFromResource(R.xml.settings_account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<AccountSettings> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new AccountSettingsLoader(getContext(), (Account)args.getParcelable(KEY_ACCOUNT));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<AccountSettings> loader, final AccountSettings settings) {
|
|
||||||
if (settings == null) {
|
|
||||||
getActivity().finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// category: authentication
|
|
||||||
final EditTextPreference prefPassword = (EditTextPreference)findPreference("password");
|
|
||||||
prefPassword.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
LoginCredentials credentials = newValue != null ? new LoginCredentials(settings.getUri(), account.name, (String) newValue) : null;
|
|
||||||
LoginCredentialsChangeFragment.newInstance(account, credentials).show(getFragmentManager(), null);
|
|
||||||
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// category: synchronization
|
|
||||||
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
|
|
||||||
final Long syncIntervalContacts = settings.getSyncInterval(App.getAddressBooksAuthority());
|
|
||||||
if (syncIntervalContacts != null) {
|
|
||||||
prefSyncContacts.setValue(syncIntervalContacts.toString());
|
|
||||||
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
|
||||||
prefSyncContacts.setSummary(R.string.settings_sync_summary_manually);
|
|
||||||
else
|
|
||||||
prefSyncContacts.setSummary(getString(R.string.settings_sync_summary_periodically, prefSyncContacts.getEntry()));
|
|
||||||
prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
settings.setSyncInterval(App.getAddressBooksAuthority(), Long.parseLong((String)newValue));
|
|
||||||
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
prefSyncContacts.setEnabled(false);
|
|
||||||
prefSyncContacts.setSummary(R.string.settings_sync_summary_not_available);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ListPreference prefSyncCalendars = (ListPreference)findPreference("sync_interval_calendars");
|
|
||||||
final Long syncIntervalCalendars = settings.getSyncInterval(CalendarContract.AUTHORITY);
|
|
||||||
if (syncIntervalCalendars != null) {
|
|
||||||
prefSyncCalendars.setValue(syncIntervalCalendars.toString());
|
|
||||||
if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
|
||||||
prefSyncCalendars.setSummary(R.string.settings_sync_summary_manually);
|
|
||||||
else
|
|
||||||
prefSyncCalendars.setSummary(getString(R.string.settings_sync_summary_periodically, prefSyncCalendars.getEntry()));
|
|
||||||
prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String)newValue));
|
|
||||||
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
prefSyncCalendars.setEnabled(false);
|
|
||||||
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
|
|
||||||
}
|
|
||||||
|
|
||||||
final SwitchPreferenceCompat prefWifiOnly = (SwitchPreferenceCompat)findPreference("sync_wifi_only");
|
|
||||||
prefWifiOnly.setChecked(settings.getSyncWifiOnly());
|
|
||||||
prefWifiOnly.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object wifiOnly) {
|
|
||||||
settings.setSyncWiFiOnly((Boolean)wifiOnly);
|
|
||||||
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final EditTextPreference prefWifiOnlySSID = (EditTextPreference)findPreference("sync_wifi_only_ssid");
|
|
||||||
final String onlySSID = settings.getSyncWifiOnlySSID();
|
|
||||||
prefWifiOnlySSID.setText(onlySSID);
|
|
||||||
if (onlySSID != null)
|
|
||||||
prefWifiOnlySSID.setSummary(getString(R.string.settings_sync_wifi_only_ssid_on, onlySSID));
|
|
||||||
else
|
|
||||||
prefWifiOnlySSID.setSummary(R.string.settings_sync_wifi_only_ssid_off);
|
|
||||||
prefWifiOnlySSID.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
String ssid = (String)newValue;
|
|
||||||
settings.setSyncWifiOnlySSID(!TextUtils.isEmpty(ssid) ? ssid : null);
|
|
||||||
getLoaderManager().restartLoader(0, getArguments(), AccountSettingsFragment.this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<AccountSettings> loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class AccountSettingsLoader extends AsyncTaskLoader<AccountSettings> implements SyncStatusObserver {
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
Object listenerHandle;
|
|
||||||
|
|
||||||
public AccountSettingsLoader(Context context, Account account) {
|
|
||||||
super(context);
|
|
||||||
this.account = account;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
forceLoad();
|
|
||||||
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStopLoading() {
|
|
||||||
ContentResolver.removeStatusChangeListener(listenerHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void abandon() {
|
|
||||||
onStopLoading();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AccountSettings loadInBackground() {
|
|
||||||
AccountSettings settings;
|
|
||||||
try {
|
|
||||||
settings = new AccountSettings(getContext(), account);
|
|
||||||
} catch(InvalidAccountException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStatusChanged(int which) {
|
|
||||||
App.log.fine("Reloading account settings");
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SyncStatusObserver
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.support.v4.app.LoaderManager
|
||||||
|
import android.support.v4.app.NavUtils
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v7.preference.*
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.MenuItem
|
||||||
|
import com.etesync.syncadapter.AccountSettings
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||||
|
import com.etesync.syncadapter.InvalidAccountException
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.setup.LoginCredentials
|
||||||
|
import com.etesync.syncadapter.ui.setup.LoginCredentialsChangeFragment
|
||||||
|
|
||||||
|
class AccountSettingsActivity : BaseActivity() {
|
||||||
|
private var account: Account? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
account = intent.getParcelableExtra(KEY_ACCOUNT)
|
||||||
|
title = getString(R.string.settings_title, account!!.name)
|
||||||
|
|
||||||
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val frag = AccountSettingsFragment()
|
||||||
|
frag.arguments = intent.extras
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(android.R.id.content, frag)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == android.R.id.home) {
|
||||||
|
val intent = Intent(this, AccountActivity::class.java)
|
||||||
|
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account)
|
||||||
|
NavUtils.navigateUpTo(this, intent)
|
||||||
|
return true
|
||||||
|
} else
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSettingsFragment : PreferenceFragmentCompat(), LoaderManager.LoaderCallbacks<AccountSettings> {
|
||||||
|
internal lateinit var account: Account
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
account = arguments!!.getParcelable(KEY_ACCOUNT)
|
||||||
|
|
||||||
|
loaderManager.initLoader(0, arguments, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(bundle: Bundle, s: String) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_account)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<AccountSettings> {
|
||||||
|
return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<AccountSettings>, settings: AccountSettings?) {
|
||||||
|
if (settings == null) {
|
||||||
|
activity!!.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// category: authentication
|
||||||
|
val prefPassword = findPreference("password") as EditTextPreference
|
||||||
|
prefPassword.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
val credentials = if (newValue != null) LoginCredentials(settings.uri, account!!.name, newValue as String) else null
|
||||||
|
LoginCredentialsChangeFragment.newInstance(account!!, credentials!!).show(fragmentManager!!, null)
|
||||||
|
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// category: synchronization
|
||||||
|
val prefSyncContacts = findPreference("sync_interval_contacts") as ListPreference
|
||||||
|
val syncIntervalContacts = settings.getSyncInterval(App.getAddressBooksAuthority())
|
||||||
|
if (syncIntervalContacts != null) {
|
||||||
|
prefSyncContacts.value = syncIntervalContacts.toString()
|
||||||
|
if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||||
|
prefSyncContacts.setSummary(R.string.settings_sync_summary_manually)
|
||||||
|
else
|
||||||
|
prefSyncContacts.summary = getString(R.string.settings_sync_summary_periodically, prefSyncContacts.entry)
|
||||||
|
prefSyncContacts.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
settings.setSyncInterval(App.getAddressBooksAuthority(), java.lang.Long.parseLong(newValue as String))
|
||||||
|
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefSyncContacts.isEnabled = false
|
||||||
|
prefSyncContacts.setSummary(R.string.settings_sync_summary_not_available)
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefSyncCalendars = findPreference("sync_interval_calendars") as ListPreference
|
||||||
|
val syncIntervalCalendars = settings.getSyncInterval(CalendarContract.AUTHORITY)
|
||||||
|
if (syncIntervalCalendars != null) {
|
||||||
|
prefSyncCalendars.value = syncIntervalCalendars.toString()
|
||||||
|
if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY)
|
||||||
|
prefSyncCalendars.setSummary(R.string.settings_sync_summary_manually)
|
||||||
|
else
|
||||||
|
prefSyncCalendars.summary = getString(R.string.settings_sync_summary_periodically, prefSyncCalendars.entry)
|
||||||
|
prefSyncCalendars.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
settings.setSyncInterval(CalendarContract.AUTHORITY, java.lang.Long.parseLong(newValue as String))
|
||||||
|
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefSyncCalendars.isEnabled = false
|
||||||
|
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available)
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefWifiOnly = findPreference("sync_wifi_only") as SwitchPreferenceCompat
|
||||||
|
prefWifiOnly.isChecked = settings.syncWifiOnly
|
||||||
|
prefWifiOnly.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, wifiOnly ->
|
||||||
|
settings.setSyncWiFiOnly(wifiOnly as Boolean)
|
||||||
|
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefWifiOnlySSID = findPreference("sync_wifi_only_ssid") as EditTextPreference
|
||||||
|
val onlySSID = settings.syncWifiOnlySSID
|
||||||
|
prefWifiOnlySSID.text = onlySSID
|
||||||
|
if (onlySSID != null)
|
||||||
|
prefWifiOnlySSID.summary = getString(R.string.settings_sync_wifi_only_ssid_on, onlySSID)
|
||||||
|
else
|
||||||
|
prefWifiOnlySSID.setSummary(R.string.settings_sync_wifi_only_ssid_off)
|
||||||
|
prefWifiOnlySSID.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
val ssid = newValue as String
|
||||||
|
settings.syncWifiOnlySSID = if (!TextUtils.isEmpty(ssid)) ssid else null
|
||||||
|
loaderManager.restartLoader(0, arguments, this@AccountSettingsFragment)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<AccountSettings>) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AccountSettingsLoader(context: Context, internal val account: Account) : AsyncTaskLoader<AccountSettings>(context), SyncStatusObserver {
|
||||||
|
internal lateinit var listenerHandle: Any
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
listenerHandle = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopLoading() {
|
||||||
|
ContentResolver.removeStatusChangeListener(listenerHandle)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun abandon() {
|
||||||
|
onStopLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): AccountSettings? {
|
||||||
|
val settings: AccountSettings
|
||||||
|
try {
|
||||||
|
settings = AccountSettings(context, account)
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(which: Int) {
|
||||||
|
App.log.fine("Reloading account settings")
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SyncStatusObserver;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.design.widget.NavigationView;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v4.app.FragmentTransaction;
|
|
||||||
import android.support.v4.view.GravityCompat;
|
|
||||||
import android.support.v4.widget.DrawerLayout;
|
|
||||||
import android.support.v7.app.ActionBarDrawerToggle;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.setup.LoginActivity;
|
|
||||||
import com.etesync.syncadapter.utils.HintManager;
|
|
||||||
import com.etesync.syncadapter.utils.ShowcaseBuilder;
|
|
||||||
|
|
||||||
import tourguide.tourguide.ToolTip;
|
|
||||||
|
|
||||||
import static android.content.ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS;
|
|
||||||
import static com.etesync.syncadapter.BuildConfig.DEBUG;
|
|
||||||
import static com.etesync.syncadapter.Constants.serviceUrl;
|
|
||||||
|
|
||||||
public class AccountsActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener, SyncStatusObserver {
|
|
||||||
public static final String HINT_ACCOUNT_ADD = "AddAccount";
|
|
||||||
|
|
||||||
private Snackbar syncStatusSnackbar;
|
|
||||||
private Object syncStatusObserver;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_accounts);
|
|
||||||
|
|
||||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
|
|
||||||
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
startActivity(new Intent(AccountsActivity.this, LoginActivity.class));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
|
||||||
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
|
||||||
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
|
|
||||||
drawer.setDrawerListener(toggle);
|
|
||||||
toggle.syncState();
|
|
||||||
|
|
||||||
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
|
|
||||||
navigationView.setNavigationItemSelectedListener(this);
|
|
||||||
navigationView.setItemIconTintList(null);
|
|
||||||
|
|
||||||
if (savedInstanceState == null && !getPackageName().equals(getCallingPackage())) {
|
|
||||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
|
||||||
for (StartupDialogFragment fragment : StartupDialogFragment.getStartupDialogs(this))
|
|
||||||
ft.add(fragment, null);
|
|
||||||
ft.commit();
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
Toast.makeText(this, "Server: " + serviceUrl.toString(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PermissionsActivity.requestAllPermissions(this);
|
|
||||||
|
|
||||||
if (!HintManager.getHintSeen(this, HINT_ACCOUNT_ADD)) {
|
|
||||||
ShowcaseBuilder.getBuilder(this)
|
|
||||||
.setToolTip(new ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.accounts_showcase_add)).setGravity(Gravity.TOP | Gravity.LEFT))
|
|
||||||
.playOn(fab);
|
|
||||||
HintManager.setHintSeen(this, HINT_ACCOUNT_ADD, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
onStatusChanged(SYNC_OBSERVER_TYPE_SETTINGS);
|
|
||||||
syncStatusObserver = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_SETTINGS, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
if (syncStatusObserver != null) {
|
|
||||||
ContentResolver.removeStatusChangeListener(syncStatusObserver);
|
|
||||||
syncStatusObserver = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStatusChanged(int which) {
|
|
||||||
if (syncStatusSnackbar != null) {
|
|
||||||
syncStatusSnackbar.dismiss();
|
|
||||||
syncStatusSnackbar = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ContentResolver.getMasterSyncAutomatically()) {
|
|
||||||
syncStatusSnackbar = Snackbar.make(findViewById(R.id.coordinator), R.string.accounts_global_sync_disabled, Snackbar.LENGTH_INDEFINITE)
|
|
||||||
.setAction(R.string.accounts_global_sync_enable, new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
ContentResolver.setMasterSyncAutomatically(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
syncStatusSnackbar.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
|
||||||
if (drawer.isDrawerOpen(GravityCompat.START))
|
|
||||||
drawer.closeDrawer(GravityCompat.START);
|
|
||||||
else
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onNavigationItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.nav_about:
|
|
||||||
startActivity(new Intent(this, AboutActivity.class));
|
|
||||||
break;
|
|
||||||
case R.id.nav_app_settings:
|
|
||||||
startActivity(new Intent(this, AppSettingsActivity.class));
|
|
||||||
break;
|
|
||||||
case R.id.nav_website:
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri));
|
|
||||||
break;
|
|
||||||
case R.id.nav_guide:
|
|
||||||
WebViewActivity.openUrl(this, Constants.helpUri);
|
|
||||||
break;
|
|
||||||
case R.id.nav_faq:
|
|
||||||
WebViewActivity.openUrl(this, Constants.faqUri);
|
|
||||||
break;
|
|
||||||
case R.id.nav_report_issue:
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Constants.reportIssueUri));
|
|
||||||
break;
|
|
||||||
case R.id.nav_contact:
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Constants.contactUri));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
|
||||||
drawer.closeDrawer(GravityCompat.START);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
138
app/src/main/java/com/etesync/syncadapter/ui/AccountsActivity.kt
Normal file
138
app/src/main/java/com/etesync/syncadapter/ui/AccountsActivity.kt
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SyncStatusObserver
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.design.widget.FloatingActionButton
|
||||||
|
import android.support.design.widget.NavigationView
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.support.v4.view.GravityCompat
|
||||||
|
import android.support.v4.widget.DrawerLayout
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle
|
||||||
|
import android.support.v7.widget.Toolbar
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.etesync.syncadapter.BuildConfig.DEBUG
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.Constants.serviceUrl
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.setup.LoginActivity
|
||||||
|
import com.etesync.syncadapter.utils.HintManager
|
||||||
|
import com.etesync.syncadapter.utils.ShowcaseBuilder
|
||||||
|
import tourguide.tourguide.ToolTip
|
||||||
|
|
||||||
|
class AccountsActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener, SyncStatusObserver {
|
||||||
|
|
||||||
|
private var syncStatusSnackbar: Snackbar? = null
|
||||||
|
private var syncStatusObserver: Any? = null
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_accounts)
|
||||||
|
|
||||||
|
val toolbar = findViewById<View>(R.id.toolbar) as Toolbar
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
|
val fab = findViewById<View>(R.id.fab) as FloatingActionButton
|
||||||
|
fab.setOnClickListener { startActivity(Intent(this@AccountsActivity, LoginActivity::class.java)) }
|
||||||
|
|
||||||
|
val drawer = findViewById<View>(R.id.drawer_layout) as DrawerLayout
|
||||||
|
val toggle = ActionBarDrawerToggle(
|
||||||
|
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
|
||||||
|
drawer.setDrawerListener(toggle)
|
||||||
|
toggle.syncState()
|
||||||
|
|
||||||
|
val navigationView = findViewById<View>(R.id.nav_view) as NavigationView
|
||||||
|
navigationView.setNavigationItemSelectedListener(this)
|
||||||
|
navigationView.itemIconTintList = null
|
||||||
|
|
||||||
|
if (savedInstanceState == null && packageName != callingPackage) {
|
||||||
|
val ft = supportFragmentManager.beginTransaction()
|
||||||
|
for (fragment in StartupDialogFragment.getStartupDialogs(this))
|
||||||
|
ft.add(fragment, null)
|
||||||
|
ft.commit()
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Toast.makeText(this, "Server: " + serviceUrl.toString(), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionsActivity.requestAllPermissions(this)
|
||||||
|
|
||||||
|
if (!HintManager.getHintSeen(this, HINT_ACCOUNT_ADD)) {
|
||||||
|
ShowcaseBuilder.getBuilder(this)
|
||||||
|
.setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.accounts_showcase_add)).setGravity(Gravity.TOP or Gravity.LEFT))
|
||||||
|
.playOn(fab)
|
||||||
|
HintManager.setHintSeen(this, HINT_ACCOUNT_ADD, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
onStatusChanged(SYNC_OBSERVER_TYPE_SETTINGS)
|
||||||
|
syncStatusObserver = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_SETTINGS, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
if (syncStatusObserver != null) {
|
||||||
|
ContentResolver.removeStatusChangeListener(syncStatusObserver)
|
||||||
|
syncStatusObserver = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(which: Int) {
|
||||||
|
if (syncStatusSnackbar != null) {
|
||||||
|
syncStatusSnackbar!!.dismiss()
|
||||||
|
syncStatusSnackbar = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContentResolver.getMasterSyncAutomatically()) {
|
||||||
|
syncStatusSnackbar = Snackbar.make(findViewById(R.id.coordinator), R.string.accounts_global_sync_disabled, Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.setAction(R.string.accounts_global_sync_enable) { ContentResolver.setMasterSyncAutomatically(true) }
|
||||||
|
syncStatusSnackbar!!.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
val drawer = findViewById<View>(R.id.drawer_layout) as DrawerLayout
|
||||||
|
if (drawer.isDrawerOpen(GravityCompat.START))
|
||||||
|
drawer.closeDrawer(GravityCompat.START)
|
||||||
|
else
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.nav_about -> startActivity(Intent(this, AboutActivity::class.java))
|
||||||
|
R.id.nav_app_settings -> startActivity(Intent(this, AppSettingsActivity::class.java))
|
||||||
|
R.id.nav_website -> startActivity(Intent(Intent.ACTION_VIEW, Constants.webUri))
|
||||||
|
R.id.nav_guide -> WebViewActivity.openUrl(this, Constants.helpUri)
|
||||||
|
R.id.nav_faq -> WebViewActivity.openUrl(this, Constants.faqUri)
|
||||||
|
R.id.nav_report_issue -> startActivity(Intent(Intent.ACTION_VIEW, Constants.reportIssueUri))
|
||||||
|
R.id.nav_contact -> startActivity(Intent(Intent.ACTION_VIEW, Constants.contactUri))
|
||||||
|
}
|
||||||
|
|
||||||
|
val drawer = findViewById<View>(R.id.drawer_layout) as DrawerLayout
|
||||||
|
drawer.closeDrawer(GravityCompat.START)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val HINT_ACCOUNT_ADD = "AddAccount"
|
||||||
|
}
|
||||||
|
}
|
@ -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,236 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.design.widget.Snackbar;
|
|
||||||
import android.support.v7.preference.EditTextPreference;
|
|
||||||
import android.support.v7.preference.ListPreference;
|
|
||||||
import android.support.v7.preference.Preference;
|
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
|
||||||
import android.support.v7.preference.SwitchPreferenceCompat;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.model.ServiceDB;
|
|
||||||
import com.etesync.syncadapter.model.Settings;
|
|
||||||
import com.etesync.syncadapter.utils.HintManager;
|
|
||||||
import com.etesync.syncadapter.utils.LanguageUtils;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class AppSettingsActivity extends BaseActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(android.R.id.content, new SettingsFragment())
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
|
||||||
ServiceDB.OpenHelper dbHelper;
|
|
||||||
Settings settings;
|
|
||||||
|
|
||||||
Preference
|
|
||||||
prefResetHints,
|
|
||||||
prefResetCertificates;
|
|
||||||
SwitchPreferenceCompat
|
|
||||||
prefOverrideProxy,
|
|
||||||
prefDistrustSystemCerts,
|
|
||||||
prefLogToExternalStorage;
|
|
||||||
|
|
||||||
EditTextPreference
|
|
||||||
prefProxyHost,
|
|
||||||
prefProxyPort;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
dbHelper = new ServiceDB.OpenHelper(getContext());
|
|
||||||
settings = new Settings(dbHelper.getReadableDatabase());
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
dbHelper.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreatePreferences(Bundle bundle, String s) {
|
|
||||||
addPreferencesFromResource(R.xml.settings_app);
|
|
||||||
|
|
||||||
prefResetHints = findPreference("reset_hints");
|
|
||||||
|
|
||||||
prefOverrideProxy = (SwitchPreferenceCompat)findPreference("override_proxy");
|
|
||||||
prefOverrideProxy.setChecked(settings.getBoolean(App.OVERRIDE_PROXY, false));
|
|
||||||
prefOverrideProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
settings.putBoolean(App.OVERRIDE_PROXY, (boolean)newValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
prefProxyHost = (EditTextPreference)findPreference("proxy_host");
|
|
||||||
String proxyHost = settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT);
|
|
||||||
prefProxyHost.setText(proxyHost);
|
|
||||||
prefProxyHost.setSummary(proxyHost);
|
|
||||||
prefProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
String host = (String)newValue;
|
|
||||||
try {
|
|
||||||
URI uri = new URI(null, host, null, null);
|
|
||||||
} catch(URISyntaxException e) {
|
|
||||||
Snackbar.make(getView(), e.getLocalizedMessage(), Snackbar.LENGTH_LONG).show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
settings.putString(App.OVERRIDE_PROXY_HOST, host);
|
|
||||||
prefProxyHost.setSummary(host);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
prefProxyPort = (EditTextPreference)findPreference("proxy_port");
|
|
||||||
String proxyPort = settings.getString(App.OVERRIDE_PROXY_PORT, String.valueOf(App.OVERRIDE_PROXY_PORT_DEFAULT));
|
|
||||||
prefProxyPort.setText(proxyPort);
|
|
||||||
prefProxyPort.setSummary(proxyPort);
|
|
||||||
prefProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
int port;
|
|
||||||
try {
|
|
||||||
port = Integer.parseInt((String)newValue);
|
|
||||||
} catch(NumberFormatException e) {
|
|
||||||
port = App.OVERRIDE_PROXY_PORT_DEFAULT;
|
|
||||||
}
|
|
||||||
settings.putInt(App.OVERRIDE_PROXY_PORT, port);
|
|
||||||
prefProxyPort.setText(String.valueOf(port));
|
|
||||||
prefProxyPort.setSummary(String.valueOf(port));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
prefDistrustSystemCerts = (SwitchPreferenceCompat) findPreference("distrust_system_certs");
|
|
||||||
prefDistrustSystemCerts.setChecked(settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false));
|
|
||||||
|
|
||||||
prefResetCertificates = findPreference("reset_certificates");
|
|
||||||
|
|
||||||
prefLogToExternalStorage = (SwitchPreferenceCompat) findPreference("log_to_external_storage");
|
|
||||||
prefLogToExternalStorage.setChecked(settings.getBoolean(App.LOG_TO_EXTERNAL_STORAGE, false));
|
|
||||||
|
|
||||||
initSelectLanguageList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initSelectLanguageList() {
|
|
||||||
ListPreference listPreference = (ListPreference) findPreference("select_language");
|
|
||||||
new LanguageTask(listPreference).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceTreeClick(Preference preference) {
|
|
||||||
if (preference == prefResetHints)
|
|
||||||
resetHints();
|
|
||||||
else if (preference == prefDistrustSystemCerts)
|
|
||||||
setDistrustSystemCerts(((SwitchPreferenceCompat)preference).isChecked());
|
|
||||||
else if (preference == prefResetCertificates)
|
|
||||||
resetCertificates();
|
|
||||||
else if (preference == prefLogToExternalStorage)
|
|
||||||
setExternalLogging(((SwitchPreferenceCompat)preference).isChecked());
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetHints() {
|
|
||||||
HintManager.resetHints(getContext());
|
|
||||||
Snackbar.make(getView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDistrustSystemCerts(boolean distrust) {
|
|
||||||
settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust);
|
|
||||||
|
|
||||||
// re-initialize certificate manager
|
|
||||||
App app = (App)getContext().getApplicationContext();
|
|
||||||
app.reinitCertManager();
|
|
||||||
|
|
||||||
// reinitialize certificate manager of :sync process
|
|
||||||
getContext().sendBroadcast(new Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetCertificates() {
|
|
||||||
((App)getContext().getApplicationContext()).getCertManager().resetCertificates();
|
|
||||||
Snackbar.make(getView(), getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setExternalLogging(boolean externalLogging) {
|
|
||||||
settings.putBoolean(App.LOG_TO_EXTERNAL_STORAGE, externalLogging);
|
|
||||||
|
|
||||||
// reinitialize logger of default process
|
|
||||||
App app = (App) getContext().getApplicationContext();
|
|
||||||
app.reinitLogger();
|
|
||||||
|
|
||||||
// reinitialize logger of :sync process
|
|
||||||
getContext().sendBroadcast(new Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LanguageTask extends AsyncTask<Void, Void, LanguageUtils.LocaleList> {
|
|
||||||
private ListPreference mListPreference;
|
|
||||||
|
|
||||||
LanguageTask(ListPreference listPreference) {
|
|
||||||
mListPreference = listPreference;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LanguageUtils.LocaleList doInBackground(Void... voids) {
|
|
||||||
return LanguageUtils.getAppLanguages(getContext());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(LanguageUtils.LocaleList locales) {
|
|
||||||
|
|
||||||
mListPreference.setEntries(locales.getDisplayNames());
|
|
||||||
mListPreference.setEntryValues(locales.getLocaleData());
|
|
||||||
|
|
||||||
mListPreference.setValue(settings.getString(App.FORCE_LANGUAGE,
|
|
||||||
App.DEFAULT_LANGUAGE));
|
|
||||||
mListPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
String value = newValue.toString();
|
|
||||||
if (value.equals(((ListPreference) preference).getValue())) return true;
|
|
||||||
|
|
||||||
LanguageUtils.setLanguage(getContext(), value);
|
|
||||||
|
|
||||||
settings.putString(App.FORCE_LANGUAGE, newValue.toString());
|
|
||||||
|
|
||||||
Intent intent = new Intent(getContext(), AccountsActivity.class);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
startActivity(intent);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.support.v7.preference.*
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.model.ServiceDB
|
||||||
|
import com.etesync.syncadapter.model.Settings
|
||||||
|
import com.etesync.syncadapter.utils.HintManager
|
||||||
|
import com.etesync.syncadapter.utils.LanguageUtils
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
|
class AppSettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(android.R.id.content, SettingsFragment())
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
internal lateinit var dbHelper: ServiceDB.OpenHelper
|
||||||
|
internal lateinit var settings: Settings
|
||||||
|
|
||||||
|
internal lateinit var prefResetHints: Preference
|
||||||
|
internal lateinit var prefResetCertificates: Preference
|
||||||
|
internal lateinit var prefOverrideProxy: SwitchPreferenceCompat
|
||||||
|
internal lateinit var prefDistrustSystemCerts: SwitchPreferenceCompat
|
||||||
|
internal lateinit var prefLogToExternalStorage: SwitchPreferenceCompat
|
||||||
|
|
||||||
|
internal lateinit var prefProxyHost: EditTextPreference
|
||||||
|
internal lateinit var prefProxyPort: EditTextPreference
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
dbHelper = ServiceDB.OpenHelper(context)
|
||||||
|
settings = Settings(dbHelper.readableDatabase)
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
dbHelper.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(bundle: Bundle, s: String) {
|
||||||
|
addPreferencesFromResource(R.xml.settings_app)
|
||||||
|
|
||||||
|
prefResetHints = findPreference("reset_hints")
|
||||||
|
|
||||||
|
prefOverrideProxy = findPreference("override_proxy") as SwitchPreferenceCompat
|
||||||
|
prefOverrideProxy.isChecked = settings.getBoolean(App.OVERRIDE_PROXY, false)
|
||||||
|
prefOverrideProxy.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
settings.putBoolean(App.OVERRIDE_PROXY, newValue as Boolean)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefProxyHost = findPreference("proxy_host") as EditTextPreference
|
||||||
|
val proxyHost = settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT)
|
||||||
|
prefProxyHost.text = proxyHost
|
||||||
|
prefProxyHost.summary = proxyHost
|
||||||
|
prefProxyHost.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
val host = newValue as String
|
||||||
|
try {
|
||||||
|
val uri = URI(null, host, null, null)
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
Snackbar.make(view!!, e.localizedMessage, Snackbar.LENGTH_LONG).show()
|
||||||
|
return@OnPreferenceChangeListener false
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.putString(App.OVERRIDE_PROXY_HOST, host)
|
||||||
|
prefProxyHost.summary = host
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefProxyPort = findPreference("proxy_port") as EditTextPreference
|
||||||
|
val proxyPort = settings.getString(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT.toString())
|
||||||
|
prefProxyPort.text = proxyPort
|
||||||
|
prefProxyPort.summary = proxyPort
|
||||||
|
prefProxyPort.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
var port: Int
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(newValue as String)
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
port = App.OVERRIDE_PROXY_PORT_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.putInt(App.OVERRIDE_PROXY_PORT, port)
|
||||||
|
prefProxyPort.text = port.toString()
|
||||||
|
prefProxyPort.summary = port.toString()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
prefDistrustSystemCerts = findPreference("distrust_system_certs") as SwitchPreferenceCompat
|
||||||
|
prefDistrustSystemCerts.isChecked = settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false)
|
||||||
|
|
||||||
|
prefResetCertificates = findPreference("reset_certificates")
|
||||||
|
|
||||||
|
prefLogToExternalStorage = findPreference("log_to_external_storage") as SwitchPreferenceCompat
|
||||||
|
prefLogToExternalStorage.isChecked = settings.getBoolean(App.LOG_TO_EXTERNAL_STORAGE, false)
|
||||||
|
|
||||||
|
initSelectLanguageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initSelectLanguageList() {
|
||||||
|
val listPreference = findPreference("select_language") as ListPreference
|
||||||
|
LanguageTask(listPreference).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
|
if (preference === prefResetHints)
|
||||||
|
resetHints()
|
||||||
|
else if (preference === prefDistrustSystemCerts)
|
||||||
|
setDistrustSystemCerts(preference.isChecked)
|
||||||
|
else if (preference === prefResetCertificates)
|
||||||
|
resetCertificates()
|
||||||
|
else if (preference === prefLogToExternalStorage)
|
||||||
|
setExternalLogging(preference.isChecked)
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetHints() {
|
||||||
|
HintManager.resetHints(context)
|
||||||
|
Snackbar.make(view!!, R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDistrustSystemCerts(distrust: Boolean) {
|
||||||
|
settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust)
|
||||||
|
|
||||||
|
// re-initialize certificate manager
|
||||||
|
val app = context!!.applicationContext as App
|
||||||
|
app.reinitCertManager()
|
||||||
|
|
||||||
|
// reinitialize certificate manager of :sync process
|
||||||
|
context!!.sendBroadcast(Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetCertificates() {
|
||||||
|
(context!!.applicationContext as App).certManager.resetCertificates()
|
||||||
|
Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setExternalLogging(externalLogging: Boolean) {
|
||||||
|
settings.putBoolean(App.LOG_TO_EXTERNAL_STORAGE, externalLogging)
|
||||||
|
|
||||||
|
// reinitialize logger of default process
|
||||||
|
val app = context!!.applicationContext as App
|
||||||
|
app.reinitLogger()
|
||||||
|
|
||||||
|
// reinitialize logger of :sync process
|
||||||
|
context!!.sendBroadcast(Intent(App.ReinitSettingsReceiver.ACTION_REINIT_SETTINGS))
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class LanguageTask internal constructor(private val mListPreference: ListPreference) : AsyncTask<Void, Void, LanguageUtils.LocaleList>() {
|
||||||
|
|
||||||
|
override fun doInBackground(vararg voids: Void): LanguageUtils.LocaleList {
|
||||||
|
return LanguageUtils.getAppLanguages(context!!)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(locales: LanguageUtils.LocaleList) {
|
||||||
|
|
||||||
|
mListPreference.entries = locales.displayNames
|
||||||
|
mListPreference.entryValues = locales.localeData
|
||||||
|
|
||||||
|
mListPreference.value = settings.getString(App.FORCE_LANGUAGE,
|
||||||
|
App.DEFAULT_LANGUAGE)
|
||||||
|
mListPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference, newValue ->
|
||||||
|
val value = newValue.toString()
|
||||||
|
if (value == (preference as ListPreference).value) return@OnPreferenceChangeListener true
|
||||||
|
|
||||||
|
LanguageUtils.setLanguage(context, value)
|
||||||
|
|
||||||
|
settings.putString(App.FORCE_LANGUAGE, newValue.toString())
|
||||||
|
|
||||||
|
val intent = Intent(context, AccountsActivity::class.java)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
startActivity(intent)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
34
app/src/main/java/com/etesync/syncadapter/ui/BaseActivity.kt
Normal file
34
app/src/main/java/com/etesync/syncadapter/ui/BaseActivity.kt
Normal file
@ -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,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.NavUtils;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.model.CollectionInfo;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import yuku.ambilwarna.AmbilWarnaDialog;
|
|
||||||
|
|
||||||
public class CreateCollectionActivity extends BaseActivity {
|
|
||||||
static final String EXTRA_ACCOUNT = "account",
|
|
||||||
EXTRA_COLLECTION_INFO = "collectionInfo";
|
|
||||||
|
|
||||||
protected Account account;
|
|
||||||
protected CollectionInfo info;
|
|
||||||
|
|
||||||
public static Intent newIntent(Context context, Account account, CollectionInfo info) {
|
|
||||||
Intent intent = new Intent(context, CreateCollectionActivity.class);
|
|
||||||
intent.putExtra(CreateCollectionActivity.EXTRA_ACCOUNT, account);
|
|
||||||
intent.putExtra(CreateCollectionActivity.EXTRA_COLLECTION_INFO, info);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
|
|
||||||
info = (CollectionInfo) getIntent().getExtras().getSerializable(EXTRA_COLLECTION_INFO);
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_create_collection);
|
|
||||||
|
|
||||||
final EditText displayName = (EditText) findViewById(R.id.display_name);
|
|
||||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
setTitle(R.string.create_calendar);
|
|
||||||
displayName.setHint(R.string.create_calendar_display_name_hint);
|
|
||||||
|
|
||||||
final View colorSquare = findViewById(R.id.color);
|
|
||||||
colorSquare.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
new AmbilWarnaDialog(CreateCollectionActivity.this, ((ColorDrawable) colorSquare.getBackground()).getColor(), true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
|
|
||||||
@Override
|
|
||||||
public void onCancel(AmbilWarnaDialog dialog) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onOk(AmbilWarnaDialog dialog, int color) {
|
|
||||||
colorSquare.setBackgroundColor(color);
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setTitle(R.string.create_addressbook);
|
|
||||||
displayName.setHint(R.string.create_addressbook_display_name_hint);
|
|
||||||
|
|
||||||
final View colorGroup = findViewById(R.id.color_group);
|
|
||||||
colorGroup.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.activity_create_collection, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == android.R.id.home) {
|
|
||||||
Intent intent = new Intent(this, AccountActivity.class);
|
|
||||||
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account);
|
|
||||||
NavUtils.navigateUpTo(this, intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreateCollection(MenuItem item) {
|
|
||||||
boolean ok = true;
|
|
||||||
if (info == null) {
|
|
||||||
info = new CollectionInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
EditText edit = (EditText) findViewById(R.id.display_name);
|
|
||||||
info.displayName = edit.getText().toString();
|
|
||||||
if (TextUtils.isEmpty(info.displayName)) {
|
|
||||||
edit.setError(getString(R.string.create_collection_display_name_required));
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
edit = (EditText) findViewById(R.id.description);
|
|
||||||
info.description = StringUtils.trimToNull(edit.getText().toString());
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
View view = findViewById(R.id.color);
|
|
||||||
info.color = ((ColorDrawable) view.getBackground()).getColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
info.selected = true;
|
|
||||||
|
|
||||||
CreateCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.NavUtils
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
|
||||||
|
import yuku.ambilwarna.AmbilWarnaDialog
|
||||||
|
|
||||||
|
open class CreateCollectionActivity : BaseActivity() {
|
||||||
|
|
||||||
|
protected lateinit var account: Account
|
||||||
|
protected lateinit var info: CollectionInfo
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)
|
||||||
|
info = intent.extras!!.getSerializable(EXTRA_COLLECTION_INFO) as CollectionInfo
|
||||||
|
|
||||||
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_create_collection)
|
||||||
|
|
||||||
|
val displayName = findViewById<View>(R.id.display_name) as EditText
|
||||||
|
if (info!!.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
setTitle(R.string.create_calendar)
|
||||||
|
displayName.setHint(R.string.create_calendar_display_name_hint)
|
||||||
|
|
||||||
|
val colorSquare = findViewById<View>(R.id.color)
|
||||||
|
colorSquare.setOnClickListener {
|
||||||
|
AmbilWarnaDialog(this@CreateCollectionActivity, (colorSquare.background as ColorDrawable).color, true, object : AmbilWarnaDialog.OnAmbilWarnaListener {
|
||||||
|
override fun onCancel(dialog: AmbilWarnaDialog) {}
|
||||||
|
|
||||||
|
override fun onOk(dialog: AmbilWarnaDialog, color: Int) {
|
||||||
|
colorSquare.setBackgroundColor(color)
|
||||||
|
}
|
||||||
|
}).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTitle(R.string.create_addressbook)
|
||||||
|
displayName.setHint(R.string.create_addressbook_display_name_hint)
|
||||||
|
|
||||||
|
val colorGroup = findViewById<View>(R.id.color_group)
|
||||||
|
colorGroup.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.activity_create_collection, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == android.R.id.home) {
|
||||||
|
val intent = Intent(this, AccountActivity::class.java)
|
||||||
|
intent.putExtra(AccountActivity.EXTRA_ACCOUNT, account)
|
||||||
|
NavUtils.navigateUpTo(this, intent)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCreateCollection(item: MenuItem) {
|
||||||
|
var ok = true
|
||||||
|
if (info == null) {
|
||||||
|
info = CollectionInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
var edit = findViewById<View>(R.id.display_name) as EditText
|
||||||
|
info!!.displayName = edit.text.toString()
|
||||||
|
if (TextUtils.isEmpty(info!!.displayName)) {
|
||||||
|
edit.error = getString(R.string.create_collection_display_name_required)
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
|
edit = findViewById<View>(R.id.description) as EditText
|
||||||
|
info!!.description = StringUtils.trimToNull(edit.text.toString())
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
if (info!!.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
val view = findViewById<View>(R.id.color)
|
||||||
|
info!!.color = (view.background as ColorDrawable).color
|
||||||
|
}
|
||||||
|
|
||||||
|
info!!.selected = true
|
||||||
|
|
||||||
|
CreateCollectionFragment.newInstance(account, info).show(supportFragmentManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal val EXTRA_ACCOUNT = "account"
|
||||||
|
internal val EXTRA_COLLECTION_INFO = "collectionInfo"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, account: Account, info: CollectionInfo): Intent {
|
||||||
|
val intent = Intent(context, CreateCollectionActivity::class.java)
|
||||||
|
intent.putExtra(CreateCollectionActivity.EXTRA_ACCOUNT, account)
|
||||||
|
intent.putExtra(CreateCollectionActivity.EXTRA_COLLECTION_INFO, info)
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,183 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
|
|
||||||
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.Crypto;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions;
|
|
||||||
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 com.etesync.syncadapter.model.ServiceEntity;
|
|
||||||
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
|
|
||||||
public class CreateCollectionFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Exception> {
|
|
||||||
private static final String
|
|
||||||
ARG_ACCOUNT = "account",
|
|
||||||
ARG_COLLECTION_INFO = "collectionInfo";
|
|
||||||
|
|
||||||
protected Account account;
|
|
||||||
protected CollectionInfo info;
|
|
||||||
|
|
||||||
public static CreateCollectionFragment newInstance(Account account, CollectionInfo info) {
|
|
||||||
CreateCollectionFragment frag = new CreateCollectionFragment();
|
|
||||||
Bundle args = new Bundle(2);
|
|
||||||
args.putParcelable(ARG_ACCOUNT, account);
|
|
||||||
args.putSerializable(ARG_COLLECTION_INFO, info);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
account = getArguments().getParcelable(ARG_ACCOUNT);
|
|
||||||
info = (CollectionInfo) getArguments().getSerializable(ARG_COLLECTION_INFO);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
ProgressDialog progress = new ProgressDialog(getContext());
|
|
||||||
progress.setTitle(R.string.create_collection_creating);
|
|
||||||
progress.setMessage(getString(R.string.please_wait));
|
|
||||||
progress.setIndeterminate(true);
|
|
||||||
progress.setCanceledOnTouchOutside(false);
|
|
||||||
setCancelable(false);
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Exception> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new CreateCollectionLoader(getContext(), account, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Exception> loader, Exception exception) {
|
|
||||||
dismissAllowingStateLoss();
|
|
||||||
|
|
||||||
Activity parent = getActivity();
|
|
||||||
if (parent != null) {
|
|
||||||
if (exception != null)
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
|
||||||
.commitAllowingStateLoss();
|
|
||||||
else
|
|
||||||
parent.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Exception> loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected static class CreateCollectionLoader extends AsyncTaskLoader<Exception> {
|
|
||||||
final Account account;
|
|
||||||
final CollectionInfo info;
|
|
||||||
|
|
||||||
public CreateCollectionLoader(Context context, Account account, CollectionInfo info) {
|
|
||||||
super(context);
|
|
||||||
this.account = account;
|
|
||||||
this.info = info;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Exception loadInBackground() {
|
|
||||||
try {
|
|
||||||
String authority = null;
|
|
||||||
|
|
||||||
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
|
|
||||||
|
|
||||||
// 1. find service ID
|
|
||||||
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
|
||||||
authority = App.getAddressBooksAuthority();
|
|
||||||
} else if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
authority = CalendarContract.AUTHORITY;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Collection must be an address book or calendar");
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceEntity serviceEntity = JournalModel.Service.fetch(data, account.name, info.type);
|
|
||||||
|
|
||||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
|
||||||
HttpUrl principal = HttpUrl.get(settings.getUri());
|
|
||||||
|
|
||||||
JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), settings), principal);
|
|
||||||
if (info.uid == null) {
|
|
||||||
info.uid = JournalManager.Journal.genUid();
|
|
||||||
Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid);
|
|
||||||
JournalManager.Journal journal = new JournalManager.Journal(crypto, info.toJson(), info.uid);
|
|
||||||
journalManager.create(journal);
|
|
||||||
} else {
|
|
||||||
Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid);
|
|
||||||
JournalManager.Journal journal = new JournalManager.Journal(crypto, info.toJson(), info.uid);
|
|
||||||
journalManager.update(journal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. add collection to service
|
|
||||||
info.serviceID = serviceEntity.getId();
|
|
||||||
JournalEntity journalEntity = JournalEntity.fetchOrCreate(data, info);
|
|
||||||
data.upsert(journalEntity);
|
|
||||||
|
|
||||||
|
|
||||||
requestSync(authority);
|
|
||||||
} catch (IllegalStateException | Exceptions.HttpException e) {
|
|
||||||
return e;
|
|
||||||
} catch (InvalidAccountException e) {
|
|
||||||
return e;
|
|
||||||
} catch (Exceptions.IntegrityException|Exceptions.GenericCryptoException e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestSync(String authority) {
|
|
||||||
Bundle extras = new Bundle();
|
|
||||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); // manual sync
|
|
||||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); // run immediately (don't queue)
|
|
||||||
ContentResolver.requestSync(account, authority, extras);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v4.app.LoaderManager
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import com.etesync.syncadapter.*
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto
|
||||||
|
import com.etesync.syncadapter.journalmanager.Exceptions
|
||||||
|
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 okhttp3.HttpUrl
|
||||||
|
|
||||||
|
class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Exception> {
|
||||||
|
|
||||||
|
protected lateinit var account: Account
|
||||||
|
protected lateinit var info: CollectionInfo
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
account = arguments!!.getParcelable(ARG_ACCOUNT)
|
||||||
|
info = arguments!!.getSerializable(ARG_COLLECTION_INFO) as CollectionInfo
|
||||||
|
|
||||||
|
loaderManager.initLoader(0, null, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val progress = ProgressDialog(context)
|
||||||
|
progress.setTitle(R.string.create_collection_creating)
|
||||||
|
progress.setMessage(getString(R.string.please_wait))
|
||||||
|
progress.isIndeterminate = true
|
||||||
|
progress.setCanceledOnTouchOutside(false)
|
||||||
|
isCancelable = false
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Exception> {
|
||||||
|
return CreateCollectionLoader(context!!, account, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<Exception>, exception: Exception?) {
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
|
||||||
|
val parent = activity
|
||||||
|
if (parent != null) {
|
||||||
|
if (exception != null)
|
||||||
|
fragmentManager!!.beginTransaction()
|
||||||
|
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
else
|
||||||
|
parent.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<Exception>) {}
|
||||||
|
|
||||||
|
|
||||||
|
protected class CreateCollectionLoader(context: Context, internal val account: Account, internal val info: CollectionInfo) : AsyncTaskLoader<Exception>(context) {
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): Exception? {
|
||||||
|
try {
|
||||||
|
var authority: String
|
||||||
|
|
||||||
|
val data = (context.applicationContext as App).data
|
||||||
|
|
||||||
|
// 1. find service ID
|
||||||
|
if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||||
|
authority = App.getAddressBooksAuthority()
|
||||||
|
} else if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
authority = CalendarContract.AUTHORITY
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Collection must be an address book or calendar")
|
||||||
|
}
|
||||||
|
|
||||||
|
val serviceEntity = JournalModel.Service.fetch(data, account.name, info.type)
|
||||||
|
|
||||||
|
val settings = AccountSettings(context, account)
|
||||||
|
val principal = HttpUrl.get(settings.uri!!)
|
||||||
|
|
||||||
|
val journalManager = JournalManager(HttpClient.create(context, settings), principal!!)
|
||||||
|
if (info.uid == null) {
|
||||||
|
info.uid = JournalManager.Journal.genUid()
|
||||||
|
val crypto = Crypto.CryptoManager(info.version, settings.password(), info.uid)
|
||||||
|
val journal = JournalManager.Journal(crypto, info.toJson(), info.uid)
|
||||||
|
journalManager.create(journal)
|
||||||
|
} else {
|
||||||
|
val crypto = Crypto.CryptoManager(info.version, settings.password(), info.uid)
|
||||||
|
val journal = JournalManager.Journal(crypto, info.toJson(), info.uid)
|
||||||
|
journalManager.update(journal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. add collection to service
|
||||||
|
info.serviceID = serviceEntity.id
|
||||||
|
val journalEntity = JournalEntity.fetchOrCreate(data, info)
|
||||||
|
data.upsert(journalEntity)
|
||||||
|
|
||||||
|
|
||||||
|
requestSync(authority)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
return e
|
||||||
|
} catch (e: Exceptions.HttpException) {
|
||||||
|
return e
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
return e
|
||||||
|
} catch (e: Exceptions.IntegrityException) {
|
||||||
|
return e
|
||||||
|
} catch (e: Exceptions.GenericCryptoException) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestSync(authority: String) {
|
||||||
|
val extras = Bundle()
|
||||||
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) // manual sync
|
||||||
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) // run immediately (don't queue)
|
||||||
|
ContentResolver.requestSync(account, authority, extras)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ARG_ACCOUNT = "account"
|
||||||
|
private val ARG_COLLECTION_INFO = "collectionInfo"
|
||||||
|
|
||||||
|
fun newInstance(account: Account, info: CollectionInfo): CreateCollectionFragment {
|
||||||
|
val frag = CreateCollectionFragment()
|
||||||
|
val args = Bundle(2)
|
||||||
|
args.putParcelable(ARG_ACCOUNT, account)
|
||||||
|
args.putSerializable(ARG_COLLECTION_INFO, info)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,278 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.LoaderManager;
|
|
||||||
import android.content.AsyncTaskLoader;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.Loader;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v4.content.FileProvider;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.AccountSettings;
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.BuildConfig;
|
|
||||||
import com.etesync.syncadapter.InvalidAccountException;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions.HttpException;
|
|
||||||
import com.etesync.syncadapter.model.EntryEntity;
|
|
||||||
import com.etesync.syncadapter.model.JournalEntity;
|
|
||||||
import com.etesync.syncadapter.model.ServiceDB;
|
|
||||||
import com.etesync.syncadapter.model.ServiceEntity;
|
|
||||||
import com.etesync.syncadapter.resource.LocalAddressBook;
|
|
||||||
|
|
||||||
import org.acra.ACRA;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.apache.commons.lang3.text.WordUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import at.bitfire.vcard4android.ContactsStorageException;
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
|
|
||||||
import static com.etesync.syncadapter.Constants.KEY_ACCOUNT;
|
|
||||||
|
|
||||||
public class DebugInfoActivity extends BaseActivity implements LoaderManager.LoaderCallbacks<String> {
|
|
||||||
public static final String
|
|
||||||
KEY_THROWABLE = "throwable",
|
|
||||||
KEY_LOGS = "logs",
|
|
||||||
KEY_AUTHORITY = "authority",
|
|
||||||
KEY_PHASE = "phase";
|
|
||||||
|
|
||||||
TextView tvReport;
|
|
||||||
String report;
|
|
||||||
|
|
||||||
File reportFile;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_debug_info);
|
|
||||||
tvReport = (TextView)findViewById(R.id.text_report);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, getIntent().getExtras(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.activity_debug_info, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void onShare(MenuItem item) {
|
|
||||||
ACRA.getErrorReporter().putCustomData("debug_info", report);
|
|
||||||
ACRA.getErrorReporter().handleSilentException(null);
|
|
||||||
ACRA.getErrorReporter().removeCustomData("debug_info");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<String> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new ReportLoader(this, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<String> loader, String data) {
|
|
||||||
if (data != null)
|
|
||||||
tvReport.setText(report = data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<String> loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static class ReportLoader extends AsyncTaskLoader<String> {
|
|
||||||
|
|
||||||
final Bundle extras;
|
|
||||||
|
|
||||||
public ReportLoader(Context context, Bundle extras) {
|
|
||||||
super(context);
|
|
||||||
this.extras = extras;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
public String loadInBackground() {
|
|
||||||
Throwable throwable = null;
|
|
||||||
String logs = null,
|
|
||||||
authority = null;
|
|
||||||
Account account = null;
|
|
||||||
String phase = null;
|
|
||||||
|
|
||||||
if (extras != null) {
|
|
||||||
throwable = (Throwable)extras.getSerializable(KEY_THROWABLE);
|
|
||||||
logs = extras.getString(KEY_LOGS);
|
|
||||||
account = extras.getParcelable(KEY_ACCOUNT);
|
|
||||||
authority = extras.getString(KEY_AUTHORITY);
|
|
||||||
phase = extras.getString(KEY_PHASE, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder report = new StringBuilder("--- BEGIN DEBUG INFO ---\n");
|
|
||||||
|
|
||||||
// begin with most specific information
|
|
||||||
|
|
||||||
if (phase != null)
|
|
||||||
report.append("SYNCHRONIZATION INFO\nSynchronization phase: ").append(phase).append("\n");
|
|
||||||
if (account != null)
|
|
||||||
report.append("Account name: ").append(account.name).append("\n");
|
|
||||||
if (authority != null)
|
|
||||||
report.append("Authority: ").append(authority).append("\n");
|
|
||||||
|
|
||||||
if (throwable instanceof HttpException) {
|
|
||||||
HttpException http = (HttpException)throwable;
|
|
||||||
if (http.getRequest() != null)
|
|
||||||
report.append("\nHTTP REQUEST:\n").append(http.getRequest()).append("\n\n");
|
|
||||||
if (http.getRequest() != null)
|
|
||||||
report.append("HTTP RESPONSE:\n").append(http.getRequest()).append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (throwable != null)
|
|
||||||
report .append("\nEXCEPTION:\n")
|
|
||||||
.append(ExceptionUtils.getStackTrace(throwable));
|
|
||||||
|
|
||||||
if (logs != null)
|
|
||||||
report.append("\nLOGS:\n").append(logs).append("\n");
|
|
||||||
|
|
||||||
final Context context = getContext();
|
|
||||||
|
|
||||||
try {
|
|
||||||
PackageManager pm = context.getPackageManager();
|
|
||||||
String installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID);
|
|
||||||
if (TextUtils.isEmpty(installedFrom))
|
|
||||||
installedFrom = "APK (directly)";
|
|
||||||
report.append("\nSOFTWARE INFORMATION\n" +
|
|
||||||
"EteSync version: ").append(BuildConfig.VERSION_NAME).append(" (").append(BuildConfig.VERSION_CODE).append(") ").append(new Date(BuildConfig.buildTime)).append("\n")
|
|
||||||
.append("Installed from: ").append(installedFrom).append("\n");
|
|
||||||
} catch(Exception ex) {
|
|
||||||
App.log.log(Level.SEVERE, "Couldn't get software information", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
report.append("CONFIGURATION\n");
|
|
||||||
// power saving
|
|
||||||
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
if (powerManager != null && Build.VERSION.SDK_INT >= 23)
|
|
||||||
report.append("Power saving disabled: ")
|
|
||||||
.append(powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID) ? "yes" : "no")
|
|
||||||
.append("\n");
|
|
||||||
// permissions
|
|
||||||
for (String permission : new String[] { Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS,
|
|
||||||
Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR,
|
|
||||||
PermissionsActivity.PERMISSION_READ_TASKS, PermissionsActivity.PERMISSION_WRITE_TASKS })
|
|
||||||
report.append(permission).append(" permission: ")
|
|
||||||
.append(ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")
|
|
||||||
.append("\n");
|
|
||||||
// system-wide sync settings
|
|
||||||
report.append("System-wide synchronization: ")
|
|
||||||
.append(ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually")
|
|
||||||
.append("\n");
|
|
||||||
// main accounts
|
|
||||||
AccountManager accountManager = AccountManager.get(context);
|
|
||||||
for (Account acct : accountManager.getAccountsByType(context.getString(R.string.account_type)))
|
|
||||||
try {
|
|
||||||
AccountSettings settings = new AccountSettings(context, acct);
|
|
||||||
report.append("Account: ").append(acct.name).append("\n" +
|
|
||||||
" Address book sync. interval: ").append(syncStatus(settings, App.getAddressBooksAuthority())).append("\n" +
|
|
||||||
" Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" +
|
|
||||||
" OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" +
|
|
||||||
" WiFi only: ").append(settings.getSyncWifiOnly());
|
|
||||||
if (settings.getSyncWifiOnlySSID() != null)
|
|
||||||
report.append(", SSID: ").append(settings.getSyncWifiOnlySSID());
|
|
||||||
report.append("\n [CardDAV] Contact group method: ").append(settings.getGroupMethod())
|
|
||||||
.append("\n Manage calendar colors: ").append(settings.getManageCalendarColors())
|
|
||||||
.append("\n");
|
|
||||||
} catch(InvalidAccountException e) {
|
|
||||||
report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n");
|
|
||||||
}
|
|
||||||
// address book accounts
|
|
||||||
for (Account acct : accountManager.getAccountsByType(App.getAddressBookAccountType()))
|
|
||||||
try {
|
|
||||||
LocalAddressBook addressBook = new LocalAddressBook(context, acct, null);
|
|
||||||
report.append("Address book account: ").append(acct.name).append("\n" +
|
|
||||||
" Main account: ").append(addressBook.getMainAccount()).append("\n" +
|
|
||||||
" URL: ").append(addressBook.getURL()).append("\n" +
|
|
||||||
" Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n");
|
|
||||||
} catch(ContactsStorageException e) {
|
|
||||||
report.append(acct).append(" is invalid: ").append(e.getMessage()).append("\n");
|
|
||||||
}
|
|
||||||
report.append("\n");
|
|
||||||
|
|
||||||
report.append("SQLITE DUMP\n");
|
|
||||||
ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
|
|
||||||
dbHelper.dump(report);
|
|
||||||
dbHelper.close();
|
|
||||||
report.append("\n");
|
|
||||||
|
|
||||||
report.append("SERVICES DUMP\n");
|
|
||||||
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
|
|
||||||
for (ServiceEntity serviceEntity : data.select(ServiceEntity.class).get()) {
|
|
||||||
report.append(serviceEntity.toString() + "\n");
|
|
||||||
}
|
|
||||||
report.append("\n");
|
|
||||||
|
|
||||||
report.append("JOURNALS DUMP\n");
|
|
||||||
List<JournalEntity> journals = data.select(JournalEntity.class).where(JournalEntity.DELETED.eq(false)).get().toList();
|
|
||||||
for (JournalEntity journal : journals) {
|
|
||||||
report.append(journal.toString() + "\n");
|
|
||||||
int entryCount = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journal)).get().value();
|
|
||||||
report.append("\tEntries: " + String.valueOf(entryCount) + "\n\n");
|
|
||||||
}
|
|
||||||
report.append("\n");
|
|
||||||
|
|
||||||
try {
|
|
||||||
report.append(
|
|
||||||
"SYSTEM INFORMATION\n" +
|
|
||||||
"Android version: ").append(Build.VERSION.RELEASE).append(" (").append(Build.DISPLAY).append(")\n" +
|
|
||||||
"Device: ").append(WordUtils.capitalize(Build.MANUFACTURER)).append(" ").append(Build.MODEL).append(" (").append(Build.DEVICE).append(")\n\n"
|
|
||||||
);
|
|
||||||
} catch(Exception ex) {
|
|
||||||
App.log.log(Level.SEVERE, "Couldn't get system details", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
report.append("--- END DEBUG INFO ---\n");
|
|
||||||
return report.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String syncStatus(AccountSettings settings, String authority) {
|
|
||||||
Long interval = settings.getSyncInterval(authority);
|
|
||||||
return interval != null ?
|
|
||||||
(interval == AccountSettings.SYNC_INTERVAL_MANUALLY ? "manually" : interval/60 + " min") :
|
|
||||||
"—";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.accounts.AccountManager
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.LoaderManager
|
||||||
|
import android.content.AsyncTaskLoader
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Loader
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.TextView
|
||||||
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
|
import com.etesync.syncadapter.*
|
||||||
|
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||||
|
import com.etesync.syncadapter.journalmanager.Exceptions.HttpException
|
||||||
|
import com.etesync.syncadapter.model.EntryEntity
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
import com.etesync.syncadapter.model.ServiceDB
|
||||||
|
import com.etesync.syncadapter.model.ServiceEntity
|
||||||
|
import com.etesync.syncadapter.resource.LocalAddressBook
|
||||||
|
import org.acra.ACRA
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
|
import org.apache.commons.lang3.text.WordUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String> {
|
||||||
|
|
||||||
|
internal lateinit var tvReport: TextView
|
||||||
|
internal lateinit var report: String
|
||||||
|
|
||||||
|
internal var reportFile: File? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_debug_info)
|
||||||
|
tvReport = findViewById(R.id.text_report)
|
||||||
|
|
||||||
|
loaderManager.initLoader(0, intent.extras, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.activity_debug_info, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun onShare(item: MenuItem) {
|
||||||
|
ACRA.getErrorReporter().putCustomData("debug_info", report)
|
||||||
|
ACRA.getErrorReporter().handleSilentException(null)
|
||||||
|
ACRA.getErrorReporter().removeCustomData("debug_info")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle): Loader<String> {
|
||||||
|
return ReportLoader(this, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<String>, data: String?) {
|
||||||
|
if (data != null) {
|
||||||
|
report = data
|
||||||
|
tvReport.setText(report)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<String>) {}
|
||||||
|
|
||||||
|
|
||||||
|
internal class ReportLoader(context: Context, val extras: Bundle?) : AsyncTaskLoader<String>(context) {
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override fun loadInBackground(): String {
|
||||||
|
var throwable: Throwable? = null
|
||||||
|
var logs: String? = null
|
||||||
|
var authority: String? = null
|
||||||
|
var account: Account? = null
|
||||||
|
var phase: String? = null
|
||||||
|
|
||||||
|
if (extras != null) {
|
||||||
|
throwable = extras.getSerializable(KEY_THROWABLE) as Throwable
|
||||||
|
logs = extras.getString(KEY_LOGS)
|
||||||
|
account = extras.getParcelable(KEY_ACCOUNT)
|
||||||
|
authority = extras.getString(KEY_AUTHORITY)
|
||||||
|
phase = extras.getString(KEY_PHASE, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val report = StringBuilder("--- BEGIN DEBUG INFO ---\n")
|
||||||
|
|
||||||
|
// begin with most specific information
|
||||||
|
|
||||||
|
if (phase != null)
|
||||||
|
report.append("SYNCHRONIZATION INFO\nSynchronization phase: ").append(phase).append("\n")
|
||||||
|
if (account != null)
|
||||||
|
report.append("Account name: ").append(account.name).append("\n")
|
||||||
|
if (authority != null)
|
||||||
|
report.append("Authority: ").append(authority).append("\n")
|
||||||
|
|
||||||
|
if (throwable is HttpException) {
|
||||||
|
val http = throwable as HttpException?
|
||||||
|
if (http!!.request != null)
|
||||||
|
report.append("\nHTTP REQUEST:\n").append(http.request).append("\n\n")
|
||||||
|
if (http.request != null)
|
||||||
|
report.append("HTTP RESPONSE:\n").append(http.request).append("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwable != null)
|
||||||
|
report.append("\nEXCEPTION:\n")
|
||||||
|
.append(ExceptionUtils.getStackTrace(throwable))
|
||||||
|
|
||||||
|
if (logs != null)
|
||||||
|
report.append("\nLOGS:\n").append(logs).append("\n")
|
||||||
|
|
||||||
|
val context = context
|
||||||
|
|
||||||
|
try {
|
||||||
|
val pm = context.packageManager
|
||||||
|
var installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID)
|
||||||
|
if (TextUtils.isEmpty(installedFrom))
|
||||||
|
installedFrom = "APK (directly)"
|
||||||
|
report.append("\nSOFTWARE INFORMATION\n" + "EteSync version: ").append(BuildConfig.VERSION_NAME).append(" (").append(BuildConfig.VERSION_CODE).append(") ").append(Date(BuildConfig.buildTime)).append("\n")
|
||||||
|
.append("Installed from: ").append(installedFrom).append("\n")
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't get software information", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
report.append("CONFIGURATION\n")
|
||||||
|
// power saving
|
||||||
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
if (powerManager != null && Build.VERSION.SDK_INT >= 23)
|
||||||
|
report.append("Power saving disabled: ")
|
||||||
|
.append(if (powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) "yes" else "no")
|
||||||
|
.append("\n")
|
||||||
|
// permissions
|
||||||
|
for (permission in arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, PermissionsActivity.PERMISSION_READ_TASKS, PermissionsActivity.PERMISSION_WRITE_TASKS))
|
||||||
|
report.append(permission).append(" permission: ")
|
||||||
|
.append(if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) "granted" else "denied")
|
||||||
|
.append("\n")
|
||||||
|
// system-wide sync settings
|
||||||
|
report.append("System-wide synchronization: ")
|
||||||
|
.append(if (ContentResolver.getMasterSyncAutomatically()) "automatically" else "manually")
|
||||||
|
.append("\n")
|
||||||
|
// main accounts
|
||||||
|
val accountManager = AccountManager.get(context)
|
||||||
|
for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
|
||||||
|
try {
|
||||||
|
val settings = AccountSettings(context, acct)
|
||||||
|
report.append("Account: ").append(acct.name).append("\n" + " Address book sync. interval: ").append(syncStatus(settings, App.getAddressBooksAuthority())).append("\n" + " Calendar sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + " OpenTasks sync. interval: ").append(syncStatus(settings, "org.dmfs.tasks")).append("\n" + " WiFi only: ").append(settings.syncWifiOnly)
|
||||||
|
if (settings.syncWifiOnlySSID != null)
|
||||||
|
report.append(", SSID: ").append(settings.syncWifiOnlySSID)
|
||||||
|
report.append("\n [CardDAV] Contact group method: ").append(settings.groupMethod)
|
||||||
|
.append("\n Manage calendar colors: ").append(settings.manageCalendarColors)
|
||||||
|
.append("\n")
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// address book accounts
|
||||||
|
for (acct in accountManager.getAccountsByType(App.getAddressBookAccountType()))
|
||||||
|
try {
|
||||||
|
val addressBook = LocalAddressBook(context, acct, null)
|
||||||
|
report.append("Address book account: ").append(acct.name).append("\n" + " Main account: ").append(addressBook.mainAccount).append("\n" + " URL: ").append(addressBook.url).append("\n" + " Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n")
|
||||||
|
} catch (e: ContactsStorageException) {
|
||||||
|
report.append(acct).append(" is invalid: ").append(e.message).append("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
report.append("\n")
|
||||||
|
|
||||||
|
report.append("SQLITE DUMP\n")
|
||||||
|
val dbHelper = ServiceDB.OpenHelper(context)
|
||||||
|
dbHelper.dump(report)
|
||||||
|
dbHelper.close()
|
||||||
|
report.append("\n")
|
||||||
|
|
||||||
|
report.append("SERVICES DUMP\n")
|
||||||
|
val data = (getContext().applicationContext as App).data
|
||||||
|
for (serviceEntity in data.select(ServiceEntity::class.java).get()) {
|
||||||
|
report.append(serviceEntity.toString() + "\n")
|
||||||
|
}
|
||||||
|
report.append("\n")
|
||||||
|
|
||||||
|
report.append("JOURNALS DUMP\n")
|
||||||
|
val journals = data.select(JournalEntity::class.java).where(JournalEntity.DELETED.eq(false)).get().toList()
|
||||||
|
for (journal in journals) {
|
||||||
|
report.append(journal.toString() + "\n")
|
||||||
|
val entryCount = data.count(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journal)).get().value()
|
||||||
|
report.append("\tEntries: " + entryCount.toString() + "\n\n")
|
||||||
|
}
|
||||||
|
report.append("\n")
|
||||||
|
|
||||||
|
try {
|
||||||
|
report.append(
|
||||||
|
"SYSTEM INFORMATION\n" + "Android version: ").append(Build.VERSION.RELEASE).append(" (").append(Build.DISPLAY).append(")\n" + "Device: ").append(WordUtils.capitalize(Build.MANUFACTURER)).append(" ").append(Build.MODEL).append(" (").append(Build.DEVICE).append(")\n\n"
|
||||||
|
)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't get system details", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
report.append("--- END DEBUG INFO ---\n")
|
||||||
|
return report.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun syncStatus(settings: AccountSettings, authority: String): String {
|
||||||
|
val interval = settings.getSyncInterval(authority)
|
||||||
|
return if (interval != null)
|
||||||
|
if (interval == AccountSettings.SYNC_INTERVAL_MANUALLY) "manually" else (interval / 60).toString() + " min"
|
||||||
|
else
|
||||||
|
"—"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val KEY_THROWABLE = "throwable"
|
||||||
|
val KEY_LOGS = "logs"
|
||||||
|
val KEY_AUTHORITY = "authority"
|
||||||
|
val KEY_PHASE = "phase"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
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.Crypto;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions;
|
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager;
|
|
||||||
import com.etesync.syncadapter.model.CollectionInfo;
|
|
||||||
import com.etesync.syncadapter.model.JournalEntity;
|
|
||||||
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
|
|
||||||
public class DeleteCollectionFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Exception> {
|
|
||||||
protected static final String
|
|
||||||
ARG_ACCOUNT = "account",
|
|
||||||
ARG_COLLECTION_INFO = "collectionInfo";
|
|
||||||
|
|
||||||
protected Account account;
|
|
||||||
protected CollectionInfo collectionInfo;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, getArguments(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
ProgressDialog progress = new ProgressDialog(getContext());
|
|
||||||
progress.setTitle(R.string.delete_collection_deleting_collection);
|
|
||||||
progress.setMessage(getString(R.string.please_wait));
|
|
||||||
progress.setIndeterminate(true);
|
|
||||||
progress.setCanceledOnTouchOutside(false);
|
|
||||||
setCancelable(false);
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Exception> onCreateLoader(int id, Bundle args) {
|
|
||||||
account = args.getParcelable(ARG_ACCOUNT);
|
|
||||||
collectionInfo = (CollectionInfo) args.getSerializable(ARG_COLLECTION_INFO);
|
|
||||||
return new DeleteCollectionLoader(getContext(), account, collectionInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader loader, Exception exception) {
|
|
||||||
dismissAllowingStateLoss();
|
|
||||||
|
|
||||||
if (exception != null)
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
|
||||||
.commitAllowingStateLoss();
|
|
||||||
else {
|
|
||||||
Activity activity = getActivity();
|
|
||||||
if (activity instanceof Refreshable)
|
|
||||||
((Refreshable) activity).refresh();
|
|
||||||
else if (activity instanceof EditCollectionActivity)
|
|
||||||
activity.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected static class DeleteCollectionLoader extends AsyncTaskLoader<Exception> {
|
|
||||||
final Account account;
|
|
||||||
final CollectionInfo collectionInfo;
|
|
||||||
|
|
||||||
public DeleteCollectionLoader(Context context, Account account, CollectionInfo collectionInfo) {
|
|
||||||
super(context);
|
|
||||||
this.account = account;
|
|
||||||
this.collectionInfo = collectionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Exception loadInBackground() {
|
|
||||||
try {
|
|
||||||
// delete collection locally
|
|
||||||
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
|
|
||||||
|
|
||||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
|
||||||
HttpUrl principal = HttpUrl.get(settings.getUri());
|
|
||||||
|
|
||||||
JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), settings), principal);
|
|
||||||
Crypto.CryptoManager crypto = new Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid);
|
|
||||||
|
|
||||||
journalManager.delete(new JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid));
|
|
||||||
JournalEntity journalEntity = JournalEntity.fetch(data, collectionInfo.getServiceEntity(data), collectionInfo.uid);
|
|
||||||
journalEntity.setDeleted(true);
|
|
||||||
data.update(journalEntity);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} catch (Exceptions.HttpException|Exceptions.IntegrityException|Exceptions.GenericCryptoException e) {
|
|
||||||
return e;
|
|
||||||
} catch (InvalidAccountException e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class ConfirmDeleteCollectionFragment extends DialogFragment {
|
|
||||||
|
|
||||||
public static ConfirmDeleteCollectionFragment newInstance(Account account, CollectionInfo collectionInfo) {
|
|
||||||
ConfirmDeleteCollectionFragment frag = new ConfirmDeleteCollectionFragment();
|
|
||||||
Bundle args = new Bundle(2);
|
|
||||||
args.putParcelable(ARG_ACCOUNT, account);
|
|
||||||
args.putSerializable(ARG_COLLECTION_INFO, collectionInfo);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
CollectionInfo collectionInfo = (CollectionInfo) getArguments().getSerializable(ARG_COLLECTION_INFO);
|
|
||||||
String name = TextUtils.isEmpty(collectionInfo.displayName) ? collectionInfo.uid : collectionInfo.displayName;
|
|
||||||
|
|
||||||
return new AlertDialog.Builder(getContext())
|
|
||||||
.setTitle(R.string.delete_collection_confirm_title)
|
|
||||||
.setMessage(getString(R.string.delete_collection_confirm_warning, name))
|
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
DialogFragment frag = new DeleteCollectionFragment();
|
|
||||||
frag.setArguments(getArguments());
|
|
||||||
frag.show(getFragmentManager(), null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v4.app.LoaderManager
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.etesync.syncadapter.*
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto
|
||||||
|
import com.etesync.syncadapter.journalmanager.Exceptions
|
||||||
|
import com.etesync.syncadapter.journalmanager.JournalManager
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
class DeleteCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Exception> {
|
||||||
|
|
||||||
|
protected lateinit var account: Account
|
||||||
|
protected lateinit var collectionInfo: CollectionInfo
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
loaderManager.initLoader(0, arguments, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val progress = ProgressDialog(context)
|
||||||
|
progress.setTitle(R.string.delete_collection_deleting_collection)
|
||||||
|
progress.setMessage(getString(R.string.please_wait))
|
||||||
|
progress.isIndeterminate = true
|
||||||
|
progress.setCanceledOnTouchOutside(false)
|
||||||
|
isCancelable = false
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Exception> {
|
||||||
|
account = args!!.getParcelable(ARG_ACCOUNT)
|
||||||
|
collectionInfo = args.getSerializable(ARG_COLLECTION_INFO) as CollectionInfo
|
||||||
|
return DeleteCollectionLoader(context!!, account, collectionInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<Exception>, exception: Exception?) {
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
fragmentManager!!.beginTransaction()
|
||||||
|
.add(ExceptionInfoFragment.newInstance(exception, account), null)
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
else {
|
||||||
|
val activity = activity
|
||||||
|
if (activity is Refreshable)
|
||||||
|
(activity as Refreshable).refresh()
|
||||||
|
else if (activity is EditCollectionActivity)
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<Exception>) {}
|
||||||
|
|
||||||
|
|
||||||
|
protected class DeleteCollectionLoader(context: Context, internal val account: Account, internal val collectionInfo: CollectionInfo) : AsyncTaskLoader<Exception>(context) {
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): Exception? {
|
||||||
|
try {
|
||||||
|
// delete collection locally
|
||||||
|
val data = (context.applicationContext as App).data
|
||||||
|
|
||||||
|
val settings = AccountSettings(context, account)
|
||||||
|
val principal = HttpUrl.get(settings.uri!!)
|
||||||
|
|
||||||
|
val journalManager = JournalManager(HttpClient.create(context, settings), principal!!)
|
||||||
|
val crypto = Crypto.CryptoManager(collectionInfo.version, settings.password(), collectionInfo.uid)
|
||||||
|
|
||||||
|
journalManager.delete(JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.uid))
|
||||||
|
val journalEntity = JournalEntity.fetch(data, collectionInfo.getServiceEntity(data), collectionInfo.uid)
|
||||||
|
journalEntity!!.isDeleted = true
|
||||||
|
data.update(journalEntity)
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (e: Exceptions.HttpException) {
|
||||||
|
return e
|
||||||
|
} catch (e: Exceptions.IntegrityException) {
|
||||||
|
return e
|
||||||
|
} catch (e: Exceptions.GenericCryptoException) {
|
||||||
|
return e
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmDeleteCollectionFragment : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val collectionInfo = arguments!!.getSerializable(ARG_COLLECTION_INFO) as CollectionInfo
|
||||||
|
val name = if (TextUtils.isEmpty(collectionInfo.displayName)) collectionInfo.uid else collectionInfo.displayName
|
||||||
|
|
||||||
|
return AlertDialog.Builder(context!!)
|
||||||
|
.setTitle(R.string.delete_collection_confirm_title)
|
||||||
|
.setMessage(getString(R.string.delete_collection_confirm_warning, name))
|
||||||
|
.setPositiveButton(android.R.string.yes) { dialog, which ->
|
||||||
|
val frag = DeleteCollectionFragment()
|
||||||
|
frag.arguments = arguments
|
||||||
|
frag.show(fragmentManager!!, null)
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.no) { dialog, which -> dismiss() }
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(account: Account, collectionInfo: CollectionInfo): ConfirmDeleteCollectionFragment {
|
||||||
|
val frag = ConfirmDeleteCollectionFragment()
|
||||||
|
val args = Bundle(2)
|
||||||
|
args.putParcelable(ARG_ACCOUNT, account)
|
||||||
|
args.putSerializable(ARG_COLLECTION_INFO, collectionInfo)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected val ARG_ACCOUNT = "account"
|
||||||
|
protected val ARG_COLLECTION_INFO = "collectionInfo"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
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.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
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 EditCollectionActivity extends CreateCollectionActivity {
|
|
||||||
public static Intent newIntent(Context context, Account account, CollectionInfo info) {
|
|
||||||
Intent intent = new Intent(context, EditCollectionActivity.class);
|
|
||||||
intent.putExtra(CreateCollectionActivity.EXTRA_ACCOUNT, account);
|
|
||||||
intent.putExtra(CreateCollectionActivity.EXTRA_COLLECTION_INFO, info);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setTitle(R.string.edit_collection);
|
|
||||||
|
|
||||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
final View colorSquare = findViewById(R.id.color);
|
|
||||||
if (info.color != null) {
|
|
||||||
colorSquare.setBackgroundColor(info.color);
|
|
||||||
} else {
|
|
||||||
colorSquare.setBackgroundColor(LocalCalendar.defaultColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final EditText edit = (EditText) findViewById(R.id.display_name);
|
|
||||||
edit.setText(info.displayName);
|
|
||||||
|
|
||||||
final EditText desc = (EditText) findViewById(R.id.description);
|
|
||||||
desc.setText(info.description);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.activity_edit_collection, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
|
|
||||||
if (getParent() instanceof Refreshable) {
|
|
||||||
((Refreshable) getParent()).refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDeleteCollection(MenuItem item) {
|
|
||||||
EntityDataStore<Persistable> data = ((App) getApplication()).getData();
|
|
||||||
int journalCount = data.count(JournalEntity.class).where(JournalEntity.SERVICE_MODEL.eq(info.getServiceEntity(data))).get().value();
|
|
||||||
|
|
||||||
if (journalCount < 2) {
|
|
||||||
new AlertDialog.Builder(this)
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setTitle(R.string.account_delete_collection_last_title)
|
|
||||||
.setMessage(R.string.account_delete_collection_last_text)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
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 EditCollectionActivity : CreateCollectionActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setTitle(R.string.edit_collection)
|
||||||
|
|
||||||
|
if (info!!.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
val colorSquare = findViewById<View>(R.id.color)
|
||||||
|
if (info!!.color != null) {
|
||||||
|
colorSquare.setBackgroundColor(info!!.color)
|
||||||
|
} else {
|
||||||
|
colorSquare.setBackgroundColor(LocalCalendar.defaultColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val edit = findViewById<View>(R.id.display_name) as EditText
|
||||||
|
edit.setText(info!!.displayName)
|
||||||
|
|
||||||
|
val desc = findViewById<View>(R.id.description) as EditText
|
||||||
|
desc.setText(info!!.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.activity_edit_collection, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
|
||||||
|
if (parent is Refreshable) {
|
||||||
|
(parent as Refreshable).refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDeleteCollection(item: MenuItem) {
|
||||||
|
val data = (application as App).data
|
||||||
|
val journalCount = data.count(JournalEntity::class.java).where(JournalEntity.SERVICE_MODEL.eq(info!!.getServiceEntity(data))).get().value()
|
||||||
|
|
||||||
|
if (journalCount < 2) {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setTitle(R.string.account_delete_collection_last_title)
|
||||||
|
.setMessage(R.string.account_delete_collection_last_text)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(supportFragmentManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newIntent(context: Context, account: Account, info: CollectionInfo): Intent {
|
||||||
|
val intent = Intent(context, EditCollectionActivity::class.java)
|
||||||
|
intent.putExtra(CreateCollectionActivity.EXTRA_ACCOUNT, account)
|
||||||
|
intent.putExtra(CreateCollectionActivity.EXTRA_COLLECTION_INFO, info)
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions.HttpException;
|
|
||||||
|
|
||||||
public class ExceptionInfoFragment extends DialogFragment {
|
|
||||||
protected static final String
|
|
||||||
ARG_ACCOUNT = "account",
|
|
||||||
ARG_EXCEPTION = "exception";
|
|
||||||
|
|
||||||
public static ExceptionInfoFragment newInstance(@NonNull Exception exception, Account account) {
|
|
||||||
ExceptionInfoFragment frag = new ExceptionInfoFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putSerializable(ARG_EXCEPTION, exception);
|
|
||||||
args.putParcelable(ARG_ACCOUNT, account);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
Bundle args = getArguments();
|
|
||||||
final Exception exception = (Exception)args.getSerializable(ARG_EXCEPTION);
|
|
||||||
final Account account = args.getParcelable(ARG_ACCOUNT);
|
|
||||||
|
|
||||||
int title = R.string.exception;
|
|
||||||
if (exception instanceof HttpException)
|
|
||||||
title = R.string.exception_httpexception;
|
|
||||||
else if (exception instanceof IOException)
|
|
||||||
title = R.string.exception_ioexception;
|
|
||||||
|
|
||||||
Dialog dialog = new AlertDialog.Builder(getContext())
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(exception.getClass().getCanonicalName() + "\n" + exception.getLocalizedMessage())
|
|
||||||
.setNegativeButton(R.string.exception_show_details, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Intent intent = new Intent(getContext(), DebugInfoActivity.class);
|
|
||||||
intent.putExtra(DebugInfoActivity.KEY_THROWABLE, exception);
|
|
||||||
if (account != null)
|
|
||||||
intent.putExtra(Constants.KEY_ACCOUNT, account);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
setCancelable(false);
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.journalmanager.Exceptions.HttpException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class ExceptionInfoFragment : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val args = arguments
|
||||||
|
val exception = args!!.getSerializable(ARG_EXCEPTION) as Exception
|
||||||
|
val account = args.getParcelable<Account>(ARG_ACCOUNT)
|
||||||
|
|
||||||
|
var title = R.string.exception
|
||||||
|
if (exception is HttpException)
|
||||||
|
title = R.string.exception_httpexception
|
||||||
|
else if (exception is IOException)
|
||||||
|
title = R.string.exception_ioexception
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(context!!)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(exception.javaClass.canonicalName + "\n" + exception.localizedMessage)
|
||||||
|
.setNegativeButton(R.string.exception_show_details) { dialog, which ->
|
||||||
|
val intent = Intent(context, DebugInfoActivity::class.java)
|
||||||
|
intent.putExtra(DebugInfoActivity.KEY_THROWABLE, exception)
|
||||||
|
if (account != null)
|
||||||
|
intent.putExtra(Constants.KEY_ACCOUNT, account)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which -> }
|
||||||
|
.create()
|
||||||
|
isCancelable = false
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected val ARG_ACCOUNT = "account"
|
||||||
|
protected val ARG_EXCEPTION = "exception"
|
||||||
|
|
||||||
|
fun newInstance(exception: Exception, account: Account): ExceptionInfoFragment {
|
||||||
|
val frag = ExceptionInfoFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putSerializable(ARG_EXCEPTION, exception)
|
||||||
|
args.putParcelable(ARG_ACCOUNT, account)
|
||||||
|
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,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.app.ActivityCompat;
|
|
||||||
import android.support.v4.app.NotificationManagerCompat;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.resource.LocalTaskList;
|
|
||||||
|
|
||||||
public class PermissionsActivity extends BaseActivity {
|
|
||||||
final static private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
|
|
||||||
|
|
||||||
public static final String
|
|
||||||
PERMISSION_READ_TASKS = "org.dmfs.permission.READ_TASKS",
|
|
||||||
PERMISSION_WRITE_TASKS = "org.dmfs.permission.WRITE_TASKS";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void refresh() {
|
|
||||||
boolean noCalendarPermissions =
|
|
||||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED;
|
|
||||||
findViewById(R.id.calendar_permissions).setVisibility(noCalendarPermissions ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
boolean noContactsPermissions =
|
|
||||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED;
|
|
||||||
findViewById(R.id.contacts_permissions).setVisibility(noContactsPermissions ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
boolean noTaskPermissions;
|
|
||||||
if (LocalTaskList.tasksProviderAvailable(this)) {
|
|
||||||
noTaskPermissions =
|
|
||||||
ActivityCompat.checkSelfPermission(this, PERMISSION_READ_TASKS) != PackageManager.PERMISSION_GRANTED ||
|
|
||||||
ActivityCompat.checkSelfPermission(this, PERMISSION_WRITE_TASKS) != PackageManager.PERMISSION_GRANTED;
|
|
||||||
findViewById(R.id.opentasks_permissions).setVisibility(noTaskPermissions ? View.VISIBLE : View.GONE);
|
|
||||||
} else {
|
|
||||||
findViewById(R.id.opentasks_permissions).setVisibility(View.GONE);
|
|
||||||
noTaskPermissions = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!noCalendarPermissions && !noContactsPermissions && !noTaskPermissions) {
|
|
||||||
NotificationManagerCompat nm = NotificationManagerCompat.from(this);
|
|
||||||
nm.cancel(Constants.NOTIFICATION_PERMISSIONS);
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestCalendarPermissions(View v) {
|
|
||||||
ActivityCompat.requestPermissions(this, new String[] {
|
|
||||||
Manifest.permission.READ_CALENDAR,
|
|
||||||
Manifest.permission.WRITE_CALENDAR
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestContactsPermissions(View v) {
|
|
||||||
ActivityCompat.requestPermissions(this, new String[] {
|
|
||||||
Manifest.permission.READ_CONTACTS,
|
|
||||||
Manifest.permission.WRITE_CONTACTS
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestOpenTasksPermissions(View v) {
|
|
||||||
ActivityCompat.requestPermissions(this, new String[] {
|
|
||||||
PERMISSION_READ_TASKS,
|
|
||||||
PERMISSION_WRITE_TASKS
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void requestAllPermissions(Activity activity) {
|
|
||||||
ActivityCompat.requestPermissions(activity, new String[] {
|
|
||||||
Manifest.permission.READ_CALENDAR,
|
|
||||||
Manifest.permission.WRITE_CALENDAR,
|
|
||||||
Manifest.permission.READ_CONTACTS,
|
|
||||||
Manifest.permission.WRITE_CONTACTS
|
|
||||||
}, REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.ActivityCompat
|
||||||
|
import android.support.v4.app.NotificationManagerCompat
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.resource.LocalTaskList
|
||||||
|
|
||||||
|
class PermissionsActivity : BaseActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun refresh() {
|
||||||
|
val noCalendarPermissions = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED
|
||||||
|
findViewById<View>(R.id.calendar_permissions).visibility = if (noCalendarPermissions) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
val noContactsPermissions = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED
|
||||||
|
findViewById<View>(R.id.contacts_permissions).visibility = if (noContactsPermissions) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
val noTaskPermissions: Boolean
|
||||||
|
if (LocalTaskList.tasksProviderAvailable(this)) {
|
||||||
|
noTaskPermissions = ActivityCompat.checkSelfPermission(this, PERMISSION_READ_TASKS) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, PERMISSION_WRITE_TASKS) != PackageManager.PERMISSION_GRANTED
|
||||||
|
findViewById<View>(R.id.opentasks_permissions).visibility = if (noTaskPermissions) View.VISIBLE else View.GONE
|
||||||
|
} else {
|
||||||
|
findViewById<View>(R.id.opentasks_permissions).visibility = View.GONE
|
||||||
|
noTaskPermissions = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noCalendarPermissions && !noContactsPermissions && !noTaskPermissions) {
|
||||||
|
val nm = NotificationManagerCompat.from(this)
|
||||||
|
nm.cancel(Constants.NOTIFICATION_PERMISSIONS)
|
||||||
|
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestCalendarPermissions(v: View) {
|
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestContactsPermissions(v: View) {
|
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestOpenTasksPermissions(v: View) {
|
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(PERMISSION_READ_TASKS, PERMISSION_WRITE_TASKS), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124
|
||||||
|
|
||||||
|
val PERMISSION_READ_TASKS = "org.dmfs.permission.READ_TASKS"
|
||||||
|
val PERMISSION_WRITE_TASKS = "org.dmfs.permission.WRITE_TASKS"
|
||||||
|
|
||||||
|
fun requestAllPermissions(activity: Activity) {
|
||||||
|
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS), REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,166 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.BuildConfig;
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.utils.HintManager;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class StartupDialogFragment extends DialogFragment {
|
|
||||||
private static final String
|
|
||||||
HINT_BATTERY_OPTIMIZATIONS = "BatteryOptimizations",
|
|
||||||
HINT_VENDOR_SPECIFIC_BUGS = "VendorSpecificBugs";
|
|
||||||
|
|
||||||
private static final String ARGS_MODE = "mode";
|
|
||||||
|
|
||||||
enum Mode {
|
|
||||||
BATTERY_OPTIMIZATIONS,
|
|
||||||
DEVELOPMENT_VERSION,
|
|
||||||
GOOGLE_PLAY_ACCOUNTS_REMOVED,
|
|
||||||
VENDOR_SPECIFIC_BUGS,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StartupDialogFragment[] getStartupDialogs(Context context) {
|
|
||||||
List<StartupDialogFragment> dialogs = new LinkedList<>();
|
|
||||||
|
|
||||||
if (BuildConfig.VERSION_NAME.contains("-alpha") || BuildConfig.VERSION_NAME.contains("-beta") || BuildConfig.VERSION_NAME.contains("-rc"))
|
|
||||||
dialogs.add(StartupDialogFragment.instantiate(Mode.DEVELOPMENT_VERSION));
|
|
||||||
|
|
||||||
// battery optimization whitelisting
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !HintManager.getHintSeen(context, HINT_BATTERY_OPTIMIZATIONS)) {
|
|
||||||
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
if (powerManager != null && !powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID))
|
|
||||||
dialogs.add(StartupDialogFragment.instantiate(Mode.BATTERY_OPTIMIZATIONS));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vendor specific bugs
|
|
||||||
String manu = Build.MANUFACTURER;
|
|
||||||
if (!HintManager.getHintSeen(context, HINT_BATTERY_OPTIMIZATIONS) && (manu.equalsIgnoreCase("Xiaomi") || manu.equalsIgnoreCase("Huawei")) && !Build.DISPLAY.contains("lineage")) {
|
|
||||||
dialogs.add(StartupDialogFragment.instantiate(Mode.VENDOR_SPECIFIC_BUGS));
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.reverse(dialogs);
|
|
||||||
return dialogs.toArray(new StartupDialogFragment[dialogs.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StartupDialogFragment instantiate(Mode mode) {
|
|
||||||
StartupDialogFragment frag = new StartupDialogFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putString(ARGS_MODE, mode.name());
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
setCancelable(false);
|
|
||||||
|
|
||||||
Mode mode = Mode.valueOf(getArguments().getString(ARGS_MODE));
|
|
||||||
switch (mode) {
|
|
||||||
case BATTERY_OPTIMIZATIONS:
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.startup_battery_optimization)
|
|
||||||
.setMessage(R.string.startup_battery_optimization_message)
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.startup_battery_optimization_disable, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
|
||||||
Uri.parse("package:" + BuildConfig.APPLICATION_ID));
|
|
||||||
if (intent.resolveActivity(getContext().getPackageManager()) != null)
|
|
||||||
getContext().startActivity(intent);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
HintManager.setHintSeen(getContext(), HINT_BATTERY_OPTIMIZATIONS, true);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
|
|
||||||
case DEVELOPMENT_VERSION:
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setIcon(R.mipmap.ic_launcher)
|
|
||||||
.setTitle(R.string.startup_development_version)
|
|
||||||
.setMessage(R.string.startup_development_version_message)
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.startup_development_version_give_feedback, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Constants.feedbackUri));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
case VENDOR_SPECIFIC_BUGS:
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.startup_vendor_specific_bugs)
|
|
||||||
.setMessage(R.string.startup_vendor_specific_bugs_message)
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.startup_vendor_specific_bugs_open_faq, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
WebViewActivity.openUrl(getContext(), Constants.faqUri.buildUpon().encodedFragment("vendor-issues").build());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.startup_dont_show_again, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
HintManager.setHintSeen(getContext(), HINT_VENDOR_SPECIFIC_BUGS, true);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException(/* illegal mode argument */);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String installedFrom(Context context) {
|
|
||||||
try {
|
|
||||||
return context.getPackageManager().getInstallerPackageName(context.getPackageName());
|
|
||||||
} catch(IllegalArgumentException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import com.etesync.syncadapter.BuildConfig
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.utils.HintManager
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class StartupDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
BATTERY_OPTIMIZATIONS,
|
||||||
|
DEVELOPMENT_VERSION,
|
||||||
|
GOOGLE_PLAY_ACCOUNTS_REMOVED,
|
||||||
|
VENDOR_SPECIFIC_BUGS
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
isCancelable = false
|
||||||
|
|
||||||
|
val mode = Mode.valueOf(arguments!!.getString(ARGS_MODE))
|
||||||
|
when (mode) {
|
||||||
|
StartupDialogFragment.Mode.BATTERY_OPTIMIZATIONS -> return AlertDialog.Builder(activity!!)
|
||||||
|
.setTitle(R.string.startup_battery_optimization)
|
||||||
|
.setMessage(R.string.startup_battery_optimization_message)
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which -> }
|
||||||
|
.setNeutralButton(R.string.startup_battery_optimization_disable) { dialog, which ->
|
||||||
|
val intent = Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||||
|
Uri.parse("package:" + BuildConfig.APPLICATION_ID))
|
||||||
|
if (intent.resolveActivity(context!!.packageManager) != null)
|
||||||
|
context!!.startActivity(intent)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.startup_dont_show_again) { dialog, which -> HintManager.setHintSeen(context!!, HINT_BATTERY_OPTIMIZATIONS, true) }
|
||||||
|
.create()
|
||||||
|
|
||||||
|
StartupDialogFragment.Mode.DEVELOPMENT_VERSION -> return AlertDialog.Builder(activity!!)
|
||||||
|
.setIcon(R.mipmap.ic_launcher)
|
||||||
|
.setTitle(R.string.startup_development_version)
|
||||||
|
.setMessage(R.string.startup_development_version_message)
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which -> }
|
||||||
|
.setNeutralButton(R.string.startup_development_version_give_feedback) { dialog, which -> startActivity(Intent(Intent.ACTION_VIEW, Constants.feedbackUri)) }
|
||||||
|
.create()
|
||||||
|
StartupDialogFragment.Mode.VENDOR_SPECIFIC_BUGS -> return AlertDialog.Builder(activity!!)
|
||||||
|
.setTitle(R.string.startup_vendor_specific_bugs)
|
||||||
|
.setMessage(R.string.startup_vendor_specific_bugs_message)
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which -> }
|
||||||
|
.setNeutralButton(R.string.startup_vendor_specific_bugs_open_faq) { dialog, which -> WebViewActivity.openUrl(context!!, Constants.faqUri.buildUpon().encodedFragment("vendor-issues").build()) }
|
||||||
|
.setNegativeButton(R.string.startup_dont_show_again) { dialog, which -> HintManager.setHintSeen(context!!, HINT_VENDOR_SPECIFIC_BUGS, true) }
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw IllegalArgumentException(/* illegal mode argument */)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val HINT_BATTERY_OPTIMIZATIONS = "BatteryOptimizations"
|
||||||
|
private val HINT_VENDOR_SPECIFIC_BUGS = "VendorSpecificBugs"
|
||||||
|
|
||||||
|
private val ARGS_MODE = "mode"
|
||||||
|
|
||||||
|
fun getStartupDialogs(context: Context): Array<StartupDialogFragment> {
|
||||||
|
val dialogs = LinkedList<StartupDialogFragment>()
|
||||||
|
|
||||||
|
if (BuildConfig.VERSION_NAME.contains("-alpha") || BuildConfig.VERSION_NAME.contains("-beta") || BuildConfig.VERSION_NAME.contains("-rc"))
|
||||||
|
dialogs.add(StartupDialogFragment.instantiate(Mode.DEVELOPMENT_VERSION))
|
||||||
|
|
||||||
|
// battery optimization whitelisting
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !HintManager.getHintSeen(context, HINT_BATTERY_OPTIMIZATIONS)) {
|
||||||
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
if (powerManager != null && !powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID))
|
||||||
|
dialogs.add(StartupDialogFragment.instantiate(Mode.BATTERY_OPTIMIZATIONS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendor specific bugs
|
||||||
|
val manu = Build.MANUFACTURER
|
||||||
|
if (!HintManager.getHintSeen(context, HINT_BATTERY_OPTIMIZATIONS) && (manu.equals("Xiaomi", ignoreCase = true) || manu.equals("Huawei", ignoreCase = true)) && !Build.DISPLAY.contains("lineage")) {
|
||||||
|
dialogs.add(StartupDialogFragment.instantiate(Mode.VENDOR_SPECIFIC_BUGS))
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(dialogs)
|
||||||
|
return dialogs.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun instantiate(mode: Mode): StartupDialogFragment {
|
||||||
|
val frag = StartupDialogFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putString(ARGS_MODE, mode.name)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installedFrom(context: Context): String? {
|
||||||
|
try {
|
||||||
|
return context.packageManager.getInstallerPackageName(context.packageName)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,261 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.content.ContentProviderClient;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
import android.provider.ContactsContract;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
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.EntryEntity;
|
|
||||||
import com.etesync.syncadapter.model.JournalEntity;
|
|
||||||
import com.etesync.syncadapter.resource.LocalAddressBook;
|
|
||||||
import com.etesync.syncadapter.resource.LocalCalendar;
|
|
||||||
import com.etesync.syncadapter.ui.importlocal.ImportActivity;
|
|
||||||
import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment;
|
|
||||||
import com.etesync.syncadapter.utils.HintManager;
|
|
||||||
import com.etesync.syncadapter.utils.ShowcaseBuilder;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import at.bitfire.ical4android.CalendarStorageException;
|
|
||||||
import at.bitfire.vcard4android.ContactsStorageException;
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
import tourguide.tourguide.ToolTip;
|
|
||||||
import tourguide.tourguide.TourGuide;
|
|
||||||
|
|
||||||
public class ViewCollectionActivity extends BaseActivity implements Refreshable {
|
|
||||||
private final static String HINT_IMPORT = "Import";
|
|
||||||
public final static String EXTRA_ACCOUNT = "account",
|
|
||||||
EXTRA_COLLECTION_INFO = "collectionInfo";
|
|
||||||
|
|
||||||
private Account account;
|
|
||||||
private JournalEntity journalEntity;
|
|
||||||
protected CollectionInfo info;
|
|
||||||
private boolean isOwner;
|
|
||||||
|
|
||||||
public static Intent newIntent(Context context, Account account, CollectionInfo info) {
|
|
||||||
Intent intent = new Intent(context, ViewCollectionActivity.class);
|
|
||||||
intent.putExtra(ViewCollectionActivity.EXTRA_ACCOUNT, account);
|
|
||||||
intent.putExtra(ViewCollectionActivity.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();
|
|
||||||
isOwner = journalEntity.isOwner(account.name);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
new LoadCountTask().execute();
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
final TextView owner = (TextView) findViewById(R.id.owner);
|
|
||||||
if (isOwner) {
|
|
||||||
owner.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
owner.setVisibility(View.VISIBLE);
|
|
||||||
owner.setText(getString(R.string.account_owner, journalEntity.getOwner()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.view_collection_activity);
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
|
|
||||||
info = (CollectionInfo) getIntent().getExtras().getSerializable(EXTRA_COLLECTION_INFO);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.add(R.id.list_entries_container, ListEntriesFragment.newInstance(info))
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
|
|
||||||
final TextView title = (TextView) findViewById(R.id.display_name);
|
|
||||||
if (!HintManager.getHintSeen(this, HINT_IMPORT)) {
|
|
||||||
TourGuide tourGuide = ShowcaseBuilder.getBuilder(this)
|
|
||||||
.setToolTip(new ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_import)).setGravity(Gravity.BOTTOM))
|
|
||||||
.setPointer(null);
|
|
||||||
tourGuide.mOverlay.setHoleRadius(0);
|
|
||||||
tourGuide.playOn(title);
|
|
||||||
HintManager.setHintSeen(this, HINT_IMPORT, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.activity_view_collection, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onEditCollection(MenuItem item) {
|
|
||||||
if (isOwner) {
|
|
||||||
startActivity(EditCollectionActivity.newIntent(this, account, info));
|
|
||||||
} else {
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
|
||||||
.setIcon(R.drawable.ic_info_dark)
|
|
||||||
.setTitle(R.string.not_allowed_title)
|
|
||||||
.setMessage(getString(R.string.edit_owner_only, journalEntity.getOwner()))
|
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}).create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onImport(MenuItem item) {
|
|
||||||
startActivity(ImportActivity.newIntent(ViewCollectionActivity.this, account, info));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onManageMembers(MenuItem item) {
|
|
||||||
if (info.version < 2) {
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
|
||||||
.setIcon(R.drawable.ic_info_dark)
|
|
||||||
.setTitle(R.string.not_allowed_title)
|
|
||||||
.setMessage(R.string.members_old_journals_not_allowed)
|
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}).create();
|
|
||||||
dialog.show();
|
|
||||||
} else if (isOwner) {
|
|
||||||
startActivity(CollectionMembersActivity.newIntent(this, account, info));
|
|
||||||
} else {
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
|
||||||
.setIcon(R.drawable.ic_info_dark)
|
|
||||||
.setTitle(R.string.not_allowed_title)
|
|
||||||
.setMessage(getString(R.string.members_owner_only, journalEntity.getOwner()))
|
|
||||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}).create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LoadCountTask extends AsyncTask<Void, Void, Long> {
|
|
||||||
private int entryCount;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Long doInBackground(Void... aVoids) {
|
|
||||||
EntityDataStore<Persistable> data = ((App) getApplicationContext()).getData();
|
|
||||||
|
|
||||||
final JournalEntity journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
|
|
||||||
|
|
||||||
entryCount = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value();
|
|
||||||
long count;
|
|
||||||
|
|
||||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
try {
|
|
||||||
ContentProviderClient providerClient = getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI);
|
|
||||||
LocalCalendar resource = LocalCalendar.findByName(account, providerClient, LocalCalendar.Factory.INSTANCE, info.uid);
|
|
||||||
providerClient.release();
|
|
||||||
if (resource == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
count = resource.count();
|
|
||||||
} catch (FileNotFoundException | CalendarStorageException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
ContentProviderClient providerClient = getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI);
|
|
||||||
LocalAddressBook resource = LocalAddressBook.findByUid(ViewCollectionActivity.this, providerClient, account, info.uid);
|
|
||||||
providerClient.release();
|
|
||||||
if (resource == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
count = resource.count();
|
|
||||||
} catch (ContactsStorageException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Long result) {
|
|
||||||
final TextView stats = (TextView) findViewById(R.id.stats);
|
|
||||||
findViewById(R.id.progressBar).setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
stats.setText("Stats loading error.");
|
|
||||||
} else {
|
|
||||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
stats.setText(String.format(Locale.getDefault(), "Events: %d, Journal entries: %d",
|
|
||||||
result, entryCount));
|
|
||||||
} else {
|
|
||||||
stats.setText(String.format(Locale.getDefault(), "Contacts: %d, Journal Entries: %d",
|
|
||||||
result, entryCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import at.bitfire.ical4android.CalendarStorageException
|
||||||
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
|
import com.etesync.syncadapter.model.EntryEntity
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
import com.etesync.syncadapter.resource.LocalAddressBook
|
||||||
|
import com.etesync.syncadapter.resource.LocalCalendar
|
||||||
|
import com.etesync.syncadapter.ui.importlocal.ImportActivity
|
||||||
|
import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment
|
||||||
|
import com.etesync.syncadapter.utils.HintManager
|
||||||
|
import com.etesync.syncadapter.utils.ShowcaseBuilder
|
||||||
|
import tourguide.tourguide.ToolTip
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ViewCollectionActivity : BaseActivity(), Refreshable {
|
||||||
|
|
||||||
|
private lateinit var account: Account
|
||||||
|
private var journalEntity: JournalEntity? = null
|
||||||
|
protected lateinit var info: CollectionInfo
|
||||||
|
private var isOwner: Boolean = false
|
||||||
|
|
||||||
|
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
|
||||||
|
isOwner = journalEntity!!.isOwner(account.name)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadCountTask().execute()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
val owner = findViewById<View>(R.id.owner) as TextView
|
||||||
|
if (isOwner) {
|
||||||
|
owner.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
owner.visibility = View.VISIBLE
|
||||||
|
owner.text = getString(R.string.account_owner, journalEntity!!.owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setContentView(R.layout.view_collection_activity)
|
||||||
|
|
||||||
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)
|
||||||
|
info = intent.extras!!.getSerializable(EXTRA_COLLECTION_INFO) as CollectionInfo
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.add(R.id.list_entries_container, ListEntriesFragment.newInstance(info))
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
val title = findViewById<View>(R.id.display_name) as TextView
|
||||||
|
if (!HintManager.getHintSeen(this, HINT_IMPORT)) {
|
||||||
|
val tourGuide = ShowcaseBuilder.getBuilder(this)
|
||||||
|
.setToolTip(ToolTip().setTitle(getString(R.string.tourguide_title)).setDescription(getString(R.string.account_showcase_import)).setGravity(Gravity.BOTTOM))
|
||||||
|
.setPointer(null)
|
||||||
|
tourGuide.mOverlay.setHoleRadius(0)
|
||||||
|
tourGuide.playOn(title)
|
||||||
|
HintManager.setHintSeen(this, HINT_IMPORT, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.activity_view_collection, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onEditCollection(item: MenuItem) {
|
||||||
|
if (isOwner) {
|
||||||
|
startActivity(EditCollectionActivity.newIntent(this, account, info))
|
||||||
|
} else {
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setIcon(R.drawable.ic_info_dark)
|
||||||
|
.setTitle(R.string.not_allowed_title)
|
||||||
|
.setMessage(getString(R.string.edit_owner_only, journalEntity!!.owner))
|
||||||
|
.setPositiveButton(android.R.string.yes) { dialog, which -> }.create()
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onImport(item: MenuItem) {
|
||||||
|
startActivity(ImportActivity.newIntent(this@ViewCollectionActivity, account, info))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onManageMembers(item: MenuItem) {
|
||||||
|
if (info.version < 2) {
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setIcon(R.drawable.ic_info_dark)
|
||||||
|
.setTitle(R.string.not_allowed_title)
|
||||||
|
.setMessage(R.string.members_old_journals_not_allowed)
|
||||||
|
.setPositiveButton(android.R.string.yes) { dialog, which -> }.create()
|
||||||
|
dialog.show()
|
||||||
|
} else if (isOwner) {
|
||||||
|
startActivity(CollectionMembersActivity.newIntent(this, account, info))
|
||||||
|
} else {
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setIcon(R.drawable.ic_info_dark)
|
||||||
|
.setTitle(R.string.not_allowed_title)
|
||||||
|
.setMessage(getString(R.string.members_owner_only, journalEntity!!.owner))
|
||||||
|
.setPositiveButton(android.R.string.yes) { dialog, which -> }.create()
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class LoadCountTask : AsyncTask<Void, Void, Long>() {
|
||||||
|
private var entryCount: Int = 0
|
||||||
|
|
||||||
|
override fun doInBackground(vararg aVoids: Void): Long? {
|
||||||
|
val data = (applicationContext as App).data
|
||||||
|
|
||||||
|
val journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid)
|
||||||
|
|
||||||
|
entryCount = data.count(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value()
|
||||||
|
val count: Long
|
||||||
|
|
||||||
|
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
try {
|
||||||
|
val providerClient = contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)
|
||||||
|
val resource = LocalCalendar.findByName(account, providerClient, LocalCalendar.Factory.INSTANCE, info.uid)
|
||||||
|
providerClient!!.release()
|
||||||
|
if (resource == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
count = resource.count()
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
} catch (e: CalendarStorageException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
val providerClient = contentResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)
|
||||||
|
val resource = LocalAddressBook.findByUid(this@ViewCollectionActivity, providerClient!!, account, info.uid)
|
||||||
|
providerClient.release()
|
||||||
|
if (resource == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
count = resource.count()
|
||||||
|
} catch (e: ContactsStorageException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(result: Long?) {
|
||||||
|
val stats = findViewById<View>(R.id.stats) as TextView
|
||||||
|
findViewById<View>(R.id.progressBar).visibility = View.GONE
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
stats.text = "Stats loading error."
|
||||||
|
} else {
|
||||||
|
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
stats.text = String.format(Locale.getDefault(), "Events: %d, Journal entries: %d",
|
||||||
|
result, entryCount)
|
||||||
|
} else {
|
||||||
|
stats.text = String.format(Locale.getDefault(), "Contacts: %d, Journal Entries: %d",
|
||||||
|
result, entryCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val HINT_IMPORT = "Import"
|
||||||
|
val EXTRA_ACCOUNT = "account"
|
||||||
|
val EXTRA_COLLECTION_INFO = "collectionInfo"
|
||||||
|
|
||||||
|
fun newIntent(context: Context, account: Account, info: CollectionInfo): Intent {
|
||||||
|
val intent = Intent(context, ViewCollectionActivity::class.java)
|
||||||
|
intent.putExtra(ViewCollectionActivity.EXTRA_ACCOUNT, account)
|
||||||
|
intent.putExtra(ViewCollectionActivity.EXTRA_COLLECTION_INFO, info)
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
189
app/src/main/java/com/etesync/syncadapter/ui/WebViewActivity.kt
Normal file
189
app/src/main/java/com/etesync/syncadapter/ui/WebViewActivity.kt
Normal file
@ -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,187 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.journalviewer;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.ListFragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
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.EntryEntity;
|
|
||||||
import com.etesync.syncadapter.model.JournalEntity;
|
|
||||||
import com.etesync.syncadapter.model.JournalModel;
|
|
||||||
import com.etesync.syncadapter.model.SyncEntry;
|
|
||||||
import com.etesync.syncadapter.ui.JournalItemActivity;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
|
|
||||||
public class ListEntriesFragment extends ListFragment implements AdapterView.OnItemClickListener {
|
|
||||||
protected static final String EXTRA_COLLECTION_INFO = "collectionInfo";
|
|
||||||
|
|
||||||
private EntityDataStore<Persistable> data;
|
|
||||||
private CollectionInfo info;
|
|
||||||
private JournalEntity journalEntity;
|
|
||||||
private AsyncTask asyncTask;
|
|
||||||
|
|
||||||
private TextView emptyTextView;
|
|
||||||
|
|
||||||
public static ListEntriesFragment newInstance(CollectionInfo info) {
|
|
||||||
ListEntriesFragment frag = new ListEntriesFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putSerializable(EXTRA_COLLECTION_INFO, info);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
data = ((App) getContext().getApplicationContext()).getData();
|
|
||||||
info = (CollectionInfo) getArguments().getSerializable(EXTRA_COLLECTION_INFO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
getActivity().setTitle(info.displayName);
|
|
||||||
View view = inflater.inflate(R.layout.journal_viewer_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 onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
asyncTask = new JournalFetch().execute();
|
|
||||||
|
|
||||||
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) {
|
|
||||||
EntryEntity entry = (EntryEntity) getListAdapter().getItem(position);
|
|
||||||
startActivity(JournalItemActivity.newIntent(getContext(), info, entry.getContent()));
|
|
||||||
}
|
|
||||||
|
|
||||||
class EntriesListAdapter extends ArrayAdapter<EntryEntity> {
|
|
||||||
EntriesListAdapter(Context context) {
|
|
||||||
super(context, R.layout.journal_viewer_list_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public View getView(int position, View v, @NonNull ViewGroup parent) {
|
|
||||||
if (v == null)
|
|
||||||
v = LayoutInflater.from(getContext()).inflate(R.layout.journal_viewer_list_item, parent, false);
|
|
||||||
|
|
||||||
EntryEntity entryEntity = getItem(position);
|
|
||||||
|
|
||||||
TextView tv = (TextView) v.findViewById(R.id.title);
|
|
||||||
|
|
||||||
// FIXME: hacky way to make it show sensible info
|
|
||||||
CollectionInfo info = journalEntity.getInfo();
|
|
||||||
setJournalEntryView(v, info, entryEntity.getContent());
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getLine(String content, String prefix) {
|
|
||||||
if (content == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = content.indexOf(prefix);
|
|
||||||
if (start >= 0) {
|
|
||||||
int end = content.indexOf("\n", start);
|
|
||||||
content = content.substring(start + prefix.length(), end);
|
|
||||||
} else {
|
|
||||||
content = null;
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setJournalEntryView(View v, CollectionInfo info, SyncEntry syncEntry) {
|
|
||||||
|
|
||||||
TextView tv = (TextView) v.findViewById(R.id.title);
|
|
||||||
|
|
||||||
// FIXME: hacky way to make it show sensible info
|
|
||||||
String fullContent = syncEntry.getContent();
|
|
||||||
String prefix;
|
|
||||||
if (info.type == CollectionInfo.Type.CALENDAR) {
|
|
||||||
prefix = "SUMMARY:";
|
|
||||||
} else {
|
|
||||||
prefix = "FN:";
|
|
||||||
}
|
|
||||||
String content = getLine(fullContent, prefix);
|
|
||||||
content = (content != null) ? content : "Not found";
|
|
||||||
tv.setText(content);
|
|
||||||
|
|
||||||
tv = (TextView) v.findViewById(R.id.description);
|
|
||||||
content = getLine(fullContent, "UID:");
|
|
||||||
content = "UID: " + ((content != null) ? content : "Not found");
|
|
||||||
tv.setText(content);
|
|
||||||
|
|
||||||
ImageView action = (ImageView) v.findViewById(R.id.action);
|
|
||||||
switch (syncEntry.getAction()) {
|
|
||||||
case ADD:
|
|
||||||
action.setImageResource(R.drawable.action_add);
|
|
||||||
break;
|
|
||||||
case CHANGE:
|
|
||||||
action.setImageResource(R.drawable.action_change);
|
|
||||||
break;
|
|
||||||
case DELETE:
|
|
||||||
action.setImageResource(R.drawable.action_delete);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class JournalFetch extends AsyncTask<Void, Void, List<EntryEntity>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<EntryEntity> doInBackground(Void... voids) {
|
|
||||||
journalEntity = JournalModel.Journal.fetch(data, info.getServiceEntity(data), info.uid);
|
|
||||||
return data.select(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).orderBy(EntryEntity.ID.desc()).get().toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(List<EntryEntity> result) {
|
|
||||||
EntriesListAdapter listAdapter = new EntriesListAdapter(getContext());
|
|
||||||
setListAdapter(listAdapter);
|
|
||||||
|
|
||||||
listAdapter.addAll(result);
|
|
||||||
|
|
||||||
emptyTextView.setText(getString(R.string.journal_entries_list_empty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.journalviewer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.ListFragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.model.*
|
||||||
|
import com.etesync.syncadapter.ui.JournalItemActivity
|
||||||
|
import io.requery.Persistable
|
||||||
|
import io.requery.sql.EntityDataStore
|
||||||
|
|
||||||
|
class ListEntriesFragment : ListFragment(), AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
|
private var data: EntityDataStore<Persistable>? = null
|
||||||
|
private lateinit var info: CollectionInfo
|
||||||
|
private var journalEntity: JournalEntity? = null
|
||||||
|
private var asyncTask: AsyncTask<*, *, *>? = null
|
||||||
|
|
||||||
|
private var emptyTextView: TextView? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
data = (context!!.applicationContext as App).data
|
||||||
|
info = arguments!!.getSerializable(EXTRA_COLLECTION_INFO) as CollectionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
activity!!.title = info.displayName
|
||||||
|
val view = inflater.inflate(R.layout.journal_viewer_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 onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
asyncTask = JournalFetch().execute()
|
||||||
|
|
||||||
|
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 entry = listAdapter.getItem(position) as EntryEntity
|
||||||
|
startActivity(JournalItemActivity.newIntent(context!!, info, entry.content))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal inner class EntriesListAdapter(context: Context) : ArrayAdapter<EntryEntity>(context, R.layout.journal_viewer_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.journal_viewer_list_item, parent, false)
|
||||||
|
|
||||||
|
val entryEntity = getItem(position)
|
||||||
|
|
||||||
|
val tv = v!!.findViewById<View>(R.id.title) as TextView
|
||||||
|
|
||||||
|
// FIXME: hacky way to make it show sensible info
|
||||||
|
val info = journalEntity!!.info
|
||||||
|
setJournalEntryView(v, info, entryEntity!!.content)
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class JournalFetch : AsyncTask<Void, Void, List<EntryEntity>>() {
|
||||||
|
|
||||||
|
override fun doInBackground(vararg voids: Void): List<EntryEntity> {
|
||||||
|
journalEntity = JournalModel.Journal.fetch(data!!, info!!.getServiceEntity(data), info!!.uid)
|
||||||
|
return data!!.select(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journalEntity)).orderBy(EntryEntity.ID.desc()).get().toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(result: List<EntryEntity>) {
|
||||||
|
val listAdapter = EntriesListAdapter(context!!)
|
||||||
|
setListAdapter(listAdapter)
|
||||||
|
|
||||||
|
listAdapter.addAll(result)
|
||||||
|
|
||||||
|
emptyTextView!!.text = getString(R.string.journal_entries_list_empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected val EXTRA_COLLECTION_INFO = "collectionInfo"
|
||||||
|
|
||||||
|
fun newInstance(info: CollectionInfo): ListEntriesFragment {
|
||||||
|
val frag = ListEntriesFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putSerializable(EXTRA_COLLECTION_INFO, info)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLine(content: String?, prefix: String): String? {
|
||||||
|
var content: String? = content ?: return null
|
||||||
|
|
||||||
|
val start = content!!.indexOf(prefix)
|
||||||
|
if (start >= 0) {
|
||||||
|
val end = content.indexOf("\n", start)
|
||||||
|
content = content.substring(start + prefix.length, end)
|
||||||
|
} else {
|
||||||
|
content = null
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setJournalEntryView(v: View, info: CollectionInfo, syncEntry: SyncEntry) {
|
||||||
|
|
||||||
|
var tv = v.findViewById<View>(R.id.title) as TextView
|
||||||
|
|
||||||
|
// FIXME: hacky way to make it show sensible info
|
||||||
|
val fullContent = syncEntry.content
|
||||||
|
val prefix: String
|
||||||
|
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
prefix = "SUMMARY:"
|
||||||
|
} else {
|
||||||
|
prefix = "FN:"
|
||||||
|
}
|
||||||
|
var content = getLine(fullContent, prefix)
|
||||||
|
content = content ?: "Not found"
|
||||||
|
tv.text = content
|
||||||
|
|
||||||
|
tv = v.findViewById<View>(R.id.description) as TextView
|
||||||
|
content = getLine(fullContent, "UID:")
|
||||||
|
content = "UID: " + (content ?: "Not found")
|
||||||
|
tv.text = content
|
||||||
|
|
||||||
|
val action = v.findViewById<View>(R.id.action) as ImageView
|
||||||
|
when (syncEntry.action) {
|
||||||
|
SyncEntry.Actions.ADD -> action.setImageResource(R.drawable.action_add)
|
||||||
|
SyncEntry.Actions.CHANGE -> action.setImageResource(R.drawable.action_change)
|
||||||
|
SyncEntry.Actions.DELETE -> action.setImageResource(R.drawable.action_delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.HttpClient;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto;
|
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions;
|
|
||||||
import com.etesync.syncadapter.journalmanager.JournalAuthenticator;
|
|
||||||
import com.etesync.syncadapter.log.StringHandler;
|
|
||||||
import com.etesync.syncadapter.model.CollectionInfo;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
public class BaseConfigurationFinder {
|
|
||||||
protected final Context context;
|
|
||||||
protected final LoginCredentials credentials;
|
|
||||||
|
|
||||||
protected final Logger log;
|
|
||||||
protected final StringHandler logBuffer = new StringHandler();
|
|
||||||
protected OkHttpClient httpClient;
|
|
||||||
|
|
||||||
public BaseConfigurationFinder(@NonNull Context context, @NonNull LoginCredentials credentials) {
|
|
||||||
this.context = context;
|
|
||||||
this.credentials = credentials;
|
|
||||||
|
|
||||||
log = Logger.getLogger("syncadapter.BaseConfigurationFinder");
|
|
||||||
log.setLevel(Level.FINEST);
|
|
||||||
log.addHandler(logBuffer);
|
|
||||||
|
|
||||||
httpClient = HttpClient.create(context, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Configuration findInitialConfiguration() {
|
|
||||||
boolean failed = false;
|
|
||||||
Configuration.ServiceInfo
|
|
||||||
cardDavConfig = findInitialConfiguration(CollectionInfo.Type.ADDRESS_BOOK),
|
|
||||||
calDavConfig = findInitialConfiguration(CollectionInfo.Type.CALENDAR);
|
|
||||||
|
|
||||||
JournalAuthenticator authenticator = new JournalAuthenticator(httpClient, HttpUrl.get(credentials.uri));
|
|
||||||
|
|
||||||
String authtoken = null;
|
|
||||||
try {
|
|
||||||
authtoken = authenticator.getAuthToken(credentials.userName, credentials.password);
|
|
||||||
} catch (Exceptions.HttpException|IOException e) {
|
|
||||||
log.warning(e.getMessage());
|
|
||||||
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Configuration(
|
|
||||||
credentials.uri,
|
|
||||||
credentials.userName, authtoken,
|
|
||||||
cardDavConfig, calDavConfig,
|
|
||||||
logBuffer.toString(), failed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Configuration.ServiceInfo findInitialConfiguration(@NonNull CollectionInfo.Type service) {
|
|
||||||
// put discovered information here
|
|
||||||
final Configuration.ServiceInfo config = new Configuration.ServiceInfo();
|
|
||||||
log.info("Finding initial " + service.toString() + " service configuration");
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// data classes
|
|
||||||
|
|
||||||
public static class Configuration implements Serializable {
|
|
||||||
// We have to use URI here because HttpUrl is not serializable!
|
|
||||||
|
|
||||||
public Configuration(final URI url, final String userName, final String authtoken, final ServiceInfo cardDAV, final ServiceInfo calDAV, final String logs, final boolean failed) {
|
|
||||||
this.url = url;
|
|
||||||
this.userName = userName;
|
|
||||||
this.authtoken = authtoken;
|
|
||||||
this.cardDAV = cardDAV;
|
|
||||||
this.calDAV = calDAV;
|
|
||||||
this.logs = logs;
|
|
||||||
this.failed = failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ServiceInfo implements Serializable {
|
|
||||||
public final Map<String, CollectionInfo> collections = new HashMap<>();
|
|
||||||
|
|
||||||
@java.lang.Override
|
|
||||||
@java.lang.SuppressWarnings("all")
|
|
||||||
public java.lang.String toString() {
|
|
||||||
return "BaseConfigurationFinder.Configuration.ServiceInfo(collections=" + this.collections + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final URI url;
|
|
||||||
|
|
||||||
public final String userName, authtoken;
|
|
||||||
public String rawPassword;
|
|
||||||
public String password;
|
|
||||||
public Crypto.AsymmetricKeyPair keyPair;
|
|
||||||
|
|
||||||
public final ServiceInfo cardDAV;
|
|
||||||
public final ServiceInfo calDAV;
|
|
||||||
|
|
||||||
public final String logs;
|
|
||||||
|
|
||||||
public Throwable error;
|
|
||||||
|
|
||||||
private final boolean failed;
|
|
||||||
|
|
||||||
public boolean isFailed() {
|
|
||||||
return failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@java.lang.Override
|
|
||||||
@java.lang.SuppressWarnings("all")
|
|
||||||
public java.lang.String toString() {
|
|
||||||
return "BaseConfigurationFinder.Configuration(url=" + this.url + ", userName=" + this.userName + ", keyPair=" + this.keyPair + ", cardDAV=" + this.cardDAV + ", calDAV=" + this.calDAV + ", error=" + this.error + ", failed=" + this.isFailed() + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.etesync.syncadapter.HttpClient
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto
|
||||||
|
import com.etesync.syncadapter.journalmanager.Exceptions
|
||||||
|
import com.etesync.syncadapter.journalmanager.JournalAuthenticator
|
||||||
|
import com.etesync.syncadapter.log.StringHandler
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.*
|
||||||
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
class BaseConfigurationFinder(protected val context: Context, protected val credentials: LoginCredentials) {
|
||||||
|
|
||||||
|
protected val log: Logger
|
||||||
|
protected val logBuffer = StringHandler()
|
||||||
|
protected var httpClient: OkHttpClient
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
log = Logger.getLogger("syncadapter.BaseConfigurationFinder")
|
||||||
|
log.level = Level.FINEST
|
||||||
|
log.addHandler(logBuffer)
|
||||||
|
|
||||||
|
httpClient = HttpClient.create(context, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun findInitialConfiguration(): Configuration {
|
||||||
|
var failed = false
|
||||||
|
val cardDavConfig = findInitialConfiguration(CollectionInfo.Type.ADDRESS_BOOK)
|
||||||
|
val calDavConfig = findInitialConfiguration(CollectionInfo.Type.CALENDAR)
|
||||||
|
|
||||||
|
val authenticator = JournalAuthenticator(httpClient, HttpUrl.get(credentials.uri!!)!!)
|
||||||
|
|
||||||
|
var authtoken: String? = null
|
||||||
|
try {
|
||||||
|
authtoken = authenticator.getAuthToken(credentials.userName, credentials.password)
|
||||||
|
} catch (e: Exceptions.HttpException) {
|
||||||
|
log.warning(e.message)
|
||||||
|
|
||||||
|
failed = true
|
||||||
|
} catch (e: IOException) {
|
||||||
|
log.warning(e.message)
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return Configuration(
|
||||||
|
credentials.uri,
|
||||||
|
credentials.userName, authtoken,
|
||||||
|
cardDavConfig, calDavConfig,
|
||||||
|
logBuffer.toString(), failed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun findInitialConfiguration(service: CollectionInfo.Type): Configuration.ServiceInfo {
|
||||||
|
// put discovered information here
|
||||||
|
val config = Configuration.ServiceInfo()
|
||||||
|
log.info("Finding initial " + service.toString() + " service configuration")
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// data classes
|
||||||
|
|
||||||
|
class Configuration
|
||||||
|
// We have to use URI here because HttpUrl is not serializable!
|
||||||
|
|
||||||
|
(val url: URI, val userName: String, val authtoken: String?, val cardDAV: ServiceInfo, val calDAV: ServiceInfo, val logs: String, val isFailed: Boolean) : Serializable {
|
||||||
|
var rawPassword: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var keyPair: Crypto.AsymmetricKeyPair? = null
|
||||||
|
|
||||||
|
var error: Throwable? = null
|
||||||
|
|
||||||
|
class ServiceInfo : Serializable {
|
||||||
|
val collections: Map<String, CollectionInfo> = HashMap()
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "BaseConfigurationFinder.Configuration.ServiceInfo(collections=" + this.collections + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "BaseConfigurationFinder.Configuration(url=" + this.url + ", userName=" + this.userName + ", keyPair=" + this.keyPair + ", cardDAV=" + this.cardDAV + ", calDAV=" + this.calDAV + ", error=" + this.error + ", failed=" + this.isFailed + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.DebugInfoActivity;
|
|
||||||
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration;
|
|
||||||
|
|
||||||
public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> {
|
|
||||||
protected static final String ARG_LOGIN_CREDENTIALS = "credentials";
|
|
||||||
|
|
||||||
public static DetectConfigurationFragment newInstance(LoginCredentials credentials) {
|
|
||||||
DetectConfigurationFragment frag = new DetectConfigurationFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putParcelable(ARG_LOGIN_CREDENTIALS, credentials);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
ProgressDialog progress = new ProgressDialog(getActivity());
|
|
||||||
progress.setTitle(R.string.login_configuration_detection);
|
|
||||||
progress.setMessage(getString(R.string.login_querying_server));
|
|
||||||
progress.setIndeterminate(true);
|
|
||||||
progress.setCanceledOnTouchOutside(false);
|
|
||||||
setCancelable(false);
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, getArguments(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Configuration> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new ServerConfigurationLoader(getContext(), (LoginCredentials)args.getParcelable(ARG_LOGIN_CREDENTIALS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Configuration> loader, Configuration data) {
|
|
||||||
if (data != null) {
|
|
||||||
if (data.isFailed())
|
|
||||||
// no service found: show error message
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.add(NothingDetectedFragment.newInstance(data.logs), null)
|
|
||||||
.commitAllowingStateLoss();
|
|
||||||
else
|
|
||||||
// service found: continue
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.replace(android.R.id.content, EncryptionDetailsFragment.newInstance(data))
|
|
||||||
.addToBackStack(null)
|
|
||||||
.commitAllowingStateLoss();
|
|
||||||
} else
|
|
||||||
App.log.severe("Configuration detection failed");
|
|
||||||
|
|
||||||
dismissAllowingStateLoss();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Configuration> loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class NothingDetectedFragment extends DialogFragment {
|
|
||||||
private static String KEY_LOGS = "logs";
|
|
||||||
|
|
||||||
public static NothingDetectedFragment newInstance(String logs) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString(KEY_LOGS, logs);
|
|
||||||
NothingDetectedFragment fragment = new NothingDetectedFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.login_configuration_detection)
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setMessage(R.string.login_wrong_username_or_password)
|
|
||||||
.setNeutralButton(R.string.login_view_logs, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Intent intent = new Intent(getActivity(), DebugInfoActivity.class);
|
|
||||||
intent.putExtra(DebugInfoActivity.KEY_LOGS, getArguments().getString(KEY_LOGS));
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
// dismiss
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> {
|
|
||||||
final Context context;
|
|
||||||
final LoginCredentials credentials;
|
|
||||||
|
|
||||||
public ServerConfigurationLoader(Context context, LoginCredentials credentials) {
|
|
||||||
super(context);
|
|
||||||
this.context = context;
|
|
||||||
this.credentials = credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Configuration loadInBackground() {
|
|
||||||
return new BaseConfigurationFinder(context, credentials).findInitialConfiguration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v4.app.LoaderManager
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.DebugInfoActivity
|
||||||
|
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
|
||||||
|
|
||||||
|
class DetectConfigurationFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Configuration> {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val progress = ProgressDialog(activity)
|
||||||
|
progress.setTitle(R.string.login_configuration_detection)
|
||||||
|
progress.setMessage(getString(R.string.login_querying_server))
|
||||||
|
progress.isIndeterminate = true
|
||||||
|
progress.setCanceledOnTouchOutside(false)
|
||||||
|
isCancelable = false
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
loaderManager.initLoader(0, arguments, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Configuration> {
|
||||||
|
return ServerConfigurationLoader(context!!, args!!.getParcelable(ARG_LOGIN_CREDENTIALS) as LoginCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<Configuration>, data: Configuration?) {
|
||||||
|
if (data != null) {
|
||||||
|
if (data.isFailed)
|
||||||
|
// no service found: show error message
|
||||||
|
fragmentManager!!.beginTransaction()
|
||||||
|
.add(NothingDetectedFragment.newInstance(data.logs), null)
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
else
|
||||||
|
// service found: continue
|
||||||
|
fragmentManager!!.beginTransaction()
|
||||||
|
.replace(android.R.id.content, EncryptionDetailsFragment.newInstance(data))
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
} else
|
||||||
|
App.log.severe("Configuration detection failed")
|
||||||
|
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<Configuration>) {}
|
||||||
|
|
||||||
|
|
||||||
|
class NothingDetectedFragment : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return AlertDialog.Builder(activity!!)
|
||||||
|
.setTitle(R.string.login_configuration_detection)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setMessage(R.string.login_wrong_username_or_password)
|
||||||
|
.setNeutralButton(R.string.login_view_logs) { dialog, which ->
|
||||||
|
val intent = Intent(activity, DebugInfoActivity::class.java)
|
||||||
|
intent.putExtra(DebugInfoActivity.KEY_LOGS, arguments!!.getString(KEY_LOGS))
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||||
|
// dismiss
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val KEY_LOGS = "logs"
|
||||||
|
|
||||||
|
fun newInstance(logs: String): NothingDetectedFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(KEY_LOGS, logs)
|
||||||
|
val fragment = NothingDetectedFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ServerConfigurationLoader(context: Context, val credentials: LoginCredentials) : AsyncTaskLoader<Configuration>(context) {
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): Configuration? {
|
||||||
|
return BaseConfigurationFinder(context, credentials).findInitialConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected val ARG_LOGIN_CREDENTIALS = "credentials"
|
||||||
|
|
||||||
|
fun newInstance(credentials: LoginCredentials): DetectConfigurationFragment {
|
||||||
|
val frag = DetectConfigurationFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putParcelable(ARG_LOGIN_CREDENTIALS, credentials)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.widget.EditPassword;
|
|
||||||
|
|
||||||
public class EncryptionDetailsFragment extends Fragment {
|
|
||||||
|
|
||||||
private static final String KEY_CONFIG = "config";
|
|
||||||
EditPassword editPassword = null;
|
|
||||||
|
|
||||||
|
|
||||||
public static EncryptionDetailsFragment newInstance(BaseConfigurationFinder.Configuration config) {
|
|
||||||
EncryptionDetailsFragment frag = new EncryptionDetailsFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putSerializable(KEY_CONFIG, config);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
final View v = inflater.inflate(R.layout.login_encryption_details, container, false);
|
|
||||||
|
|
||||||
Button btnBack = (Button)v.findViewById(R.id.back);
|
|
||||||
btnBack.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
getFragmentManager().popBackStack();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final BaseConfigurationFinder.Configuration config = (BaseConfigurationFinder.Configuration)getArguments().getSerializable(KEY_CONFIG);
|
|
||||||
|
|
||||||
TextView accountName = (TextView)v.findViewById(R.id.account_name);
|
|
||||||
accountName.setText(getString(R.string.login_encryption_account_label) + " " + config.userName);
|
|
||||||
|
|
||||||
editPassword = (EditPassword) v.findViewById(R.id.encryption_password);
|
|
||||||
|
|
||||||
Button btnCreate = (Button)v.findViewById(R.id.create_account);
|
|
||||||
btnCreate.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (validateEncryptionData(config) == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupEncryptionFragment.newInstance(config).show(getFragmentManager(), null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final TextView extra_details = (TextView) v.findViewById(R.id.encryption_extra_info);
|
|
||||||
extra_details.setText(getString(R.string.login_encryption_extra_info, Constants.faqUri.buildUpon().appendEncodedPath("#securing-etesync").build().toString()));
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BaseConfigurationFinder.Configuration validateEncryptionData(BaseConfigurationFinder.Configuration config) {
|
|
||||||
boolean valid = true;
|
|
||||||
String password = editPassword.getText().toString();
|
|
||||||
if (password.isEmpty()) {
|
|
||||||
editPassword.setError(getString(R.string.login_password_required));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.rawPassword = password;
|
|
||||||
|
|
||||||
return valid ? config : null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.widget.EditPassword
|
||||||
|
|
||||||
|
class EncryptionDetailsFragment : Fragment() {
|
||||||
|
internal var editPassword: EditPassword? = null
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val v = inflater.inflate(R.layout.login_encryption_details, container, false)
|
||||||
|
|
||||||
|
val btnBack = v.findViewById<View>(R.id.back) as Button
|
||||||
|
btnBack.setOnClickListener { fragmentManager!!.popBackStack() }
|
||||||
|
|
||||||
|
val config = arguments!!.getSerializable(KEY_CONFIG) as BaseConfigurationFinder.Configuration
|
||||||
|
|
||||||
|
val accountName = v.findViewById<View>(R.id.account_name) as TextView
|
||||||
|
accountName.text = getString(R.string.login_encryption_account_label) + " " + config.userName
|
||||||
|
|
||||||
|
editPassword = v.findViewById<View>(R.id.encryption_password) as EditPassword
|
||||||
|
|
||||||
|
val btnCreate = v.findViewById<View>(R.id.create_account) as Button
|
||||||
|
btnCreate.setOnClickListener(View.OnClickListener {
|
||||||
|
if (validateEncryptionData(config) == null) {
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupEncryptionFragment.newInstance(config).show(fragmentManager!!, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
val extra_details = v.findViewById<View>(R.id.encryption_extra_info) as TextView
|
||||||
|
extra_details.text = getString(R.string.login_encryption_extra_info, Constants.faqUri.buildUpon().appendEncodedPath("#securing-etesync").build().toString())
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateEncryptionData(config: BaseConfigurationFinder.Configuration): BaseConfigurationFinder.Configuration? {
|
||||||
|
var valid = true
|
||||||
|
val password = editPassword!!.text.toString()
|
||||||
|
if (password.isEmpty()) {
|
||||||
|
editPassword!!.setError(getString(R.string.login_password_required))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config.rawPassword = password
|
||||||
|
|
||||||
|
return if (valid) config else null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val KEY_CONFIG = "config"
|
||||||
|
|
||||||
|
|
||||||
|
fun newInstance(config: BaseConfigurationFinder.Configuration): EncryptionDetailsFragment {
|
||||||
|
val frag = EncryptionDetailsFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putSerializable(KEY_CONFIG, config)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.BaseActivity;
|
|
||||||
import com.etesync.syncadapter.ui.WebViewActivity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activity to initially connect to a server and create an account.
|
|
||||||
* Fields for server/user data can be pre-filled with extras in the Intent.
|
|
||||||
*/
|
|
||||||
public class LoginActivity extends BaseActivity {
|
|
||||||
/**
|
|
||||||
* When set, and {@link #EXTRA_PASSWORD} is set too, the user name field will be set to this value.
|
|
||||||
*/
|
|
||||||
public static final String EXTRA_USERNAME = "username";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When set, the password field will be set to this value.
|
|
||||||
*/
|
|
||||||
public static final String EXTRA_PASSWORD = "password";
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState == null)
|
|
||||||
// first call, add fragment
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(android.R.id.content, new LoginCredentialsFragment())
|
|
||||||
.commit();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.activity_login, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showHelp(MenuItem item) {
|
|
||||||
WebViewActivity.openUrl(this, Constants.helpUri);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.BaseActivity
|
||||||
|
import com.etesync.syncadapter.ui.WebViewActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity to initially connect to a server and create an account.
|
||||||
|
* Fields for server/user data can be pre-filled with extras in the Intent.
|
||||||
|
*/
|
||||||
|
class LoginActivity : BaseActivity() {
|
||||||
|
|
||||||
|
|
||||||
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if (savedInstanceState == null)
|
||||||
|
// first call, add fragment
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(android.R.id.content, LoginCredentialsFragment())
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.activity_login, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showHelp(item: MenuItem) {
|
||||||
|
WebViewActivity.openUrl(this, Constants.helpUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* When set, and [.EXTRA_PASSWORD] is set too, the user name field will be set to this value.
|
||||||
|
*/
|
||||||
|
val EXTRA_USERNAME = "username"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set, the password field will be set to this value.
|
||||||
|
*/
|
||||||
|
val EXTRA_PASSWORD = "password"
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
public class LoginCredentials implements Parcelable {
|
|
||||||
public final URI uri;
|
|
||||||
public final String userName, password;
|
|
||||||
|
|
||||||
public LoginCredentials(URI uri, String userName, String password) {
|
|
||||||
this.userName = userName;
|
|
||||||
this.password = password;
|
|
||||||
|
|
||||||
if (uri == null) {
|
|
||||||
try {
|
|
||||||
uri = new URI(Constants.serviceUrl.toString());
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
App.log.severe("Should never happen, it's a constant");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.uri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeSerializable(uri);
|
|
||||||
dest.writeString(userName);
|
|
||||||
dest.writeString(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator CREATOR = new Creator<LoginCredentials>() {
|
|
||||||
@Override
|
|
||||||
public LoginCredentials createFromParcel(Parcel source) {
|
|
||||||
return new LoginCredentials(
|
|
||||||
(URI)source.readSerializable(),
|
|
||||||
source.readString(), source.readString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoginCredentials[] newArray(int size) {
|
|
||||||
return new LoginCredentials[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
|
class LoginCredentials(uri: URI?, val userName: String, val password: String) : Parcelable {
|
||||||
|
val uri: URI?
|
||||||
|
|
||||||
|
init {
|
||||||
|
var uri = uri
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
try {
|
||||||
|
uri = URI(Constants.serviceUrl.toString())
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
App.log.severe("Should never happen, it's a constant")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
this.uri = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
dest.writeSerializable(uri)
|
||||||
|
dest.writeString(userName)
|
||||||
|
dest.writeString(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<*> = object : Parcelable.Creator<LoginCredentials> {
|
||||||
|
override fun createFromParcel(source: Parcel): LoginCredentials {
|
||||||
|
return LoginCredentials(
|
||||||
|
source.readSerializable() as URI,
|
||||||
|
source.readString(), source.readString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<LoginCredentials> {
|
||||||
|
return arrayOfNulls<LoginCredentials>(size) as Array<LoginCredentials>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,160 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
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.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v4.app.DialogFragment;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.AccountSettings;
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import com.etesync.syncadapter.InvalidAccountException;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.DebugInfoActivity;
|
|
||||||
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration;
|
|
||||||
|
|
||||||
public class LoginCredentialsChangeFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> {
|
|
||||||
protected static final String ARG_LOGIN_CREDENTIALS = "credentials",
|
|
||||||
ARG_ACCOUNT = "account";
|
|
||||||
private Account account;
|
|
||||||
|
|
||||||
public static LoginCredentialsChangeFragment newInstance(Account account, LoginCredentials credentials) {
|
|
||||||
LoginCredentialsChangeFragment frag = new LoginCredentialsChangeFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putParcelable(ARG_ACCOUNT, account);
|
|
||||||
args.putParcelable(ARG_LOGIN_CREDENTIALS, credentials);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
ProgressDialog progress = new ProgressDialog(getActivity());
|
|
||||||
progress.setTitle(R.string.login_configuration_detection);
|
|
||||||
progress.setMessage(getString(R.string.login_querying_server));
|
|
||||||
progress.setIndeterminate(true);
|
|
||||||
progress.setCanceledOnTouchOutside(false);
|
|
||||||
setCancelable(false);
|
|
||||||
return progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, getArguments(), this);
|
|
||||||
account = getArguments().getParcelable(ARG_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Configuration> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new ServerConfigurationLoader(getContext(), (LoginCredentials) args.getParcelable(ARG_LOGIN_CREDENTIALS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Configuration> loader, Configuration data) {
|
|
||||||
if (data != null) {
|
|
||||||
if (data.isFailed())
|
|
||||||
// no service found: show error message
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.add(NothingDetectedFragment.newInstance(data.logs), null)
|
|
||||||
.commitAllowingStateLoss();
|
|
||||||
else {
|
|
||||||
final AccountSettings settings;
|
|
||||||
|
|
||||||
try {
|
|
||||||
settings = new AccountSettings(getActivity(), account);
|
|
||||||
} catch (InvalidAccountException e) {
|
|
||||||
App.log.log(Level.INFO, "Account is invalid or doesn't exist (anymore)", e);
|
|
||||||
getActivity().finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settings.setAuthToken(data.authtoken);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
App.log.severe("Configuration detection failed");
|
|
||||||
|
|
||||||
dismissAllowingStateLoss();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Configuration> loader) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class NothingDetectedFragment extends DialogFragment {
|
|
||||||
private static String KEY_LOGS = "logs";
|
|
||||||
|
|
||||||
public static NothingDetectedFragment newInstance(String logs) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString(KEY_LOGS, logs);
|
|
||||||
NothingDetectedFragment fragment = new NothingDetectedFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
return new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.login_configuration_detection)
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setMessage(R.string.login_wrong_username_or_password)
|
|
||||||
.setNeutralButton(R.string.login_view_logs, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
Intent intent = new Intent(getActivity(), DebugInfoActivity.class);
|
|
||||||
intent.putExtra(DebugInfoActivity.KEY_LOGS, getArguments().getString(KEY_LOGS));
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
// dismiss
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> {
|
|
||||||
final Context context;
|
|
||||||
final LoginCredentials credentials;
|
|
||||||
|
|
||||||
public ServerConfigurationLoader(Context context, LoginCredentials credentials) {
|
|
||||||
super(context);
|
|
||||||
this.context = context;
|
|
||||||
this.credentials = credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStartLoading() {
|
|
||||||
forceLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Configuration loadInBackground() {
|
|
||||||
return new BaseConfigurationFinder(context, credentials).findInitialConfiguration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v4.app.LoaderManager
|
||||||
|
import android.support.v4.content.AsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import com.etesync.syncadapter.AccountSettings
|
||||||
|
import com.etesync.syncadapter.App
|
||||||
|
import com.etesync.syncadapter.InvalidAccountException
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.DebugInfoActivity
|
||||||
|
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
class LoginCredentialsChangeFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Configuration> {
|
||||||
|
private var account: Account? = null
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val progress = ProgressDialog(activity)
|
||||||
|
progress.setTitle(R.string.login_configuration_detection)
|
||||||
|
progress.setMessage(getString(R.string.login_querying_server))
|
||||||
|
progress.isIndeterminate = true
|
||||||
|
progress.setCanceledOnTouchOutside(false)
|
||||||
|
isCancelable = false
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
loaderManager.initLoader(0, arguments, this)
|
||||||
|
account = arguments!!.getParcelable(ARG_ACCOUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Configuration> {
|
||||||
|
return ServerConfigurationLoader(context!!, args!!.getParcelable(ARG_LOGIN_CREDENTIALS) as LoginCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<Configuration>, data: Configuration?) {
|
||||||
|
if (data != null) {
|
||||||
|
if (data.isFailed)
|
||||||
|
// no service found: show error message
|
||||||
|
fragmentManager!!.beginTransaction()
|
||||||
|
.add(NothingDetectedFragment.newInstance(data.logs), null)
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
else {
|
||||||
|
val settings: AccountSettings
|
||||||
|
|
||||||
|
try {
|
||||||
|
settings = AccountSettings(activity!!, account!!)
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
App.log.log(Level.INFO, "Account is invalid or doesn't exist (anymore)", e)
|
||||||
|
activity!!.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.setAuthToken(data.authtoken!!)
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
App.log.severe("Configuration detection failed")
|
||||||
|
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<Configuration>) {}
|
||||||
|
|
||||||
|
|
||||||
|
class NothingDetectedFragment : DialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
return AlertDialog.Builder(activity!!)
|
||||||
|
.setTitle(R.string.login_configuration_detection)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setMessage(R.string.login_wrong_username_or_password)
|
||||||
|
.setNeutralButton(R.string.login_view_logs) { dialog, which ->
|
||||||
|
val intent = Intent(activity, DebugInfoActivity::class.java)
|
||||||
|
intent.putExtra(DebugInfoActivity.KEY_LOGS, arguments!!.getString(KEY_LOGS))
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||||
|
// dismiss
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val KEY_LOGS = "logs"
|
||||||
|
|
||||||
|
fun newInstance(logs: String): NothingDetectedFragment {
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString(KEY_LOGS, logs)
|
||||||
|
val fragment = NothingDetectedFragment()
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ServerConfigurationLoader(context: Context, val credentials: LoginCredentials) : AsyncTaskLoader<Configuration>(context) {
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadInBackground(): Configuration? {
|
||||||
|
return BaseConfigurationFinder(context, credentials).findInitialConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected val ARG_LOGIN_CREDENTIALS = "credentials"
|
||||||
|
protected val ARG_ACCOUNT = "account"
|
||||||
|
|
||||||
|
fun newInstance(account: Account, credentials: LoginCredentials): LoginCredentialsChangeFragment {
|
||||||
|
val frag = LoginCredentialsChangeFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putParcelable(ARG_ACCOUNT, account)
|
||||||
|
args.putParcelable(ARG_LOGIN_CREDENTIALS, credentials)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,144 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckedTextView;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.Constants;
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
import com.etesync.syncadapter.ui.WebViewActivity;
|
|
||||||
import com.etesync.syncadapter.ui.widget.EditPassword;
|
|
||||||
|
|
||||||
import net.cachapa.expandablelayout.ExpandableLayout;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
|
|
||||||
public class LoginCredentialsFragment extends Fragment {
|
|
||||||
EditText editUserName;
|
|
||||||
EditPassword editUrlPassword;
|
|
||||||
|
|
||||||
CheckedTextView showAdvanced;
|
|
||||||
EditText customServer;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View v = inflater.inflate(R.layout.login_credentials_fragment, container, false);
|
|
||||||
|
|
||||||
editUserName = (EditText) v.findViewById(R.id.user_name);
|
|
||||||
editUrlPassword = (EditPassword) v.findViewById(R.id.url_password);
|
|
||||||
showAdvanced = (CheckedTextView) v.findViewById(R.id.show_advanced);
|
|
||||||
customServer = (EditText) v.findViewById(R.id.custom_server);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
Activity activity = getActivity();
|
|
||||||
Intent intent = (activity != null) ? activity.getIntent() : null;
|
|
||||||
if (intent != null) {
|
|
||||||
// we've got initial login data
|
|
||||||
String username = intent.getStringExtra(LoginActivity.EXTRA_USERNAME),
|
|
||||||
password = intent.getStringExtra(LoginActivity.EXTRA_PASSWORD);
|
|
||||||
|
|
||||||
editUserName.setText(username);
|
|
||||||
editUrlPassword.setText(password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Button createAccount = (Button) v.findViewById(R.id.create_account);
|
|
||||||
createAccount.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
Uri createUri = Constants.registrationUrl.buildUpon().appendQueryParameter("email", editUserName.getText().toString()).build();
|
|
||||||
WebViewActivity.openUrl(getContext(), createUri);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final Button login = (Button) v.findViewById(R.id.login);
|
|
||||||
login.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
LoginCredentials credentials = validateLoginData();
|
|
||||||
if (credentials != null)
|
|
||||||
DetectConfigurationFragment.newInstance(credentials).show(getFragmentManager(), null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final TextView forgotPassword = (TextView) v.findViewById(R.id.forgot_password);
|
|
||||||
forgotPassword.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
WebViewActivity.openUrl(getContext(), Constants.forgotPassword);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final ExpandableLayout advancedLayout = (ExpandableLayout) v.findViewById(R.id.advanced_layout);
|
|
||||||
|
|
||||||
showAdvanced.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
|
|
||||||
if (showAdvanced.isChecked()) {
|
|
||||||
showAdvanced.setChecked(false);
|
|
||||||
advancedLayout.collapse();
|
|
||||||
} else {
|
|
||||||
showAdvanced.setChecked(true);
|
|
||||||
advancedLayout.expand();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LoginCredentials validateLoginData() {
|
|
||||||
boolean valid = true;
|
|
||||||
|
|
||||||
String userName = editUserName.getText().toString();
|
|
||||||
if (userName.isEmpty()) {
|
|
||||||
editUserName.setError(getString(R.string.login_email_address_error));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String password = editUrlPassword.getText().toString();
|
|
||||||
if (password.isEmpty()) {
|
|
||||||
editUrlPassword.setError(getString(R.string.login_password_required));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
URI uri = null;
|
|
||||||
if (showAdvanced.isChecked()) {
|
|
||||||
String server = customServer.getText().toString();
|
|
||||||
// If this field is null, just use the default
|
|
||||||
if (!server.isEmpty()) {
|
|
||||||
HttpUrl url = HttpUrl.parse(server);
|
|
||||||
if (url != null) {
|
|
||||||
uri = url.uri();
|
|
||||||
} else {
|
|
||||||
customServer.setError(getString(R.string.login_custom_server_error));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return valid ? new LoginCredentials(uri, userName, password) : null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.CheckedTextView
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.etesync.syncadapter.Constants
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
import com.etesync.syncadapter.ui.WebViewActivity
|
||||||
|
import com.etesync.syncadapter.ui.widget.EditPassword
|
||||||
|
import net.cachapa.expandablelayout.ExpandableLayout
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class LoginCredentialsFragment : Fragment() {
|
||||||
|
internal lateinit var editUserName: EditText
|
||||||
|
internal lateinit var editUrlPassword: EditPassword
|
||||||
|
|
||||||
|
internal lateinit var showAdvanced: CheckedTextView
|
||||||
|
internal lateinit var customServer: EditText
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val v = inflater.inflate(R.layout.login_credentials_fragment, container, false)
|
||||||
|
|
||||||
|
editUserName = v.findViewById<View>(R.id.user_name) as EditText
|
||||||
|
editUrlPassword = v.findViewById<View>(R.id.url_password) as EditPassword
|
||||||
|
showAdvanced = v.findViewById<View>(R.id.show_advanced) as CheckedTextView
|
||||||
|
customServer = v.findViewById<View>(R.id.custom_server) as EditText
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val activity = activity
|
||||||
|
val intent = activity?.intent
|
||||||
|
if (intent != null) {
|
||||||
|
// we've got initial login data
|
||||||
|
val username = intent.getStringExtra(LoginActivity.EXTRA_USERNAME)
|
||||||
|
val password = intent.getStringExtra(LoginActivity.EXTRA_PASSWORD)
|
||||||
|
|
||||||
|
editUserName.setText(username)
|
||||||
|
editUrlPassword.setText(password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val createAccount = v.findViewById<View>(R.id.create_account) as Button
|
||||||
|
createAccount.setOnClickListener {
|
||||||
|
val createUri = Constants.registrationUrl.buildUpon().appendQueryParameter("email", editUserName.text.toString()).build()
|
||||||
|
WebViewActivity.openUrl(context!!, createUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
val login = v.findViewById<View>(R.id.login) as Button
|
||||||
|
login.setOnClickListener {
|
||||||
|
val credentials = validateLoginData()
|
||||||
|
if (credentials != null)
|
||||||
|
DetectConfigurationFragment.newInstance(credentials).show(fragmentManager!!, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val forgotPassword = v.findViewById<View>(R.id.forgot_password) as TextView
|
||||||
|
forgotPassword.setOnClickListener { WebViewActivity.openUrl(context!!, Constants.forgotPassword) }
|
||||||
|
|
||||||
|
val advancedLayout = v.findViewById<View>(R.id.advanced_layout) as ExpandableLayout
|
||||||
|
|
||||||
|
showAdvanced.setOnClickListener {
|
||||||
|
if (showAdvanced.isChecked) {
|
||||||
|
showAdvanced.isChecked = false
|
||||||
|
advancedLayout.collapse()
|
||||||
|
} else {
|
||||||
|
showAdvanced.isChecked = true
|
||||||
|
advancedLayout.expand()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun validateLoginData(): LoginCredentials? {
|
||||||
|
var valid = true
|
||||||
|
|
||||||
|
val userName = editUserName.text.toString()
|
||||||
|
if (userName.isEmpty()) {
|
||||||
|
editUserName.error = getString(R.string.login_email_address_error)
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val password = editUrlPassword.text.toString()
|
||||||
|
if (password.isEmpty()) {
|
||||||
|
editUrlPassword.setError(getString(R.string.login_password_required))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri: URI? = null
|
||||||
|
if (showAdvanced.isChecked) {
|
||||||
|
val server = customServer.text.toString()
|
||||||
|
// If this field is null, just use the default
|
||||||
|
if (!server.isEmpty()) {
|
||||||
|
val url = HttpUrl.parse(server)
|
||||||
|
if (url != null) {
|
||||||
|
uri = url.uri()
|
||||||
|
} else {
|
||||||
|
customServer.error = getString(R.string.login_custom_server_error)
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (valid) LoginCredentials(uri, userName, password) else null
|
||||||
|
}
|
||||||
|
}
|
@ -1,231 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.setup;
|
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
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.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.Exceptions;
|
|
||||||
import com.etesync.syncadapter.journalmanager.UserInfoManager;
|
|
||||||
import com.etesync.syncadapter.model.CollectionInfo;
|
|
||||||
import com.etesync.syncadapter.model.JournalEntity;
|
|
||||||
import com.etesync.syncadapter.model.ServiceEntity;
|
|
||||||
import com.etesync.syncadapter.resource.LocalTaskList;
|
|
||||||
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration;
|
|
||||||
import com.etesync.syncadapter.utils.AndroidCompat;
|
|
||||||
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import at.bitfire.ical4android.TaskProvider;
|
|
||||||
import io.requery.Persistable;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
public class SetupEncryptionFragment extends DialogFragment {
|
|
||||||
private static final String KEY_CONFIG = "config";
|
|
||||||
|
|
||||||
public static SetupEncryptionFragment newInstance(BaseConfigurationFinder.Configuration config) {
|
|
||||||
SetupEncryptionFragment frag = new SetupEncryptionFragment();
|
|
||||||
Bundle args = new Bundle(1);
|
|
||||||
args.putSerializable(KEY_CONFIG, config);
|
|
||||||
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);
|
|
||||||
|
|
||||||
new SetupEncryptionLoader(getContext(), (Configuration) getArguments().getSerializable(KEY_CONFIG)).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SetupEncryptionLoader extends AsyncTask<Void, Void, Configuration> {
|
|
||||||
final Context context;
|
|
||||||
final Configuration config;
|
|
||||||
|
|
||||||
public SetupEncryptionLoader(Context context, Configuration config) {
|
|
||||||
super();
|
|
||||||
this.context = context;
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Configuration result) {
|
|
||||||
if ((config.error != null) && (config.error instanceof Exceptions.IntegrityException)) {
|
|
||||||
App.log.severe("Wrong encryption password.");
|
|
||||||
new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.wrong_encryption_password)
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setMessage(getString(R.string.wrong_encryption_password_content, config.error.getLocalizedMessage()))
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
// dismiss
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (createAccount(config.userName, config)) {
|
|
||||||
getActivity().setResult(Activity.RESULT_OK);
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
|
||||||
} catch (InvalidAccountException e) {
|
|
||||||
App.log.severe("Account creation failed!");
|
|
||||||
new AlertDialog.Builder(getActivity())
|
|
||||||
.setTitle(R.string.account_creation_failed)
|
|
||||||
.setIcon(R.drawable.ic_error_dark)
|
|
||||||
.setMessage(e.getLocalizedMessage())
|
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
// dismiss
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dismissAllowingStateLoss();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Configuration doInBackground(Void... aVoids) {
|
|
||||||
App.log.info("Started deriving key");
|
|
||||||
config.password = Crypto.deriveKey(config.userName, config.rawPassword);
|
|
||||||
App.log.info("Finished deriving key");
|
|
||||||
config.error = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Crypto.CryptoManager cryptoManager;
|
|
||||||
OkHttpClient httpClient = HttpClient.create(getContext(), config.url, config.authtoken);
|
|
||||||
|
|
||||||
UserInfoManager userInfoManager = new UserInfoManager(httpClient, HttpUrl.get(config.url));
|
|
||||||
UserInfoManager.UserInfo userInfo = userInfoManager.get(config.userName);
|
|
||||||
if (userInfo != null) {
|
|
||||||
App.log.info("Fetched userInfo for " + config.userName);
|
|
||||||
cryptoManager = new Crypto.CryptoManager(userInfo.getVersion(), config.password, "userInfo");
|
|
||||||
userInfo.verify(cryptoManager);
|
|
||||||
config.keyPair = new Crypto.AsymmetricKeyPair(userInfo.getContent(cryptoManager), userInfo.getPubkey());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
config.error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected boolean createAccount(String accountName, BaseConfigurationFinder.Configuration config) throws InvalidAccountException {
|
|
||||||
Account account = new Account(accountName, App.getAccountType());
|
|
||||||
|
|
||||||
// create Android account
|
|
||||||
App.log.log(Level.INFO, "Creating Android account with initial config", new Object[] { account, config.userName, config.url });
|
|
||||||
|
|
||||||
AccountManager accountManager = AccountManager.get(getContext());
|
|
||||||
if (!accountManager.addAccountExplicitly(account, config.password, null))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
AccountSettings.setUserData(accountManager, account, config.url, config.userName);
|
|
||||||
|
|
||||||
// add entries for account to service DB
|
|
||||||
App.log.log(Level.INFO, "Writing account configuration to database", config);
|
|
||||||
try {
|
|
||||||
AccountSettings settings = new AccountSettings(getContext(), account);
|
|
||||||
|
|
||||||
settings.setAuthToken(config.authtoken);
|
|
||||||
if (config.keyPair != null) {
|
|
||||||
settings.setKeyPair(config.keyPair);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.cardDAV != null) {
|
|
||||||
// insert CardDAV service
|
|
||||||
insertService(accountName, CollectionInfo.Type.ADDRESS_BOOK, config.cardDAV);
|
|
||||||
|
|
||||||
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
|
|
||||||
settings.setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL);
|
|
||||||
} else {
|
|
||||||
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.calDAV != null) {
|
|
||||||
// insert CalDAV service
|
|
||||||
insertService(accountName, CollectionInfo.Type.CALENDAR, config.calDAV);
|
|
||||||
|
|
||||||
// calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
|
|
||||||
settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL);
|
|
||||||
|
|
||||||
// enable task sync if OpenTasks is installed
|
|
||||||
// further changes will be handled by PackageChangedReceiver
|
|
||||||
if (LocalTaskList.tasksProviderAvailable(getContext())) {
|
|
||||||
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
|
|
||||||
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch(InvalidAccountException e) {
|
|
||||||
App.log.log(Level.SEVERE, "Couldn't access account settings", e);
|
|
||||||
AndroidCompat.removeAccount(accountManager, account);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void insertService(String accountName, CollectionInfo.Type serviceType, BaseConfigurationFinder.Configuration.ServiceInfo info) {
|
|
||||||
EntityDataStore<Persistable> data = ((App) getContext().getApplicationContext()).getData();
|
|
||||||
|
|
||||||
// insert service
|
|
||||||
ServiceEntity serviceEntity = new ServiceEntity();
|
|
||||||
serviceEntity.setAccount(accountName);
|
|
||||||
serviceEntity.setType(serviceType);
|
|
||||||
data.upsert(serviceEntity);
|
|
||||||
|
|
||||||
// insert collections
|
|
||||||
for (CollectionInfo collection : info.collections.values()) {
|
|
||||||
collection.serviceID = serviceEntity.getId();
|
|
||||||
JournalEntity journalEntity = new JournalEntity(data, collection);
|
|
||||||
data.insert(journalEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.setup
|
||||||
|
|
||||||
|
import android.accounts.Account
|
||||||
|
import android.accounts.AccountManager
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.support.v4.app.DialogFragment
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import at.bitfire.ical4android.TaskProvider
|
||||||
|
import com.etesync.syncadapter.*
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto
|
||||||
|
import com.etesync.syncadapter.journalmanager.Exceptions
|
||||||
|
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
import com.etesync.syncadapter.model.ServiceEntity
|
||||||
|
import com.etesync.syncadapter.resource.LocalTaskList
|
||||||
|
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
|
||||||
|
import com.etesync.syncadapter.utils.AndroidCompat
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
class SetupEncryptionFragment : DialogFragment() {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
SetupEncryptionLoader(context!!, arguments!!.getSerializable(KEY_CONFIG) as Configuration).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class SetupEncryptionLoader(internal val context: Context, internal val config: Configuration) : AsyncTask<Void, Void, Configuration>() {
|
||||||
|
|
||||||
|
override fun onPostExecute(result: Configuration) {
|
||||||
|
if (config.error != null && config.error is Exceptions.IntegrityException) {
|
||||||
|
App.log.severe("Wrong encryption password.")
|
||||||
|
AlertDialog.Builder(activity!!)
|
||||||
|
.setTitle(R.string.wrong_encryption_password)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setMessage(getString(R.string.wrong_encryption_password_content, config.error!!.localizedMessage))
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||||
|
// dismiss
|
||||||
|
}.show()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (createAccount(config.userName, config)) {
|
||||||
|
activity!!.setResult(Activity.RESULT_OK)
|
||||||
|
activity!!.finish()
|
||||||
|
}
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
App.log.severe("Account creation failed!")
|
||||||
|
AlertDialog.Builder(activity!!)
|
||||||
|
.setTitle(R.string.account_creation_failed)
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setMessage(e.localizedMessage)
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||||
|
// dismiss
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doInBackground(vararg aVoids: Void): Configuration {
|
||||||
|
App.log.info("Started deriving key")
|
||||||
|
config.password = Crypto.deriveKey(config.userName, config.rawPassword!!)
|
||||||
|
App.log.info("Finished deriving key")
|
||||||
|
config.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
val cryptoManager: Crypto.CryptoManager
|
||||||
|
val httpClient = HttpClient.create(getContext(), config.url, config.authtoken)
|
||||||
|
|
||||||
|
val userInfoManager = UserInfoManager(httpClient, HttpUrl.get(config.url)!!)
|
||||||
|
val userInfo = userInfoManager[config.userName]
|
||||||
|
if (userInfo != null) {
|
||||||
|
App.log.info("Fetched userInfo for " + config.userName)
|
||||||
|
cryptoManager = Crypto.CryptoManager(userInfo.version!!.toInt(), config.password!!, "userInfo")
|
||||||
|
userInfo.verify(cryptoManager)
|
||||||
|
config.keyPair = Crypto.AsymmetricKeyPair(userInfo.getContent(cryptoManager)!!, userInfo.pubkey!!)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
config.error = e
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Throws(InvalidAccountException::class)
|
||||||
|
protected fun createAccount(accountName: String, config: BaseConfigurationFinder.Configuration): Boolean {
|
||||||
|
val account = Account(accountName, App.getAccountType())
|
||||||
|
|
||||||
|
// create Android account
|
||||||
|
App.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, config.userName, config.url))
|
||||||
|
|
||||||
|
val accountManager = AccountManager.get(context)
|
||||||
|
if (!accountManager.addAccountExplicitly(account, config.password, null))
|
||||||
|
return false
|
||||||
|
|
||||||
|
AccountSettings.setUserData(accountManager, account, config.url, config.userName)
|
||||||
|
|
||||||
|
// add entries for account to service DB
|
||||||
|
App.log.log(Level.INFO, "Writing account configuration to database", config)
|
||||||
|
try {
|
||||||
|
val settings = AccountSettings(context!!, account)
|
||||||
|
|
||||||
|
settings.authToken = config.authtoken
|
||||||
|
if (config.keyPair != null) {
|
||||||
|
settings.keyPair = config.keyPair!!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.cardDAV != null) {
|
||||||
|
// insert CardDAV service
|
||||||
|
insertService(accountName, CollectionInfo.Type.ADDRESS_BOOK, config.cardDAV)
|
||||||
|
|
||||||
|
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
|
||||||
|
settings.setSyncInterval(App.getAddressBooksAuthority(), Constants.DEFAULT_SYNC_INTERVAL.toLong())
|
||||||
|
} else {
|
||||||
|
ContentResolver.setIsSyncable(account, App.getAddressBooksAuthority(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.calDAV != null) {
|
||||||
|
// insert CalDAV service
|
||||||
|
insertService(accountName, CollectionInfo.Type.CALENDAR, config.calDAV)
|
||||||
|
|
||||||
|
// calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
|
||||||
|
settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL.toLong())
|
||||||
|
|
||||||
|
// enable task sync if OpenTasks is installed
|
||||||
|
// further changes will be handled by PackageChangedReceiver
|
||||||
|
if (LocalTaskList.tasksProviderAvailable(context!!)) {
|
||||||
|
ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1)
|
||||||
|
settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL.toLong())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: InvalidAccountException) {
|
||||||
|
App.log.log(Level.SEVERE, "Couldn't access account settings", e)
|
||||||
|
AndroidCompat.removeAccount(accountManager, account)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun insertService(accountName: String, serviceType: CollectionInfo.Type, info: BaseConfigurationFinder.Configuration.ServiceInfo) {
|
||||||
|
val data = (context!!.applicationContext as App).data
|
||||||
|
|
||||||
|
// insert service
|
||||||
|
val serviceEntity = ServiceEntity()
|
||||||
|
serviceEntity.account = accountName
|
||||||
|
serviceEntity.type = serviceType
|
||||||
|
data.upsert(serviceEntity)
|
||||||
|
|
||||||
|
// insert collections
|
||||||
|
for (collection in info.collections.values) {
|
||||||
|
collection.serviceID = serviceEntity.id
|
||||||
|
val journalEntity = JournalEntity(data, collection)
|
||||||
|
data.insert(journalEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val KEY_CONFIG = "config"
|
||||||
|
|
||||||
|
fun newInstance(config: BaseConfigurationFinder.Configuration): SetupEncryptionFragment {
|
||||||
|
val frag = SetupEncryptionFragment()
|
||||||
|
val args = Bundle(1)
|
||||||
|
args.putSerializable(KEY_CONFIG, config)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.widget;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.R;
|
|
||||||
|
|
||||||
public class EditPassword extends LinearLayout {
|
|
||||||
private static final String NS_ANDROID = "http://schemas.android.com/apk/res/android";
|
|
||||||
|
|
||||||
EditText editPassword;
|
|
||||||
|
|
||||||
public EditPassword(Context context) {
|
|
||||||
super(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public EditPassword(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
|
|
||||||
inflate(context, R.layout.edit_password, this);
|
|
||||||
|
|
||||||
editPassword = (EditText)findViewById(R.id.password);
|
|
||||||
editPassword.setHint(attrs.getAttributeResourceValue(NS_ANDROID, "hint", 0));
|
|
||||||
editPassword.setText(attrs.getAttributeValue(NS_ANDROID, "text"));
|
|
||||||
|
|
||||||
CheckBox checkShowPassword = (CheckBox)findViewById(R.id.show_password);
|
|
||||||
checkShowPassword.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
|
||||||
int inputType = editPassword.getInputType() & ~EditorInfo.TYPE_MASK_VARIATION;
|
|
||||||
inputType |= isChecked ? EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
|
|
||||||
editPassword.setInputType(inputType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Editable getText() {
|
|
||||||
return editPassword.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setError(CharSequence error) {
|
|
||||||
editPassword.setError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setText(CharSequence text) {
|
|
||||||
editPassword.setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.Editable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import com.etesync.syncadapter.R
|
||||||
|
|
||||||
|
class EditPassword : LinearLayout {
|
||||||
|
|
||||||
|
internal var editPassword: EditText
|
||||||
|
|
||||||
|
val text: Editable
|
||||||
|
get() = editPassword.text
|
||||||
|
|
||||||
|
init {
|
||||||
|
View.inflate(context, R.layout.edit_password, this)
|
||||||
|
|
||||||
|
editPassword = findViewById<View>(R.id.password) as EditText
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||||
|
editPassword.setHint(attrs.getAttributeResourceValue(NS_ANDROID, "hint", 0))
|
||||||
|
editPassword.setText(attrs.getAttributeValue(NS_ANDROID, "text"))
|
||||||
|
|
||||||
|
val checkShowPassword = findViewById<View>(R.id.show_password) as CheckBox
|
||||||
|
checkShowPassword.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||||
|
var inputType = editPassword.inputType and EditorInfo.TYPE_MASK_VARIATION.inv()
|
||||||
|
inputType = inputType or if (isChecked) EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD else EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
|
editPassword.inputType = inputType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setError(error: CharSequence) {
|
||||||
|
editPassword.error = error
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setText(text: CharSequence) {
|
||||||
|
editPassword.setText(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val NS_ANDROID = "http://schemas.android.com/apk/res/android"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.ui.widget;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ListAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
|
|
||||||
public class MaximizedListView extends ListView {
|
|
||||||
|
|
||||||
public MaximizedListView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
|
|
||||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec),
|
|
||||||
widthSize = MeasureSpec.getSize(widthMeasureSpec),
|
|
||||||
heightMode = MeasureSpec.getMode(heightMeasureSpec),
|
|
||||||
heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
|
||||||
|
|
||||||
int width = 0, height = 0;
|
|
||||||
|
|
||||||
if (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST)
|
|
||||||
width = widthSize;
|
|
||||||
|
|
||||||
if (heightMode == MeasureSpec.EXACTLY)
|
|
||||||
height = heightSize;
|
|
||||||
else {
|
|
||||||
ListAdapter listAdapter = getAdapter();
|
|
||||||
if (listAdapter != null) {
|
|
||||||
int widthSpec = View.MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
|
||||||
for (int i = 0; i < listAdapter.getCount(); i++) {
|
|
||||||
View listItem = listAdapter.getView(i, null, this);
|
|
||||||
listItem.measure(widthSpec, View.MeasureSpec.UNSPECIFIED);
|
|
||||||
height += listItem.getMeasuredHeight();
|
|
||||||
}
|
|
||||||
height += getDividerHeight() * (listAdapter.getCount() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setMeasuredDimension(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the GNU Public License v3.0
|
||||||
|
* which accompanies this distribution, and is available at
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.etesync.syncadapter.ui.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ListView
|
||||||
|
|
||||||
|
class MaximizedListView(context: Context, attrs: AttributeSet) : ListView(context, attrs) {
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
|
val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
|
||||||
|
val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
|
||||||
|
val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
|
||||||
|
|
||||||
|
var width = 0
|
||||||
|
var height = 0
|
||||||
|
|
||||||
|
if (widthMode == View.MeasureSpec.EXACTLY || widthMode == View.MeasureSpec.AT_MOST)
|
||||||
|
width = widthSize
|
||||||
|
|
||||||
|
if (heightMode == View.MeasureSpec.EXACTLY)
|
||||||
|
height = heightSize
|
||||||
|
else {
|
||||||
|
val listAdapter = adapter
|
||||||
|
if (listAdapter != null) {
|
||||||
|
val widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
|
||||||
|
for (i in 0 until listAdapter.count) {
|
||||||
|
val listItem = listAdapter.getView(i, null, this)
|
||||||
|
listItem.measure(widthSpec, View.MeasureSpec.UNSPECIFIED)
|
||||||
|
height += listItem.measuredHeight
|
||||||
|
}
|
||||||
|
height += dividerHeight * (listAdapter.count - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeasuredDimension(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user