package com.fsck.k9.activity;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.SearchAccount;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.AccountSetupBasics;
import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.helper.SizeFormatter;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.ColorChip;
import com.fsck.k9.preferences.StorageExporter;
import com.fsck.k9.preferences.StorageImportExportException;
import com.fsck.k9.preferences.StorageImporter;
import com.fsck.k9.preferences.StorageImporter.AccountDescription;
import com.fsck.k9.preferences.StorageImporter.ImportContents;
public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener {
/**
* Immutable empty {@link BaseAccount} array
*/
private static final BaseAccount[] EMPTY_BASE_ACCOUNT_ARRAY = new BaseAccount[0];
/**
* Immutable empty {@link Flag} array
*/
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
private static final int DIALOG_REMOVE_ACCOUNT = 1;
private static final int DIALOG_CLEAR_ACCOUNT = 2;
private static final int DIALOG_RECREATE_ACCOUNT = 3;
private ConcurrentHashMap accountStats = new ConcurrentHashMap();
private ConcurrentHashMap pendingWork = new ConcurrentHashMap();
private BaseAccount mSelectedContextAccount;
private int mUnreadMessageCount = 0;
private AccountsHandler mHandler = new AccountsHandler();
private AccountsAdapter mAdapter;
private SearchAccount unreadAccount = null;
private SearchAccount integratedInboxAccount = null;
private FontSizes mFontSizes = K9.getFontSizes();
private static final int ACTIVITY_REQUEST_PICK_SETTINGS_FILE = 1;
class AccountsHandler extends Handler {
private void setViewTitle() {
String dispString = mListener.formatHeader(Accounts.this, getString(R.string.accounts_title), mUnreadMessageCount, getTimeFormat());
setTitle(dispString);
}
public void refreshTitle() {
runOnUiThread(new Runnable() {
public void run() {
setViewTitle();
}
});
}
public void dataChanged() {
runOnUiThread(new Runnable() {
public void run() {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
});
}
public void workingAccount(final Account account, final int res) {
runOnUiThread(new Runnable() {
public void run() {
String toastText = getString(res, account.getDescription());
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT);
toast.show();
}
});
}
public void accountSizeChanged(final Account account, final long oldSize, final long newSize) {
runOnUiThread(new Runnable() {
public void run() {
AccountStats stats = accountStats.get(account.getUuid());
if (newSize != -1 && stats != null && K9.measureAccounts()) {
stats.size = newSize;
}
String toastText = getString(R.string.account_size_changed, account.getDescription(),
SizeFormatter.formatSize(getApplication(), oldSize), SizeFormatter.formatSize(getApplication(), newSize));
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG);
toast.show();
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
});
}
public void progress(final boolean progress) {
runOnUiThread(new Runnable() {
public void run() {
setProgressBarIndeterminateVisibility(progress);
}
});
}
public void progress(final int progress) {
runOnUiThread(new Runnable() {
public void run() {
getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress);
}
});
}
}
public void setProgress(boolean progress) {
mHandler.progress(progress);
}
ActivityListener mListener = new ActivityListener() {
@Override
public void informUserOfStatus() {
mHandler.refreshTitle();
}
@Override
public void folderStatusChanged(Account account, String folderName, int unreadMessageCount) {
try {
AccountStats stats = account.getStats(Accounts.this);
if (stats == null) {
Log.w(K9.LOG_TAG, "Unable to get account stats");
} else {
accountStatusChanged(account, stats);
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Unable to get account stats", e);
}
}
@Override
public void accountStatusChanged(BaseAccount account, AccountStats stats) {
AccountStats oldStats = accountStats.get(account.getUuid());
int oldUnreadMessageCount = 0;
if (oldStats != null) {
oldUnreadMessageCount = oldStats.unreadMessageCount;
}
if (stats == null) {
stats = new AccountStats(); // empty stats for unavailable accounts
stats.available = false;
}
accountStats.put(account.getUuid(), stats);
if (account instanceof Account) {
mUnreadMessageCount += stats.unreadMessageCount - oldUnreadMessageCount;
}
mHandler.dataChanged();
pendingWork.remove(account);
if (pendingWork.isEmpty()) {
mHandler.progress(Window.PROGRESS_END);
mHandler.refreshTitle();
} else {
int level = (Window.PROGRESS_END / mAdapter.getCount()) * (mAdapter.getCount() - pendingWork.size()) ;
mHandler.progress(level);
}
}
@Override
public void accountSizeChanged(Account account, long oldSize, long newSize) {
mHandler.accountSizeChanged(account, oldSize, newSize);
}
@Override
public void synchronizeMailboxFinished(
Account account,
String folder,
int totalMessagesInMailbox,
int numNewMessages) {
MessagingController.getInstance(getApplication()).getAccountStats(Accounts.this, account, mListener);
super.synchronizeMailboxFinished(account, folder, totalMessagesInMailbox, numNewMessages);
mHandler.progress(false);
}
@Override
public void synchronizeMailboxStarted(Account account, String folder) {
super.synchronizeMailboxStarted(account, folder);
mHandler.progress(true);
}
@Override
public void synchronizeMailboxFailed(Account account, String folder,
String message) {
super.synchronizeMailboxFailed(account, folder, message);
mHandler.progress(false);
}
};
private static String ACCOUNT_STATS = "accountStats";
private static String SELECTED_CONTEXT_ACCOUNT = "selectedContextAccount";
public static final String EXTRA_STARTUP = "startup";
public static void actionLaunch(Context context) {
Intent intent = new Intent(context, Accounts.class);
intent.putExtra(EXTRA_STARTUP, true);
context.startActivity(intent);
}
public static void listAccounts(Context context) {
Intent intent = new Intent(context, Accounts.class);
intent.putExtra(EXTRA_STARTUP, false);
context.startActivity(intent);
}
@Override
public void onNewIntent(Intent intent) {
Uri uri = intent.getData();
Log.i(K9.LOG_TAG, "Accounts Activity got uri " + uri);
if (uri != null) {
ContentResolver contentResolver = getContentResolver();
Log.i(K9.LOG_TAG, "Accounts Activity got content of type " + contentResolver.getType(uri));
String contentType = contentResolver.getType(uri);
if (MimeUtility.K9_SETTINGS_MIME_TYPE.equals(contentType)) {
onImport(uri);
}
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
unreadAccount = new SearchAccount(this, false, null, null);
unreadAccount.setDescription(getString(R.string.search_all_messages_title));
unreadAccount.setEmail(getString(R.string.search_all_messages_detail));
integratedInboxAccount = new SearchAccount(this, true, null, null);
integratedInboxAccount.setDescription(getString(R.string.integrated_inbox_title));
integratedInboxAccount.setEmail(getString(R.string.integrated_inbox_detail));
Account[] accounts = Preferences.getPreferences(this).getAccounts();
Intent intent = getIntent();
boolean startup = intent.getData() == null && intent.getBooleanExtra(EXTRA_STARTUP, true);
onNewIntent(intent);
if (startup && K9.startIntegratedInbox()) {
onOpenAccount(integratedInboxAccount);
finish();
} else if (startup && accounts.length == 1 && onOpenAccount(accounts[0])) {
// fall through to "else" if !onOpenAccount()
finish();
} else {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_PROGRESS);
setContentView(R.layout.accounts);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(false);
listView.setEmptyView(findViewById(R.id.empty));
findViewById(R.id.next).setOnClickListener(this);
registerForContextMenu(listView);
if (icicle != null && icicle.containsKey(SELECTED_CONTEXT_ACCOUNT)) {
String accountUuid = icicle.getString("selectedContextAccount");
mSelectedContextAccount = Preferences.getPreferences(this).getAccount(accountUuid);
}
restoreAccountStats(icicle);
}
}
@SuppressWarnings("unchecked")
private void restoreAccountStats(Bundle icicle) {
if (icicle != null) {
Map oldStats = (Map)icicle.get(ACCOUNT_STATS);
if (oldStats != null) {
accountStats.putAll(oldStats);
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mSelectedContextAccount != null) {
outState.putString(SELECTED_CONTEXT_ACCOUNT, mSelectedContextAccount.getUuid());
}
outState.putSerializable(ACCOUNT_STATS, accountStats);
}
private StorageManager.StorageListener storageListener = new StorageManager.StorageListener() {
@Override
public void onUnmount(String providerId) {
refresh();
}
@Override
public void onMount(String providerId) {
refresh();
}
};
@Override
public void onResume() {
super.onResume();
refresh();
MessagingController.getInstance(getApplication()).addListener(mListener);
StorageManager.getInstance(getApplication()).addListener(storageListener);
}
@Override
public void onPause() {
super.onPause();
MessagingController.getInstance(getApplication()).removeListener(mListener);
StorageManager.getInstance(getApplication()).removeListener(storageListener);
}
private void refresh() {
BaseAccount[] accounts = Preferences.getPreferences(this).getAccounts();
List newAccounts = new ArrayList(accounts.length + 4);
if (accounts.length > 0) {
newAccounts.add(integratedInboxAccount);
newAccounts.add(unreadAccount);
}
newAccounts.addAll(Arrays.asList(accounts));
mAdapter = new AccountsAdapter(newAccounts.toArray(EMPTY_BASE_ACCOUNT_ARRAY));
getListView().setAdapter(mAdapter);
if (newAccounts.size() > 0) {
mHandler.progress(Window.PROGRESS_START);
}
pendingWork.clear();
for (BaseAccount account : newAccounts) {
if (account instanceof Account) {
pendingWork.put(account, "true");
Account realAccount = (Account)account;
MessagingController.getInstance(getApplication()).getAccountStats(Accounts.this, realAccount, mListener);
} else if (K9.countSearchMessages() && account instanceof SearchAccount) {
pendingWork.put(account, "true");
final SearchAccount searchAccount = (SearchAccount)account;
MessagingController.getInstance(getApplication()).searchLocalMessages(searchAccount, null, new MessagingListener() {
@Override
public void searchStats(AccountStats stats) {
mListener.accountStatusChanged(searchAccount, stats);
}
});
}
}
}
private void onAddNewAccount() {
AccountSetupBasics.actionNewAccount(this);
}
private void onEditAccount(Account account) {
AccountSettings.actionSettings(this, account);
}
private void onEditPrefs() {
Prefs.actionPrefs(this);
}
/*
* This method is called with 'null' for the argument 'account' if
* all accounts are to be checked. This is handled accordingly in
* MessagingController.checkMail().
*/
private void onCheckMail(Account account) {
MessagingController.getInstance(getApplication()).checkMail(this, account, true, true, null);
if (account == null) {
MessagingController.getInstance(getApplication()).sendPendingMessages(null);
} else {
MessagingController.getInstance(getApplication()).sendPendingMessages(account, null);
}
}
private void onClearCommands(Account account) {
MessagingController.getInstance(getApplication()).clearAllPending(account);
}
private void onEmptyTrash(Account account) {
MessagingController.getInstance(getApplication()).emptyTrash(account, null);
}
private void onCompose() {
Account defaultAccount = Preferences.getPreferences(this).getDefaultAccount();
if (defaultAccount != null) {
MessageCompose.actionCompose(this, defaultAccount);
} else {
onAddNewAccount();
}
}
/**
* Show that account's inbox or folder-list
* or return false if the account is not available.
* @param account the account to open ({@link SearchAccount} or {@link Account})
* @return false if unsuccessfull
*/
private boolean onOpenAccount(BaseAccount account) {
if (account instanceof SearchAccount) {
SearchAccount searchAccount = (SearchAccount)account;
MessageList.actionHandle(this, searchAccount.getDescription(), searchAccount);
} else {
Account realAccount = (Account)account;
if (!realAccount.isAvailable(this)) {
String toastText = getString(R.string.account_unavailable, account.getDescription());
Toast toast = Toast.makeText(getApplication(), toastText, Toast.LENGTH_SHORT);
toast.show();
Log.i(K9.LOG_TAG, "refusing to open account that is not available");
return false;
}
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
FolderList.actionHandleAccount(this, realAccount);
} else {
MessageList.actionHandleFolder(this, realAccount, realAccount.getAutoExpandFolderName());
}
}
return true;
}
public void onClick(View view) {
if (view.getId() == R.id.next) {
onAddNewAccount();
}
}
private void onDeleteAccount(Account account) {
mSelectedContextAccount = account;
showDialog(DIALOG_REMOVE_ACCOUNT);
}
@Override
public Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_REMOVE_ACCOUNT:
return createRemoveAccountDialog();
case DIALOG_CLEAR_ACCOUNT:
return createClearAccountDialog();
case DIALOG_RECREATE_ACCOUNT:
return createRecreateAccountDialog();
}
return super.onCreateDialog(id);
}
@Override
public void onPrepareDialog(int id, Dialog d) {
AlertDialog alert = (AlertDialog) d;
switch (id) {
case DIALOG_REMOVE_ACCOUNT:
alert.setMessage(getString(R.string.account_delete_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
case DIALOG_CLEAR_ACCOUNT:
alert.setMessage(getString(R.string.account_clear_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
case DIALOG_RECREATE_ACCOUNT:
alert.setMessage(getString(R.string.account_recreate_dlg_instructions_fmt,
mSelectedContextAccount.getDescription()));
break;
}
super.onPrepareDialog(id, d);
}
private Dialog createRemoveAccountDialog() {
return new AlertDialog.Builder(this)
.setTitle(R.string.account_delete_dlg_title)
.setMessage(getString(R.string.account_delete_dlg_instructions_fmt, mSelectedContextAccount.getDescription()))
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_REMOVE_ACCOUNT);
removeDialog(DIALOG_REMOVE_ACCOUNT);
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount;
try {
realAccount.getLocalStore().delete();
} catch (Exception e) {
// Ignore, this may lead to localStores on sd-cards that are currently not inserted to be left
}
MessagingController.getInstance(getApplication()).notifyAccountCancel(Accounts.this, realAccount);
Preferences.getPreferences(Accounts.this).deleteAccount(realAccount);
K9.setServicesEnabled(Accounts.this);
refresh();
}
}
})
.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_REMOVE_ACCOUNT);
removeDialog(DIALOG_REMOVE_ACCOUNT);
}
})
.create();
}
private Dialog createClearAccountDialog() {
return new AlertDialog.Builder(this)
.setTitle(R.string.account_clear_dlg_title)
.setMessage(getString(R.string.account_clear_dlg_instructions_fmt, mSelectedContextAccount.getDescription()))
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CLEAR_ACCOUNT);
removeDialog(DIALOG_CLEAR_ACCOUNT);
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount;
mHandler.workingAccount(realAccount, R.string.clearing_account);
MessagingController.getInstance(getApplication()).clear(realAccount, null);
}
}
})
.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CLEAR_ACCOUNT);
removeDialog(DIALOG_CLEAR_ACCOUNT);
}
})
.create();
}
private Dialog createRecreateAccountDialog() {
return new AlertDialog.Builder(this)
.setTitle(R.string.account_recreate_dlg_title)
.setMessage(getString(R.string.account_recreate_dlg_instructions_fmt, mSelectedContextAccount.getDescription()))
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_RECREATE_ACCOUNT);
removeDialog(DIALOG_RECREATE_ACCOUNT);
if (mSelectedContextAccount instanceof Account) {
Account realAccount = (Account)mSelectedContextAccount;
mHandler.workingAccount(realAccount, R.string.recreating_account);
MessagingController.getInstance(getApplication()).recreate(realAccount, null);
}
}
})
.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_RECREATE_ACCOUNT);
removeDialog(DIALOG_RECREATE_ACCOUNT);
}
})
.create();
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo)item.getMenuInfo();
// submenus don't actually set the menuInfo, so the "advanced"
// submenu wouldn't work.
if (menuInfo != null) {
mSelectedContextAccount = (BaseAccount)getListView().getItemAtPosition(menuInfo.position);
}
Account realAccount = null;
if (mSelectedContextAccount instanceof Account) {
realAccount = (Account)mSelectedContextAccount;
}
switch (item.getItemId()) {
case R.id.delete_account:
onDeleteAccount(realAccount);
break;
case R.id.edit_account:
onEditAccount(realAccount);
break;
case R.id.open:
onOpenAccount(mSelectedContextAccount);
break;
case R.id.check_mail:
onCheckMail(realAccount);
break;
case R.id.clear_pending:
onClearCommands(realAccount);
break;
case R.id.empty_trash:
onEmptyTrash(realAccount);
break;
case R.id.compact:
onCompact(realAccount);
break;
case R.id.clear:
onClear(realAccount);
break;
case R.id.recreate:
onRecreate(realAccount);
break;
case R.id.export:
onExport(false, realAccount);
break;
}
return true;
}
private void onCompact(Account account) {
mHandler.workingAccount(account, R.string.compacting_account);
MessagingController.getInstance(getApplication()).compact(account, null);
}
private void onClear(Account account) {
showDialog(DIALOG_CLEAR_ACCOUNT);
}
private void onRecreate(Account account) {
showDialog(DIALOG_RECREATE_ACCOUNT);
}
public void onItemClick(AdapterView> parent, View view, int position, long id) {
BaseAccount account = (BaseAccount)parent.getItemAtPosition(position);
onOpenAccount(account);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_new_account:
onAddNewAccount();
break;
case R.id.edit_prefs:
onEditPrefs();
break;
case R.id.check_mail:
onCheckMail(null);
break;
case R.id.compose:
onCompose();
break;
case R.id.about:
onAbout();
break;
case R.id.search:
onSearchRequested();
break;
case R.id.export_all:
onExport(true, null);
break;
case R.id.import_settings:
onImport();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
private static String[][] USED_LIBRARIES = new String[][] {
new String[] {"jutf7", "http://jutf7.sourceforge.net/"},
new String[] {"JZlib", "http://www.jcraft.com/jzlib/"},
new String[] {"Commons IO", "http://commons.apache.org/io/"},
new String[] {"Mime4j", "http://james.apache.org/mime4j/"},
};
private void onAbout() {
String appName = getString(R.string.app_name);
String year = "2011";
WebView wv = new WebView(this);
StringBuilder html = new StringBuilder()
.append("")
.append("")
.append("")
.append(String.format(getString(R.string.about_title_fmt),
"")
.append(appName)
.append("")
.append("
")
.append(appName)
.append(" ")
.append(String.format(getString(R.string.debug_version_fmt), getVersionNumber()))
.append("
")
.append(String.format(getString(R.string.app_authors_fmt),
getString(R.string.app_authors)))
.append("
")
.append(String.format(getString(R.string.app_revision_fmt),
"" +
getString(R.string.app_revision_url) +
""))
.append("
")
.append(String.format(getString(R.string.app_copyright_fmt), year, year))
.append("
")
.append(getString(R.string.app_license))
.append("
");
StringBuilder libs = new StringBuilder().append("
");
for (String[] library : USED_LIBRARIES) {
libs.append("- " + library[0] + "
");
}
libs.append("
");
html.append(String.format(getString(R.string.app_libraries), libs.toString()))
.append("
")
.append(String.format(getString(R.string.app_emoji_icons),
"
"))
.append("");
wv.loadDataWithBaseURL("file:///android_res/drawable/", html.toString(), "text/html", "utf-8", null);
new AlertDialog.Builder(this)
.setView(wv)
.setCancelable(true)
.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int c) {
d.dismiss();
}
})
.show();
}
/**
* Get current version number.
*
* @return String version
*/
private String getVersionNumber() {
String version = "?";
try {
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pi.versionName;
} catch (PackageManager.NameNotFoundException e) {
//Log.e(TAG, "Package name not found", e);
}
return version;
}
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.accounts_option, menu);
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.setHeaderTitle(R.string.accounts_context_menu_title);
getMenuInflater().inflate(R.menu.accounts_context, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
BaseAccount account = mAdapter.getItem(info.position);
if (account instanceof SearchAccount) {
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
if (item.getItemId() != R.id.open) {
item.setVisible(false);
}
}
}
}
private void onImport() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType(MimeUtility.K9_SETTINGS_MIME_TYPE);
startActivityForResult(Intent.createChooser(i, null), ACTIVITY_REQUEST_PICK_SETTINGS_FILE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(K9.LOG_TAG, "onActivityResult requestCode = " + requestCode + ", resultCode = " + resultCode + ", data = " + data);
if (resultCode != RESULT_OK)
return;
if (data == null) {
return;
}
switch (requestCode) {
case ACTIVITY_REQUEST_PICK_SETTINGS_FILE:
onImport(data.getData());
break;
}
}
private void onImport(Uri uri) {
//Toast.makeText(this, "Import is disabled for now", Toast.LENGTH_SHORT).show();
Log.i(K9.LOG_TAG, "onImport importing from URI " + uri.toString());
new ListImportContentsAsyncTask(uri, null).execute();
}
private void showDialog(final Context context, final int headerRes, final String message) {
this.runOnUiThread(new Runnable() {
@Override
public void run() {
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(headerRes);
builder.setMessage(message);
builder.setPositiveButton(R.string.okay_action,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
});
}
class AccountsAdapter extends ArrayAdapter {
public AccountsAdapter(BaseAccount[] accounts) {
super(Accounts.this, 0, accounts);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final BaseAccount account = getItem(position);
View view;
if (convertView != null) {
view = convertView;
} else {
view = getLayoutInflater().inflate(R.layout.accounts_item, parent, false);
}
AccountViewHolder holder = (AccountViewHolder) view.getTag();
if (holder == null) {
holder = new AccountViewHolder();
holder.description = (TextView) view.findViewById(R.id.description);
holder.email = (TextView) view.findViewById(R.id.email);
holder.newMessageCount = (TextView) view.findViewById(R.id.new_message_count);
holder.flaggedMessageCount = (TextView) view.findViewById(R.id.flagged_message_count);
holder.activeIcons = (RelativeLayout) view.findViewById(R.id.active_icons);
holder.chip = view.findViewById(R.id.chip);
holder.folders = (ImageButton) view.findViewById(R.id.folders);
holder.accountsItemLayout = (LinearLayout)view.findViewById(R.id.accounts_item_layout);
view.setTag(holder);
}
AccountStats stats = accountStats.get(account.getUuid());
if (stats != null && account instanceof Account && stats.size >= 0) {
holder.email.setText(SizeFormatter.formatSize(Accounts.this, stats.size));
holder.email.setVisibility(View.VISIBLE);
} else {
if (account.getEmail().equals(account.getDescription())) {
holder.email.setVisibility(View.GONE);
} else {
holder.email.setVisibility(View.VISIBLE);
holder.email.setText(account.getEmail());
}
}
String description = account.getDescription();
if (description == null || description.length() == 0) {
description = account.getEmail();
}
holder.description.setText(description);
Integer unreadMessageCount = null;
if (stats != null) {
unreadMessageCount = stats.unreadMessageCount;
holder.newMessageCount.setText(Integer.toString(unreadMessageCount));
holder.newMessageCount.setVisibility(unreadMessageCount > 0 ? View.VISIBLE : View.GONE);
holder.flaggedMessageCount.setText(Integer.toString(stats.flaggedMessageCount));
holder.flaggedMessageCount.setVisibility(K9.messageListStars() && stats.flaggedMessageCount > 0 ? View.VISIBLE : View.GONE);
holder.flaggedMessageCount.setOnClickListener(new AccountClickListener(account, SearchModifier.FLAGGED));
holder.newMessageCount.setOnClickListener(new AccountClickListener(account, SearchModifier.UNREAD));
view.getBackground().setAlpha(stats.available ? 0 : 127);
holder.activeIcons.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Toast toast = Toast.makeText(getApplication(), getString(R.string.tap_hint), Toast.LENGTH_SHORT);
toast.show();
}
}
);
} else {
holder.newMessageCount.setVisibility(View.GONE);
holder.flaggedMessageCount.setVisibility(View.GONE);
view.getBackground().setAlpha(0);
}
if (account instanceof Account) {
Account realAccount = (Account)account;
holder.chip.setBackgroundDrawable(realAccount.generateColorChip().drawable());
if (unreadMessageCount == null) {
holder.chip.getBackground().setAlpha(0);
} else if (unreadMessageCount == 0) {
holder.chip.getBackground().setAlpha(127);
} else {
holder.chip.getBackground().setAlpha(255);
}
} else {
holder.chip.setBackgroundDrawable(new ColorChip(0xff999999).drawable());
}
holder.description.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mFontSizes.getAccountName());
holder.email.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mFontSizes.getAccountDescription());
if (K9.useCompactLayouts()) {
holder.accountsItemLayout.setMinimumHeight(0);
}
if (account instanceof SearchAccount || K9.useCompactLayouts()) {
holder.folders.setVisibility(View.GONE);
} else {
holder.folders.setVisibility(View.VISIBLE);
holder.folders.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
FolderList.actionHandleAccount(Accounts.this, (Account)account);
}
});
}
return view;
}
class AccountViewHolder {
public TextView description;
public TextView email;
public TextView newMessageCount;
public TextView flaggedMessageCount;
public RelativeLayout activeIcons;
public View chip;
public ImageButton folders;
public LinearLayout accountsItemLayout;
}
}
private Flag[] combine(Flag[] set1, Flag[] set2) {
if (set1 == null) {
return set2;
}
if (set2 == null) {
return set1;
}
Set flags = new HashSet();
flags.addAll(Arrays.asList(set1));
flags.addAll(Arrays.asList(set2));
return flags.toArray(EMPTY_FLAG_ARRAY);
}
private class AccountClickListener implements OnClickListener {
final BaseAccount account;
final SearchModifier searchModifier;
AccountClickListener(BaseAccount nAccount, SearchModifier nSearchModifier) {
account = nAccount;
searchModifier = nSearchModifier;
}
@Override
public void onClick(View v) {
String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
if (account instanceof SearchAccount) {
SearchAccount searchAccount = (SearchAccount)account;
MessageList.actionHandle(Accounts.this,
description, "", searchAccount.isIntegrate(),
combine(searchAccount.getRequiredFlags(), searchModifier.requiredFlags),
combine(searchAccount.getForbiddenFlags(), searchModifier.forbiddenFlags));
} else {
SearchSpecification searchSpec = new SearchSpecification() {
@Override
public String[] getAccountUuids() {
return new String[] { account.getUuid() };
}
@Override
public Flag[] getForbiddenFlags() {
return searchModifier.forbiddenFlags;
}
@Override
public String getQuery() {
return "";
}
@Override
public Flag[] getRequiredFlags() {
return searchModifier.requiredFlags;
}
@Override
public boolean isIntegrate() {
return false;
}
@Override
public String[] getFolderNames() {
return null;
}
};
MessageList.actionHandle(Accounts.this, description, searchSpec);
}
}
}
public void onExport(final boolean includeGlobals, final Account account) {
// TODO, prompt to allow a user to choose which accounts to export
Set accountUuids = null;
if (account != null) {
accountUuids = new HashSet();
accountUuids.add(account.getUuid());
}
/* Disabled for now
// Prompt the user for a password
new PasswordEntryDialog(this,
getString(R.string.settings_export_encryption_password_prompt),
new PasswordEntryDialog.PasswordEntryListener() {
public void passwordChosen(final String chosenPassword) {
// Got the password. Now run export task in the background.
new ExportAsyncTask(includeGlobals, accountUuids, chosenPassword).execute();
}
public void cancel() {
// User cancelled the export. Nothing more to do.
}
})
.show();
*/
new ExportAsyncTask(includeGlobals, accountUuids, null).execute();
}
private class ExportAsyncTask extends AsyncTask {
private boolean mIncludeGlobals;
private Set mAccountUuids;
private String mEncryptionKey;
private String mFileName;
private ExportAsyncTask(boolean includeGlobals, Set accountUuids,
String encryptionKey) {
mIncludeGlobals = includeGlobals;
mAccountUuids = accountUuids;
mEncryptionKey = encryptionKey;
}
@Override
protected void onPreExecute() {
//TODO: show progress bar instead of displaying toast
String toastText = Accounts.this.getString(R.string.settings_exporting);
Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT);
toast.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
mFileName = StorageExporter.exportToFile(Accounts.this, mIncludeGlobals,
mAccountUuids, mEncryptionKey);
} catch (StorageImportExportException e) {
Log.w(K9.LOG_TAG, "Exception during export", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
showDialog(Accounts.this, R.string.settings_export_success_header,
Accounts.this.getString(R.string.settings_export_success, mFileName));
} else {
//TODO: make the exporter return an error code; translate that error code to a localized string here
showDialog(Accounts.this, R.string.settings_export_failed_header,
Accounts.this.getString(R.string.settings_export_failure, "Something went wrong"));
}
}
}
private class ImportAsyncTask extends AsyncTask {
private boolean mIncludeGlobals;
private List mAccountUuids;
private boolean mOverwrite;
private String mEncryptionKey;
private InputStream mInputStream;
private ImportAsyncTask(boolean includeGlobals, List accountUuids,
boolean overwrite, String encryptionKey, InputStream is) {
mIncludeGlobals = includeGlobals;
mAccountUuids = accountUuids;
mOverwrite = overwrite;
mEncryptionKey = encryptionKey;
mInputStream = is;
}
@Override
protected void onPreExecute() {
//TODO: show progress bar instead of displaying toast
String toastText = Accounts.this.getString(R.string.settings_importing);
Toast toast = Toast.makeText(Accounts.this, toastText, Toast.LENGTH_SHORT);
toast.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
StorageImporter.importSettings(Accounts.this, mInputStream, mEncryptionKey,
mIncludeGlobals, mAccountUuids, mOverwrite);
} catch (StorageImportExportException e) {
Log.w(K9.LOG_TAG, "Exception during export", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
showDialog(Accounts.this, R.string.settings_import_success_header,
//FIXME: use correct number of imported accounts
Accounts.this.getString(R.string.settings_import_success, 3, "unknown"));
refresh();
} else {
//TODO: make the importer return an error code; translate that error code to a localized string here
showDialog(Accounts.this, R.string.settings_import_failed_header,
Accounts.this.getString(R.string.settings_import_failure, "unknown", "Something went wrong"));
}
}
}
ImportContents mImportContents;
private class ListImportContentsAsyncTask extends AsyncTask {
private Uri mUri;
private String mEncryptionKey;
private InputStream mInputStream;
private ListImportContentsAsyncTask(Uri uri, String encryptionKey) {
mUri = uri;
mEncryptionKey = encryptionKey;
}
@Override
protected void onPreExecute() {
//TODO: show progress bar
}
@Override
protected Boolean doInBackground(Void... params) {
try {
InputStream is = getContentResolver().openInputStream(mUri);
mImportContents = StorageImporter.getImportStreamContents(
Accounts.this, is, mEncryptionKey);
// Open another InputStream in the background. This is used later by ImportAsyncTask
mInputStream = getContentResolver().openInputStream(mUri);
} catch (StorageImportExportException e) {
Log.w(K9.LOG_TAG, "Exception during export", e);
return false;
}
catch (FileNotFoundException e) {
Log.w(K9.LOG_TAG, "Couldn't read content from URI " + mUri);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
final ListView importSelectionView = new ListView(Accounts.this);
List contents = new ArrayList();
if (mImportContents.globalSettings) {
contents.add("Global settings");
}
for (AccountDescription account : mImportContents.accounts) {
contents.add(account.name);
}
importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
importSelectionView.setAdapter(new ArrayAdapter(Accounts.this, android.R.layout.simple_list_item_checked, contents));
importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> parent, View view, int pos, long id) {
CheckedTextView ctv = (CheckedTextView)view;
ctv.setChecked(!ctv.isChecked());
}
@Override
public void onNothingSelected(AdapterView> arg0) {}
});
//TODO: listview header: "Please select the settings you wish to import"
//TODO: listview footer: "Select all" / "Select none" buttons?
//TODO: listview footer: "Overwrite existing accounts?" checkbox
final AlertDialog.Builder builder = new AlertDialog.Builder(Accounts.this);
builder.setTitle("Import selection");
builder.setView(importSelectionView);
builder.setInverseBackgroundForced(true);
builder.setPositiveButton(R.string.okay_action,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ListAdapter adapter = importSelectionView.getAdapter();
int count = adapter.getCount();
SparseBooleanArray pos = importSelectionView.getCheckedItemPositions();
boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false;
List accountUuids = new ArrayList();
for (int i = 1; i < count; i++) {
if (pos.get(i)) {
accountUuids.add(mImportContents.accounts.get(i-1).uuid);
}
}
boolean overwrite = false; //TODO: get value from dialog
dialog.dismiss();
new ImportAsyncTask(includeGlobals, accountUuids, overwrite, mEncryptionKey, mInputStream).execute();
}
});
builder.setNegativeButton(R.string.cancel_action,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
try {
mInputStream.close();
} catch (Exception e) { /* Ignore */ }
}
});
builder.show();
} else {
//TODO: make the importer return an error code; translate that error code to a localized string here
showDialog(Accounts.this, R.string.settings_import_failed_header,
Accounts.this.getString(R.string.settings_import_failure, "unknown", "Something went wrong"));
}
}
}
}