/* * 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 at.bitfire.davdroid.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.AlertDialog; import android.app.LoaderManager; import android.content.AsyncTaskLoader; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; import android.content.ServiceConnection; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.menu.MenuBuilder; import android.support.v7.view.menu.MenuPresenter; 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.ArrayAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import java.io.IOException; import java.util.LinkedList; import java.util.List; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.DavService; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.CollectionInfo; import at.bitfire.davdroid.model.ServiceDB.Collections; import at.bitfire.davdroid.model.ServiceDB.OpenHelper; import at.bitfire.davdroid.model.ServiceDB.Services; import lombok.Cleanup; public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenuItemClickListener, LoaderManager.LoaderCallbacks { public static final String EXTRA_ACCOUNT_NAME = "account_name"; private String accountName; private AccountInfo accountInfo; Toolbar tbCardDAV, tbCalDAV; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); accountName = getIntent().getStringExtra(EXTRA_ACCOUNT_NAME); if (accountName == null) // invalid account name finish(); setTitle(accountName); setContentView(R.layout.activity_account); // CardDAV toolbar tbCardDAV = (Toolbar)findViewById(R.id.carddav_menu); tbCardDAV.inflateMenu(R.menu.carddav_actions); tbCardDAV.setOnMenuItemClickListener(this); // CalDAV toolbar tbCalDAV = (Toolbar)findViewById(R.id.caldav_menu); tbCalDAV.inflateMenu(R.menu.caldav_actions); tbCalDAV.setOnMenuItemClickListener(this); // load CardDAV/CalDAV collections getLoaderManager().initLoader(0, getIntent().getExtras(), this); } @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.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; default: return super.onOptionsItemSelected(item); } return true; } @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.refresh_address_books: Intent intent = new Intent(this, DavService.class); intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS); intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.carddav.id); startService(intent); break; case R.id.create_address_book: // TODO break; case R.id.refresh_calendars: intent = new Intent(this, DavService.class); intent.setAction(DavService.ACTION_REFRESH_COLLECTIONS); intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.caldav.id); startService(intent); break; } return false; } /* LOADERS AND LOADED DATA */ public static class AccountInfo { ServiceInfo carddav, caldav; public static class ServiceInfo { long id; boolean refreshing; List collections; } } @Override public Loader onCreateLoader(int id, Bundle args) { return new AccountLoader(this, args.getString(EXTRA_ACCOUNT_NAME)); } @Override public void onLoadFinished(Loader 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); ListView list = (ListView)findViewById(R.id.address_books); AddressBookAdapter adapter = new AddressBookAdapter(this); adapter.addAll(info.carddav.collections); list.setAdapter(adapter); } 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); ListView list = (ListView)findViewById(R.id.calendars); CalendarAdapter adapter = new CalendarAdapter(this); adapter.addAll(info.caldav.collections); list.setAdapter(adapter); } else card.setVisibility(View.GONE); } @Override public void onLoaderReset(Loader loader) { } private static class AccountLoader extends AsyncTaskLoader implements DavService.RefreshingStatusListener, ServiceConnection { private final String accountName; private final OpenHelper dbHelper; private DavService.InfoBinder davService; public AccountLoader(Context context, String accountName) { super(context); this.accountName = accountName; dbHelper = new OpenHelper(context); } @Override protected void onStartLoading() { getContext().bindService(new Intent(getContext(), DavService.class), this, Context.BIND_AUTO_CREATE); } @Override protected void onStopLoading() { davService.removeRefreshingStatusListener(this); getContext().unbindService(this); } @Override public void onServiceConnected(ComponentName name, IBinder service) { davService = (DavService.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 AccountInfo loadInBackground() { AccountInfo info = new AccountInfo(); try { SQLiteDatabase db = dbHelper.getReadableDatabase(); @Cleanup Cursor cursor = db.query( Services._TABLE, new String[] { Services.ID, Services.SERVICE }, Services.ACCOUNT_NAME + "=?", new String[] { accountName }, null, null, null); while (cursor.moveToNext()) { long id = cursor.getLong(0); String service = cursor.getString(1); if (Services.SERVICE_CARDDAV.equals(service)) { info.carddav = new AccountInfo.ServiceInfo(); info.carddav.id = id; info.carddav.refreshing = davService.isRefreshing(id); info.carddav.collections = readCollections(db, id); } else if (Services.SERVICE_CALDAV.equals(service)) { info.caldav = new AccountInfo.ServiceInfo(); info.caldav.id = id; info.caldav.refreshing = davService.isRefreshing(id); info.caldav.collections = readCollections(db, id); } } } finally { dbHelper.close(); } return info; } private List readCollections(SQLiteDatabase db, long service) { List collections = new LinkedList<>(); @Cleanup Cursor cursor = db.query(Collections._TABLE, Collections._COLUMNS, Collections.SERVICE_ID + "=?", new String[]{String.valueOf(service)}, null, null, null); while (cursor.moveToNext()) { ContentValues values = new ContentValues(); DatabaseUtils.cursorRowToContentValues(cursor, values); collections.add(CollectionInfo.fromDB(values)); } return collections; } } /* LIST ADAPTERS */ public static class AddressBookAdapter extends ArrayAdapter { public AddressBookAdapter(Context context) { super(context, R.layout.account_address_book_item); } @Override public View getView(int position, View v, ViewGroup parent) { if (v == null) v = LayoutInflater.from(getContext()).inflate(R.layout.account_address_book_item, parent, false); CollectionInfo info = getItem(position); TextView tv = (TextView)v.findViewById(R.id.title); tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : 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); } return v; } } public static class CalendarAdapter extends ArrayAdapter { public CalendarAdapter(Context context) { super(context, R.layout.account_calendar_item); } @Override public View getView(int position, View v, ViewGroup parent) { if (v == null) v = LayoutInflater.from(getContext()).inflate(R.layout.account_calendar_item, parent, false); CollectionInfo info = getItem(position); View vColor = v.findViewById(R.id.color); vColor.setBackgroundColor(info.color); TextView tv = (TextView)v.findViewById(R.id.title); tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : 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); } tv = (TextView)v.findViewById(R.id.events); tv.setVisibility(info.supportsVEVENT ? View.VISIBLE : View.GONE); tv = (TextView)v.findViewById(R.id.tasks); tv.setVisibility(info.supportsVTODO ? View.VISIBLE : View.GONE); return v; } } /* USER ACTIONS */ protected void deleteAccount() { Account account = new Account(accountName, Constants.ACCOUNT_TYPE); AccountManager accountManager = AccountManager.get(this); if (Build.VERSION.SDK_INT >= 22) accountManager.removeAccount(account, this, new AccountManagerCallback() { @Override public void run(AccountManagerFuture future) { try { if (future.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) finish(); } catch(OperationCanceledException|IOException|AuthenticatorException e) { Constants.log.error("Couldn't remove account", e); } } }, null); else accountManager.removeAccount(account, new AccountManagerCallback() { @Override public void run(AccountManagerFuture future) { try { if (future.getResult()) finish(); } catch (OperationCanceledException|IOException|AuthenticatorException e) { Constants.log.error("Couldn't remove account", e); } } }, null); } }