mirror of
https://github.com/moparisthebest/k-9
synced 2024-12-17 21:32:26 -05:00
Merge branch 'mail-on-sd'
* mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
This commit is contained in:
parent
4449642410
commit
14055691a3
2
.project
2
.project
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>k9mail</name>
|
||||
<name>k9mail-sdcard</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
|
@ -315,6 +315,29 @@
|
||||
<action android:name="com.fsck.k9.service.CoreReceiver.wakeLockRelease"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="com.fsck.k9.service.StorageReceiver"
|
||||
android:enabled="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<!--
|
||||
android.intent.action.MEDIA_MOUNTED
|
||||
|
||||
* Broadcast Action: External media is present and mounted at its mount point.
|
||||
* The path to the mount point for the removed media is contained in the Intent.mData field.
|
||||
* The Intent contains an extra with name "read-only" and Boolean value to indicate if the
|
||||
* media was mounted read only.
|
||||
|
||||
-->
|
||||
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
|
||||
<!--
|
||||
|
||||
MEDIA_EJECT and MEDIA_UNMOUNTED are not defined here: they have to be dynamically registered
|
||||
otherwise it would make K-9 start at the wrong time
|
||||
|
||||
-->
|
||||
<data android:scheme="file"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service
|
||||
android:name="com.fsck.k9.service.MailService"
|
||||
android:enabled="true"
|
||||
|
@ -392,6 +392,13 @@ Willkommen zum \"K-9 Mail\"-Setup. K-9 ist eine quelloffene E-Mail-Anwendung fü
|
||||
<string name="account_setup_incoming_save_all_headers_title">Alle Mail-Header herunterladen</string>
|
||||
<string name="account_setup_incoming_save_all_headers_label">Alle Header lokal speichern</string>
|
||||
|
||||
<string name="account_setup_incoming_use_sd_card_title">Nutze SD-Karte</string>
|
||||
<string name="account_setup_incoming_use_sd_card_label">Speichere die Mails auf der SD-Karte</string>
|
||||
<string name="local_storage_provider_external_label">Externes Medium (SD-Karte)</string>
|
||||
<string name="local_storage_provider_internal_label">Interner Speicher</string>
|
||||
<string name="local_storage_provider_samsunggalaxy_label">%1$s zusätzlicher interner Speicher</string>
|
||||
<string name="local_storage_provider_label">Speicherort</string>
|
||||
|
||||
<string name="account_setup_expunge_policy_label">Ordner bereinigen (Expunge)</string>
|
||||
<string name="account_setup_expunge_policy_immediately">Sofort nach Verschieben oder Kopieren</string>
|
||||
<string name="account_setup_expunge_policy_on_poll">Bei jedem Abrufen</string>
|
||||
|
@ -364,6 +364,12 @@
|
||||
<string name="account_setup_incoming_other_label">Autre</string>
|
||||
<string name="account_setup_incoming_save_all_headers_title">Téléchargement des entêtes de messages</string>
|
||||
<string name="account_setup_incoming_save_all_headers_label">Enregistrer toutes les entêtes localement</string>
|
||||
|
||||
<string name="local_storage_provider_external_label">Stockage externe (carte SD)</string>
|
||||
<string name="local_storage_provider_internal_label">Stockage interne</string>
|
||||
<string name="local_storage_provider_samsunggalaxy_label">Stockage additionnel %1$s</string>
|
||||
<string name="local_storage_provider_label">Emplacement du stockage</string>
|
||||
|
||||
<string name="account_setup_expunge_policy_label">Élimination des messages</string>
|
||||
<string name="account_setup_expunge_policy_immediately">Immédiatement après avoir supprimé ou déplacé</string>
|
||||
<string name="account_setup_expunge_policy_on_poll">Pendant chaque récupération</string>
|
||||
@ -502,6 +508,10 @@
|
||||
|
||||
<string name="account_settings_mail_check_frequency_label">Fréquence de vérification du dossier</string>
|
||||
<string name="account_settings_second_class_check_frequency_label">Fréquence de vérification pour 2ème classe</string>
|
||||
|
||||
<string name="account_settings_storage_title">Stockage</string>
|
||||
|
||||
|
||||
<string name="account_settings_color_label">Couleur du compte</string>
|
||||
<string name="account_settings_color_summary">Choisir la couleur du compte tel qu\'affichée dans les listes de dossiers ou de comptes</string>
|
||||
<string name="account_settings_led_color_label">Couleur de la DEL de notification</string>
|
||||
|
@ -398,6 +398,11 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
|
||||
<string name="account_setup_incoming_save_all_headers_title">Download headers</string>
|
||||
<string name="account_setup_incoming_save_all_headers_label">Save all message headers locally</string>
|
||||
|
||||
<string name="local_storage_provider_external_label">External storage (SD card)</string>
|
||||
<string name="local_storage_provider_internal_label">Regular internal storage</string>
|
||||
<string name="local_storage_provider_samsunggalaxy_label">%1$s additional internal storage</string>
|
||||
<string name="local_storage_provider_label">Storage location</string>
|
||||
|
||||
<string name="account_setup_expunge_policy_label">Expunge deleted messages</string>
|
||||
<string name="account_setup_expunge_policy_immediately">Immediately</string>
|
||||
<string name="account_setup_expunge_policy_on_poll">When polling</string>
|
||||
@ -558,6 +563,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
|
||||
<string name="account_settings_mail_check_frequency_label">Folder poll frequency</string>
|
||||
<string name="account_settings_second_class_check_frequency_label">2nd class check frequency</string>
|
||||
|
||||
<string name="account_settings_storage_title">Storage</string>
|
||||
|
||||
|
||||
<string name="account_settings_color_label">Account color</string>
|
||||
<string name="account_settings_color_summary">Choose the color of the account used in folder and account list</string>
|
||||
|
||||
|
@ -283,6 +283,17 @@
|
||||
android:dialogTitle="@string/account_settings_searchable_label" />
|
||||
|
||||
</PreferenceScreen>
|
||||
<PreferenceScreen
|
||||
android:title="@string/account_settings_storage_title"
|
||||
android:key="folders">
|
||||
|
||||
<ListPreference
|
||||
android:persistent="false"
|
||||
android:key="local_storage_provider"
|
||||
android:title="@string/local_storage_provider_label"
|
||||
android:dialogTitle="@string/local_storage_provider_label"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen
|
||||
android:title="@string/account_settings_notifications"
|
||||
|
@ -15,7 +15,10 @@ import com.fsck.k9.mail.Folder;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Store;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
@ -32,6 +35,18 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
public class Account implements BaseAccount
|
||||
{
|
||||
/**
|
||||
* @see Account#setLocalStoreMigrationListener(LocalStoreMigrationListener)
|
||||
*
|
||||
*/
|
||||
public interface LocalStoreMigrationListener
|
||||
{
|
||||
|
||||
void onLocalStoreMigration(String oldStoreUri,
|
||||
String newStoreUri) throws MessagingException;
|
||||
|
||||
}
|
||||
|
||||
public static final String EXPUNGE_IMMEDIATELY = "EXPUNGE_IMMEDIATELY";
|
||||
public static final String EXPUNGE_MANUALLY = "EXPUNGE_MANUALLY";
|
||||
public static final String EXPUNGE_ON_POLL = "EXPUNGE_ON_POLL";
|
||||
@ -62,7 +77,19 @@ public class Account implements BaseAccount
|
||||
|
||||
private String mUuid;
|
||||
private String mStoreUri;
|
||||
private String mLocalStoreUri;
|
||||
|
||||
/**
|
||||
* Storage provider ID, used to locate and manage the underlying DB/file
|
||||
* storage
|
||||
*/
|
||||
private String mLocalStorageProviderId;
|
||||
|
||||
/**
|
||||
* True if {@link #mLocalStoreUri} may be in use at
|
||||
* the moment.
|
||||
*/
|
||||
private boolean mIsInUse = false;
|
||||
private LocalStoreMigrationListener mLocalStoreMigrationListener;
|
||||
private String mTransportUri;
|
||||
private String mDescription;
|
||||
private String mAlwaysBcc;
|
||||
@ -146,9 +173,8 @@ public class Account implements BaseAccount
|
||||
|
||||
protected Account(Context context)
|
||||
{
|
||||
// TODO Change local store path to something readable / recognizable
|
||||
mUuid = UUID.randomUUID().toString();
|
||||
mLocalStoreUri = "local://localhost/" + context.getDatabasePath(mUuid + ".db");
|
||||
mLocalStorageProviderId = StorageManager.getInstance(K9.app).getDefaultProviderId();
|
||||
mAutomaticCheckIntervalMinutes = -1;
|
||||
mIdleRefreshMinutes = 24;
|
||||
mSaveAllHeaders = true;
|
||||
@ -216,7 +242,7 @@ public class Account implements BaseAccount
|
||||
|
||||
mStoreUri = Utility.base64Decode(prefs.getString(mUuid
|
||||
+ ".storeUri", null));
|
||||
mLocalStoreUri = prefs.getString(mUuid + ".localStoreUri", null);
|
||||
mLocalStorageProviderId = prefs.getString(mUuid + ".localStorageProvider", StorageManager.getInstance(K9.app).getDefaultProviderId());
|
||||
mTransportUri = Utility.base64Decode(prefs.getString(mUuid
|
||||
+ ".transportUri", null));
|
||||
mDescription = prefs.getString(mUuid + ".description", null);
|
||||
@ -508,7 +534,7 @@ public class Account implements BaseAccount
|
||||
}
|
||||
|
||||
editor.putString(mUuid + ".storeUri", Utility.base64Encode(mStoreUri));
|
||||
editor.putString(mUuid + ".localStoreUri", mLocalStoreUri);
|
||||
editor.putString(mUuid + ".localStorageProvider", mLocalStorageProviderId);
|
||||
editor.putString(mUuid + ".transportUri", Utility.base64Encode(mTransportUri));
|
||||
editor.putString(mUuid + ".description", mDescription);
|
||||
editor.putString(mUuid + ".alwaysBcc", mAlwaysBcc);
|
||||
@ -575,7 +601,6 @@ public class Account implements BaseAccount
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void resetVisibleLimits()
|
||||
{
|
||||
try
|
||||
@ -590,8 +615,18 @@ public class Account implements BaseAccount
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return <code>null</code> if not available
|
||||
* @throws MessagingException
|
||||
* @see {@link #isAvailable(Context)}
|
||||
*/
|
||||
public AccountStats getStats(Context context) throws MessagingException
|
||||
{
|
||||
if (!isAvailable(context))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
long startTime = System.currentTimeMillis();
|
||||
AccountStats stats = new AccountStats();
|
||||
int unreadMessageCount = 0;
|
||||
@ -772,15 +807,44 @@ public class Account implements BaseAccount
|
||||
mRingNotified = ringNotified;
|
||||
}
|
||||
|
||||
public synchronized String getLocalStoreUri()
|
||||
public String getLocalStorageProviderId()
|
||||
{
|
||||
return mLocalStoreUri;
|
||||
return mLocalStorageProviderId;
|
||||
}
|
||||
|
||||
public synchronized void setLocalStoreUri(String localStoreUri)
|
||||
public void setLocalStorageProviderId(String id)
|
||||
{
|
||||
this.mLocalStoreUri = localStoreUri;
|
||||
|
||||
if (!mLocalStorageProviderId.equals(id))
|
||||
{
|
||||
|
||||
boolean successful = false;
|
||||
try
|
||||
{
|
||||
switchLocalStorage(id);
|
||||
successful = true;
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
// if migration to/from SD-card failed once, it will fail again.
|
||||
if (!successful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mLocalStorageProviderId = id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// public synchronized void setLocalStoreUri(String localStoreUri)
|
||||
// {
|
||||
// this.mLocalStoreUri = localStoreUri;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Returns -1 for never.
|
||||
@ -1316,6 +1380,26 @@ public class Account implements BaseAccount
|
||||
mSaveAllHeaders = saveAllHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we storing out localStore on the SD-card instead of the local device
|
||||
* memory?<br/>
|
||||
* Only to be called durin initial account-setup!<br/>
|
||||
* Side-effect: changes {@link #mLocalStorageProviderId}.
|
||||
*
|
||||
* @param context
|
||||
* @param newStorageProviderId
|
||||
* Never <code>null</code>.
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public void switchLocalStorage(String newStorageProviderId) throws MessagingException
|
||||
{
|
||||
if (this.mLocalStoreMigrationListener != null && !mLocalStorageProviderId.equals(newStorageProviderId))
|
||||
{
|
||||
mLocalStoreMigrationListener.onLocalStoreMigration(mLocalStorageProviderId,
|
||||
newStorageProviderId);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean goToUnreadMessageSearch()
|
||||
{
|
||||
return goToUnreadMessageSearch;
|
||||
@ -1468,6 +1552,24 @@ public class Account implements BaseAccount
|
||||
lastSelectedFolderName = folderName;
|
||||
}
|
||||
|
||||
public boolean isInUse()
|
||||
{
|
||||
return mIsInUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a listener to be informed when the underlying {@link StorageProvider}
|
||||
* of the {@link LocalStore} of this account changes. (e.g. via
|
||||
* {@link #switchLocalStorage(Context, String)})
|
||||
*
|
||||
* @param listener
|
||||
* @see #switchLocalStorage(Context, String)
|
||||
*/
|
||||
public void setLocalStoreMigrationListener(LocalStoreMigrationListener listener)
|
||||
{
|
||||
this.mLocalStoreMigrationListener = listener;
|
||||
}
|
||||
|
||||
public synchronized CryptoProvider getCryptoProvider()
|
||||
{
|
||||
if (mCryptoProvider == null)
|
||||
@ -1482,4 +1584,18 @@ public class Account implements BaseAccount
|
||||
return mNotificationSetting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if our {@link StorageProvider} is ready. (e.g.
|
||||
* card inserted)
|
||||
*/
|
||||
public boolean isAvailable(Context context)
|
||||
{
|
||||
String localStorageProviderId = getLocalStorageProviderId();
|
||||
if (localStorageProviderId == null)
|
||||
{
|
||||
return true; // defaults to internal memory
|
||||
}
|
||||
return StorageManager.getInstance(K9.app).isReady(localStorageProviderId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,16 +5,21 @@ import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebSettings;
|
||||
|
||||
@ -27,6 +32,8 @@ import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||
import com.fsck.k9.service.BootReceiver;
|
||||
import com.fsck.k9.service.MailService;
|
||||
import com.fsck.k9.service.ShutdownReceiver;
|
||||
import com.fsck.k9.service.StorageGoneReceiver;
|
||||
|
||||
public class K9 extends Application
|
||||
{
|
||||
@ -305,7 +312,7 @@ public class K9 extends Application
|
||||
*/
|
||||
public static void setServicesEnabled(Context context)
|
||||
{
|
||||
int acctLength = Preferences.getPreferences(context).getAccounts().length;
|
||||
int acctLength = Preferences.getPreferences(context).getAvailableAccounts().size();
|
||||
|
||||
setServicesEnabled(context, acctLength > 0, null);
|
||||
|
||||
@ -313,7 +320,7 @@ public class K9 extends Application
|
||||
|
||||
public static void setServicesEnabled(Context context, Integer wakeLockId)
|
||||
{
|
||||
setServicesEnabled(context, Preferences.getPreferences(context).getAccounts().length > 0, wakeLockId);
|
||||
setServicesEnabled(context, Preferences.getPreferences(context).getAvailableAccounts().size() > 0, wakeLockId);
|
||||
}
|
||||
|
||||
public static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId)
|
||||
@ -360,6 +367,56 @@ public class K9 extends Application
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register BroadcastReceivers programmaticaly because doing it from manifest
|
||||
* would make K-9 auto-start. We don't want auto-start because the initialization
|
||||
* sequence isn't safe while some events occur (SD card unmount).
|
||||
*/
|
||||
protected void registerReceivers()
|
||||
{
|
||||
final StorageGoneReceiver receiver = new StorageGoneReceiver();
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_MEDIA_EJECT);
|
||||
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
|
||||
filter.addDataScheme("file");
|
||||
|
||||
final BlockingQueue<Handler> queue = new SynchronousQueue<Handler>();
|
||||
|
||||
// starting a new thread to handle unmount events
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Looper.prepare();
|
||||
try
|
||||
{
|
||||
queue.put(new Handler());
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "", e);
|
||||
}
|
||||
Looper.loop();
|
||||
}
|
||||
|
||||
}, "Unmount-thread").start();
|
||||
|
||||
try
|
||||
{
|
||||
final Handler storageGoneHandler = queue.take();
|
||||
registerReceiver(receiver, filter, null, storageGoneHandler);
|
||||
Log.i(K9.LOG_TAG, "Registered: unmount receiver");
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "Unable to register unmount receiver", e);
|
||||
}
|
||||
|
||||
registerReceiver(new ShutdownReceiver(), new IntentFilter(Intent.ACTION_SHUTDOWN));
|
||||
Log.i(K9.LOG_TAG, "Registered: shutdown receiver");
|
||||
}
|
||||
|
||||
public static void save(SharedPreferences.Editor editor)
|
||||
{
|
||||
editor.putBoolean("enableDebugLogging", K9.DEBUG);
|
||||
@ -452,7 +509,7 @@ public class K9 extends Application
|
||||
|
||||
K9.setK9Language(sprefs.getString("language", ""));
|
||||
K9.setK9Theme(sprefs.getInt("theme", android.R.style.Theme_Light));
|
||||
MessagingController.getInstance(this).resetVisibleLimits(prefs.getAccounts());
|
||||
MessagingController.getInstance(this).resetVisibleLimits(prefs.getAvailableAccounts());
|
||||
|
||||
/*
|
||||
* We have to give MimeMessage a temp directory because File.createTempFile(String, String)
|
||||
@ -465,6 +522,7 @@ public class K9 extends Application
|
||||
*/
|
||||
|
||||
setServicesEnabled(this);
|
||||
registerReceivers();
|
||||
|
||||
MessagingController.getInstance(this).addListener(new MessagingListener()
|
||||
{
|
||||
|
@ -2,6 +2,8 @@
|
||||
package com.fsck.k9;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@ -33,10 +35,12 @@ public class Preferences
|
||||
private Storage mStorage;
|
||||
private List<Account> accounts;
|
||||
private Account newAccount;
|
||||
private Context mContext;
|
||||
|
||||
private Preferences(Context context)
|
||||
{
|
||||
mStorage = Storage.getStorage(context);
|
||||
mContext = context;
|
||||
if (mStorage.size() == 0)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Preferences storage is zero-size, importing from Android-style preferences");
|
||||
@ -67,6 +71,7 @@ public class Preferences
|
||||
/**
|
||||
* Returns an array of the accounts on the system. If no accounts are
|
||||
* registered the method returns an empty array.
|
||||
* @return all accounts
|
||||
*/
|
||||
public synchronized Account[] getAccounts()
|
||||
{
|
||||
@ -84,6 +89,36 @@ public class Preferences
|
||||
return accounts.toArray(EMPTY_ACCOUNT_ARRAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the accounts on the system. If no accounts are
|
||||
* registered the method returns an empty array.
|
||||
* @param context
|
||||
* @return all accounts with {@link Account#isAvailable(Context)}
|
||||
*/
|
||||
public synchronized Collection<Account> getAvailableAccounts()
|
||||
{
|
||||
if (accounts == null)
|
||||
{
|
||||
loadAccounts();
|
||||
}
|
||||
|
||||
if ((newAccount != null) && newAccount.getAccountNumber() != -1)
|
||||
{
|
||||
accounts.add(newAccount);
|
||||
newAccount = null;
|
||||
}
|
||||
Collection<Account> retval = new ArrayList<Account>(accounts.size());
|
||||
for (Account account : accounts)
|
||||
{
|
||||
if (account.isAvailable(mContext))
|
||||
{
|
||||
retval.add(account);
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
public synchronized Account getAccount(String uuid)
|
||||
{
|
||||
if (accounts == null)
|
||||
@ -137,10 +172,10 @@ public class Preferences
|
||||
|
||||
if (defaultAccount == null)
|
||||
{
|
||||
Account[] accounts = getAccounts();
|
||||
if (accounts.length > 0)
|
||||
Collection<Account> accounts = getAvailableAccounts();
|
||||
if (accounts.size() > 0)
|
||||
{
|
||||
defaultAccount = accounts[0];
|
||||
defaultAccount = accounts.iterator().next();
|
||||
setDefaultAccount(defaultAccount);
|
||||
}
|
||||
}
|
||||
|
@ -164,8 +164,15 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
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);
|
||||
@ -180,6 +187,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
{
|
||||
oldUnreadMessageCount = oldStats.unreadMessageCount;
|
||||
}
|
||||
if (stats == null)
|
||||
{
|
||||
stats = new AccountStats(); // empty stats for unavailable accounts
|
||||
}
|
||||
accountStats.put(account.getUuid(), stats);
|
||||
if (account instanceof Account)
|
||||
{
|
||||
@ -343,9 +354,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
}
|
||||
else if (startup && accounts.length == 1)
|
||||
{
|
||||
onOpenAccount(accounts[0]);
|
||||
if (onOpenAccount(accounts[0]))
|
||||
{
|
||||
finish();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
@ -513,7 +526,13 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
}
|
||||
}
|
||||
|
||||
private void onOpenAccount(BaseAccount account)
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
@ -523,6 +542,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
else
|
||||
{
|
||||
Account realAccount = (Account)account;
|
||||
if (!realAccount.isAvailable(this))
|
||||
{
|
||||
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);
|
||||
@ -532,6 +556,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
MessageList.actionHandleFolder(this, realAccount, realAccount.getAutoExpandFolderName());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onClick(View view)
|
||||
@ -609,7 +634,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Ignore
|
||||
// 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);
|
||||
@ -914,6 +939,24 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
|
||||
}
|
||||
AccountStats stats = accountStats.get(account.getUuid());
|
||||
|
||||
/*
|
||||
// 20101024/fiouzy: the following code throws NullPointerException because Background is null
|
||||
|
||||
// display unavailable accounts translucent
|
||||
if (account instanceof Account) {
|
||||
Account realAccount = (Account) account;
|
||||
if (realAccount.isAvailable(Accounts.this)) {
|
||||
holder.email.getBackground().setAlpha(255);
|
||||
holder.description.getBackground().setAlpha(255);
|
||||
} else {
|
||||
holder.email.getBackground().setAlpha(127);
|
||||
holder.description.getBackground().setAlpha(127);
|
||||
}
|
||||
} else {
|
||||
holder.email.getBackground().setAlpha(255);
|
||||
holder.description.getBackground().setAlpha(255);
|
||||
}
|
||||
*/
|
||||
if (stats != null && account instanceof Account && stats.size >= 0)
|
||||
{
|
||||
holder.email.setText(SizeFormatter.formatSize(Accounts.this, stats.size));
|
||||
|
@ -3,6 +3,7 @@ package com.fsck.k9.activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -63,6 +64,11 @@ public class ChooseAccount extends K9ExpandableListActivity
|
||||
final Identity identity = (Identity) adapter.getChild(groupPosition, childPosition);
|
||||
final Account account = (Account) adapter.getGroup(groupPosition);
|
||||
|
||||
if (!account.isAvailable(v.getContext()))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Refusing selection of unavailable account");
|
||||
return true;
|
||||
}
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
intent.putExtra(EXTRA_IDENTITY, identity);
|
||||
@ -180,7 +186,6 @@ public class ChooseAccount extends K9ExpandableListActivity
|
||||
final View v;
|
||||
if (convertView == null)
|
||||
{
|
||||
// is it okay to reuse?
|
||||
v = mLayoutInflater.inflate(R.layout.choose_account_item, parent, false);
|
||||
}
|
||||
else
|
||||
@ -193,6 +198,22 @@ public class ChooseAccount extends K9ExpandableListActivity
|
||||
description.setText(account.getDescription());
|
||||
description.setTextSize(TypedValue.COMPLEX_UNIT_DIP, K9.getFontSizes().getAccountName());
|
||||
|
||||
// display unavailable accounts translucent
|
||||
/*
|
||||
* 20101030/fiouzy: NullPointerException on null getBackground()
|
||||
*
|
||||
if (account.isAvailable(parent.getContext()))
|
||||
{
|
||||
description.getBackground().setAlpha(255);
|
||||
description.getBackground().setAlpha(255);
|
||||
}
|
||||
else
|
||||
{
|
||||
description.getBackground().setAlpha(127);
|
||||
description.getBackground().setAlpha(127);
|
||||
}
|
||||
*/
|
||||
|
||||
v.findViewById(R.id.chip).setBackgroundColor(account.getChipColor());
|
||||
|
||||
return v;
|
||||
|
@ -67,6 +67,10 @@ public class FolderInfoHolder implements Comparable<FolderInfoHolder>
|
||||
|
||||
public FolderInfoHolder(Context context, Folder folder, Account account)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new IllegalArgumentException("null context given");
|
||||
}
|
||||
populate(context, folder, account);
|
||||
}
|
||||
|
||||
|
@ -382,6 +382,13 @@ public class FolderList extends K9ListActivity
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (!mAccount.isAvailable(this))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "account unavaliabale, not showing folder-list but account-list");
|
||||
startActivity(new Intent(this, Accounts.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (mAdapter == null)
|
||||
initializeActivityView();
|
||||
|
||||
@ -828,6 +835,10 @@ public class FolderList extends K9ListActivity
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (stats == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
mUnreadMessageCount = stats.unreadMessageCount;
|
||||
mHandler.refreshTitle();
|
||||
}
|
||||
@ -1028,6 +1039,11 @@ public class FolderList extends K9ListActivity
|
||||
{
|
||||
if (account != null && folderName != null)
|
||||
{
|
||||
if (!account.isAvailable(FolderList.this))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "not refreshing folder of unavailable account");
|
||||
return;
|
||||
}
|
||||
localFolder = account.getLocalStore().getFolder(folderName);
|
||||
int unreadMessageCount = localFolder.getUnreadMessageCount();
|
||||
if (localFolder != null)
|
||||
|
@ -4,6 +4,7 @@ package com.fsck.k9.activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
@ -143,6 +144,18 @@ public class LauncherShortcuts extends K9ListActivity implements OnItemClickList
|
||||
|
||||
holder.chip.setBackgroundColor(realAccount.getChipColor());
|
||||
holder.chip.getBackground().setAlpha(255);
|
||||
|
||||
// show unavailable accounts as translucent
|
||||
if (realAccount.isAvailable(getContext()))
|
||||
{
|
||||
holder.email.getBackground().setAlpha(255);
|
||||
holder.description.getBackground().setAlpha(255);
|
||||
}
|
||||
else
|
||||
{
|
||||
holder.email.getBackground().setAlpha(127);
|
||||
holder.description.getBackground().setAlpha(127);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -158,7 +171,13 @@ public class LauncherShortcuts extends K9ListActivity implements OnItemClickList
|
||||
{
|
||||
public void onClick(View v)
|
||||
{
|
||||
FolderList.actionHandleAccount(LauncherShortcuts.this, (Account)account);
|
||||
Account account2 = (Account)account;
|
||||
if (!account2.isAvailable(getContext()))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "refusing selection of unavailable account");
|
||||
return ;
|
||||
}
|
||||
FolderList.actionHandleAccount(LauncherShortcuts.this, account2);
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -75,6 +75,7 @@ import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import com.fsck.k9.mail.internet.TextBody;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
|
||||
public class MessageCompose extends K9Activity implements OnClickListener, OnFocusChangeListener
|
||||
{
|
||||
@ -1525,7 +1526,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
||||
{
|
||||
// keep things simple: trigger account choice only if there are more
|
||||
// than 1 account
|
||||
if (Preferences.getPreferences(this).getAccounts().length > 1)
|
||||
if (Preferences.getPreferences(this).getAvailableAccounts().size() > 1)
|
||||
{
|
||||
final Intent intent = new Intent(this, ChooseAccount.class);
|
||||
intent.putExtra(ChooseAccount.EXTRA_ACCOUNT, mAccount.getUuid());
|
||||
@ -2335,11 +2336,22 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
||||
}
|
||||
}
|
||||
|
||||
if (K9.DEBUG)
|
||||
Log.d(K9.LOG_TAG, "Saving identity: " + k9identity);
|
||||
message.addHeader(K9.K9MAIL_IDENTITY, k9identity);
|
||||
final MessagingController messagingController = MessagingController.getInstance(getApplication());
|
||||
|
||||
Message draftMessage = MessagingController.getInstance(getApplication()).saveDraft(mAccount, message);
|
||||
if (K9.DEBUG)
|
||||
{
|
||||
Log.d(K9.LOG_TAG, "Saving identity: " + k9identity);
|
||||
}
|
||||
try
|
||||
{
|
||||
message.addHeader(K9.K9MAIL_IDENTITY, k9identity);
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
messagingController.addErrorMessage(mAccount, "Unable to save identity", e);
|
||||
}
|
||||
|
||||
Message draftMessage = messagingController.saveDraft(mAccount, message);
|
||||
mDraftUid = draftMessage.getUid();
|
||||
|
||||
// Don't display the toast if the user is just changing the orientation
|
||||
|
@ -70,6 +70,7 @@ import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Folder;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||
|
||||
/**
|
||||
@ -327,6 +328,33 @@ public class MessageList
|
||||
/* package visibility for faster inner class access */
|
||||
MessageHelper mMessageHelper = MessageHelper.getInstance(this);
|
||||
|
||||
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
|
||||
|
||||
private final class StorageListenerImplementation implements StorageManager.StorageListener
|
||||
{
|
||||
@Override
|
||||
public void onUnmount(String providerId)
|
||||
{
|
||||
if (mAccount != null && providerId.equals(mAccount.getLocalStorageProviderId()))
|
||||
{
|
||||
runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
onAccountUnavailable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMount(String providerId)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
class MessageListHandler
|
||||
{
|
||||
public void removeMessage(final List<MessageInfoHolder> messages)
|
||||
@ -671,6 +699,13 @@ public class MessageList
|
||||
mFolderName = intent.getStringExtra(EXTRA_FOLDER);
|
||||
mQueryString = intent.getStringExtra(EXTRA_QUERY);
|
||||
|
||||
if (mAccount != null && !mAccount.isAvailable(this))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
|
||||
onAccountUnavailable();
|
||||
return;
|
||||
}
|
||||
|
||||
String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
|
||||
if (queryFlags != null)
|
||||
{
|
||||
@ -727,6 +762,8 @@ public class MessageList
|
||||
super.onPause();
|
||||
mController.removeListener(mAdapter.mListener);
|
||||
saveListState();
|
||||
|
||||
StorageManager.getInstance(getApplication()).removeListener(mStorageListener);
|
||||
}
|
||||
|
||||
public void saveListState()
|
||||
@ -769,6 +806,13 @@ public class MessageList
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (mAccount != null && !mAccount.isAvailable(this))
|
||||
{
|
||||
onAccountUnavailable();
|
||||
return;
|
||||
}
|
||||
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
|
||||
|
||||
mStars = K9.messageListStars();
|
||||
mCheckboxes = K9.messageListCheckboxes();
|
||||
|
||||
@ -3363,4 +3407,12 @@ public class MessageList
|
||||
}
|
||||
mController.copyMessages(mAccount, mCurrentFolder.name, messageList.toArray(EMPTY_MESSAGE_ARRAY), folderName, null);
|
||||
}
|
||||
|
||||
protected void onAccountUnavailable()
|
||||
{
|
||||
finish();
|
||||
// TODO inform user about account unavailability using Toast
|
||||
Accounts.listAccounts(getApplicationContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -81,6 +82,7 @@ import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Multipart;
|
||||
import com.fsck.k9.mail.Part;
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalTextBody;
|
||||
@ -170,6 +172,33 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
|
||||
private Contacts mContacts;
|
||||
|
||||
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
|
||||
|
||||
private final class StorageListenerImplementation implements StorageManager.StorageListener
|
||||
{
|
||||
@Override
|
||||
public void onUnmount(String providerId)
|
||||
{
|
||||
if (providerId.equals(mAccount.getLocalStorageProviderId()))
|
||||
{
|
||||
runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
onAccountUnavailable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMount(String providerId)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair class is only available since API Level 5, so we need
|
||||
* this helper class unfortunately
|
||||
@ -970,7 +999,7 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
if (segmentList.size() == 3)
|
||||
{
|
||||
String accountId = segmentList.get(0);
|
||||
Account[] accounts = Preferences.getPreferences(this).getAccounts();
|
||||
Collection<Account> accounts = Preferences.getPreferences(this).getAvailableAccounts();
|
||||
boolean found = false;
|
||||
for (Account account : accounts)
|
||||
{
|
||||
@ -1310,6 +1339,26 @@ public class MessageView extends K9Activity implements OnClickListener
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
if (!mAccount.isAvailable(this))
|
||||
{
|
||||
onAccountUnavailable();
|
||||
return;
|
||||
}
|
||||
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause()
|
||||
{
|
||||
StorageManager.getInstance(getApplication()).removeListener(mStorageListener);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
protected void onAccountUnavailable()
|
||||
{
|
||||
finish();
|
||||
// TODO inform user about account unavailability using Toast
|
||||
Accounts.listAccounts(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,6 +14,8 @@ import android.preference.RingtonePreference;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.Account.FolderMode;
|
||||
import com.fsck.k9.K9;
|
||||
@ -29,6 +31,12 @@ import com.fsck.k9.crypto.Apg;
|
||||
import com.fsck.k9.mail.Store;
|
||||
import com.fsck.k9.service.MailService;
|
||||
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
||||
|
||||
|
||||
public class AccountSettings extends K9PreferenceActivity
|
||||
{
|
||||
private static final String EXTRA_ACCOUNT = "account";
|
||||
@ -80,6 +88,9 @@ public class AccountSettings extends K9PreferenceActivity
|
||||
private static final String PREFERENCE_CRYPTO_APP = "crypto_app";
|
||||
private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature";
|
||||
|
||||
private static final String PREFERENCE_LOCAL_STORAGE_PROVIDER = "local_storage_provider";
|
||||
|
||||
|
||||
private Account mAccount;
|
||||
private boolean mIsPushCapable = false;
|
||||
private boolean mIsExpungeCapable = false;
|
||||
@ -124,6 +135,7 @@ public class AccountSettings extends K9PreferenceActivity
|
||||
private ListPreference mCryptoApp;
|
||||
private CheckBoxPreference mCryptoAutoSignature;
|
||||
|
||||
private ListPreference mLocalStorageProvider;
|
||||
|
||||
public static void actionSettings(Context context, Account account)
|
||||
{
|
||||
@ -411,6 +423,33 @@ public class AccountSettings extends K9PreferenceActivity
|
||||
});
|
||||
|
||||
|
||||
mLocalStorageProvider = (ListPreference) findPreference(PREFERENCE_LOCAL_STORAGE_PROVIDER);
|
||||
{
|
||||
final Map<String, String> providers;
|
||||
providers = StorageManager.getInstance(K9.app).getAvailableProviders();
|
||||
int i = 0;
|
||||
final String[] providerLabels = new String[providers.size()];
|
||||
final String[] providerIds = new String[providers.size()];
|
||||
for (final Map.Entry<String, String> entry : providers.entrySet())
|
||||
{
|
||||
providerIds[i] = entry.getKey();
|
||||
providerLabels[i] = entry.getValue();
|
||||
i++;
|
||||
}
|
||||
mLocalStorageProvider.setEntryValues(providerIds);
|
||||
mLocalStorageProvider.setEntries(providerLabels);
|
||||
mLocalStorageProvider.setValue(mAccount.getLocalStorageProviderId());
|
||||
mLocalStorageProvider.setSummary(providers.get((Object)mAccount.getLocalStorageProviderId()));
|
||||
|
||||
mLocalStorageProvider.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
|
||||
{
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue)
|
||||
{
|
||||
mLocalStorageProvider.setSummary(providers.get(newValue));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
// IMAP-specific preferences
|
||||
|
||||
mPushPollOnConnect = (CheckBoxPreference) findPreference(PREFERENCE_PUSH_POLL_ON_CONNECT);
|
||||
@ -668,6 +707,8 @@ public class AccountSettings extends K9PreferenceActivity
|
||||
mAccount.setReplyAfterQuote(mReplyAfterQuote.isChecked());
|
||||
mAccount.setCryptoApp(mCryptoApp.getValue());
|
||||
mAccount.setCryptoAutoSignature(mCryptoAutoSignature.isChecked());
|
||||
mAccount.setLocalStorageProviderId(mLocalStorageProvider.getValue());
|
||||
|
||||
|
||||
if (mIsPushCapable)
|
||||
{
|
||||
|
@ -16,11 +16,14 @@ import com.fsck.k9.*;
|
||||
import com.fsck.k9.activity.ChooseFolder;
|
||||
import com.fsck.k9.activity.K9Activity;
|
||||
import com.fsck.k9.helper.Utility;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Map;
|
||||
|
||||
public class AccountSetupIncoming extends K9Activity implements OnClickListener
|
||||
{
|
||||
|
@ -66,7 +66,9 @@ import com.fsck.k9.mail.Transport;
|
||||
import com.fsck.k9.mail.internet.MimeMessage;
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import com.fsck.k9.mail.internet.TextBody;
|
||||
import com.fsck.k9.mail.store.UnavailableAccountException;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
|
||||
@ -279,7 +281,7 @@ public class MessagingController implements Runnable
|
||||
String commandDescription = null;
|
||||
try
|
||||
{
|
||||
Command command = mCommands.take();
|
||||
final Command command = mCommands.take();
|
||||
|
||||
if (command != null)
|
||||
{
|
||||
@ -289,7 +291,32 @@ public class MessagingController implements Runnable
|
||||
Log.i(K9.LOG_TAG, "Running " + (command.isForeground ? "Foreground" : "Background") + " command '" + command.description + "', seq = " + command.sequence);
|
||||
|
||||
mBusy = true;
|
||||
try
|
||||
{
|
||||
command.runnable.run();
|
||||
}
|
||||
catch (UnavailableAccountException e)
|
||||
{
|
||||
// retry later
|
||||
new Thread()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
sleep(30 * 1000);
|
||||
mCommands.put(command);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "interrupted while putting a pending command for"
|
||||
+ " an unavailable account back into the queue."
|
||||
+ " THIS SHOULD NEVER HAPPEN.");
|
||||
}
|
||||
}
|
||||
} .start();
|
||||
}
|
||||
|
||||
if (K9.DEBUG)
|
||||
Log.i(K9.LOG_TAG, (command.isForeground ? "Foreground" : "Background") +
|
||||
@ -434,6 +461,12 @@ public class MessagingController implements Runnable
|
||||
l.listFoldersStarted(account);
|
||||
}
|
||||
List<? extends Folder> localFolders = null;
|
||||
if (!account.isAvailable(mApplication))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "not listing folders of unavailable account");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Store localStore = account.getLocalStore();
|
||||
@ -475,6 +508,7 @@ public class MessagingController implements Runnable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (MessagingListener l : getListeners(listener))
|
||||
{
|
||||
@ -748,6 +782,11 @@ public class MessagingController implements Runnable
|
||||
boolean noSpecialFolders = true;
|
||||
for (final Account account : accounts)
|
||||
{
|
||||
if (!account.isAvailable(mApplication))
|
||||
{
|
||||
Log.d(K9.LOG_TAG, "searchLocalMessagesSynchronous() ignores account that is not available");
|
||||
continue;
|
||||
}
|
||||
if (accountUuids != null && !accountUuidsSet.contains(account.getUuid()))
|
||||
{
|
||||
continue;
|
||||
@ -939,7 +978,7 @@ public class MessagingController implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
public void resetVisibleLimits(Account[] accounts)
|
||||
public void resetVisibleLimits(Collection<Account> accounts)
|
||||
{
|
||||
for (Account account : accounts)
|
||||
{
|
||||
@ -2060,6 +2099,11 @@ public class MessagingController implements Runnable
|
||||
{
|
||||
processPendingCommandsSynchronous(account);
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to process pending command because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (MessagingException me)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "processPendingCommands", me);
|
||||
@ -3385,6 +3429,10 @@ public class MessagingController implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
if (!account.isAvailable(mApplication))
|
||||
{
|
||||
throw new UnavailableAccountException();
|
||||
}
|
||||
if (messagesPendingSend(account))
|
||||
{
|
||||
NotificationManager notifMgr =
|
||||
@ -3611,6 +3659,11 @@ public class MessagingController implements Runnable
|
||||
notifMgr.notify(-1500 - account.getAccountNumber(), notif);
|
||||
}
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to send pending messages because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
for (MessagingListener l : getListeners())
|
||||
@ -3834,6 +3887,11 @@ public class MessagingController implements Runnable
|
||||
|
||||
processPendingCommands(account);
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to move/copy message because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (MessagingException me)
|
||||
{
|
||||
addErrorMessage(account, null, me);
|
||||
@ -4009,6 +4067,11 @@ public class MessagingController implements Runnable
|
||||
unsuppressMessage(account, folder, uid);
|
||||
}
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to delete message because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (MessagingException me)
|
||||
{
|
||||
addErrorMessage(account, null, me);
|
||||
@ -4090,6 +4153,11 @@ public class MessagingController implements Runnable
|
||||
queuePendingCommand(account, command);
|
||||
processPendingCommands(account);
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to empty trash because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "emptyTrash failed", e);
|
||||
@ -4212,6 +4280,14 @@ public class MessagingController implements Runnable
|
||||
|
||||
for (final Account account : accounts)
|
||||
{
|
||||
if (!account.isAvailable(context))
|
||||
{
|
||||
if (K9.DEBUG)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Skipping synchronizing unavailable account " + account.getDescription());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
final long accountInterval = account.getAutomaticCheckIntervalMinutes() * 60 * 1000;
|
||||
if (!ignoreLastCheckedTime && accountInterval <= 0)
|
||||
{
|
||||
@ -4369,7 +4445,8 @@ public class MessagingController implements Runnable
|
||||
account.setRingNotified(false);
|
||||
try
|
||||
{
|
||||
if (account.getStats(context).unreadMessageCount == 0)
|
||||
AccountStats stats = account.getStats(context);
|
||||
if (stats == null || stats.unreadMessageCount == 0)
|
||||
{
|
||||
notifyAccountCancel(context, account);
|
||||
}
|
||||
@ -4436,6 +4513,11 @@ public class MessagingController implements Runnable
|
||||
l.accountSizeChanged(account, oldSize, newSize);
|
||||
}
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to compact account because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "Failed to compact account " + account.getDescription(), e);
|
||||
@ -4472,6 +4554,11 @@ public class MessagingController implements Runnable
|
||||
l.accountStatusChanged(account, stats);
|
||||
}
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to clear account because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "Failed to clear account " + account.getDescription(), e);
|
||||
@ -4508,6 +4595,11 @@ public class MessagingController implements Runnable
|
||||
l.accountStatusChanged(account, stats);
|
||||
}
|
||||
}
|
||||
catch (UnavailableStorageException e)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "Failed to recreate an account because storage is not available - trying again later.");
|
||||
throw new UnavailableAccountException(e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "Failed to recreate account " + account.getDescription(), e);
|
||||
|
@ -5,6 +5,7 @@ import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.fsck.k9.activity.MessageReference;
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
|
||||
public abstract class Message implements Part, Body
|
||||
{
|
||||
@ -140,7 +141,7 @@ public abstract class Message implements Part, Body
|
||||
|
||||
public abstract String[] getHeader(String name) throws MessagingException;
|
||||
|
||||
public abstract Set<String> getHeaderNames();
|
||||
public abstract Set<String> getHeaderNames() throws UnavailableStorageException;
|
||||
|
||||
public abstract void removeHeader(String name) throws MessagingException;
|
||||
|
||||
@ -204,7 +205,7 @@ public abstract class Message implements Part, Body
|
||||
|
||||
public abstract void saveChanges() throws MessagingException;
|
||||
|
||||
public abstract void setEncoding(String encoding);
|
||||
public abstract void setEncoding(String encoding) throws UnavailableStorageException;
|
||||
|
||||
public MessageReference makeMessageReference()
|
||||
{
|
||||
|
@ -2,12 +2,14 @@
|
||||
package com.fsck.k9.mail;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.mail.store.ImapStore;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.Pop3Store;
|
||||
import com.fsck.k9.mail.store.WebDavStore;
|
||||
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -25,7 +27,14 @@ public abstract class Store
|
||||
protected static final int SOCKET_CONNECT_TIMEOUT = 30000;
|
||||
protected static final int SOCKET_READ_TIMEOUT = 60000;
|
||||
|
||||
/**
|
||||
* Remote stores indexed by Uri.
|
||||
*/
|
||||
private static HashMap<String, Store> mStores = new HashMap<String, Store>();
|
||||
/**
|
||||
* Local stores indexed by UUid because the Uri may change due to migration to/from SD-card.
|
||||
*/
|
||||
private static HashMap<String, Store> mLocalStores = new HashMap<String, Store>();
|
||||
|
||||
protected final Account mAccount;
|
||||
|
||||
@ -78,33 +87,18 @@ public abstract class Store
|
||||
|
||||
/**
|
||||
* Get an instance of a local mail store.
|
||||
* @throws UnavailableStorageException if not {@link StorageProvider#isReady(Context)}
|
||||
*/
|
||||
public synchronized static LocalStore getLocalInstance(Account account, Application application) throws MessagingException
|
||||
{
|
||||
String uri = account.getLocalStoreUri();
|
||||
|
||||
if (!uri.startsWith("local"))
|
||||
{
|
||||
throw new RuntimeException("LocalStore URI doesn't start with 'local'");
|
||||
}
|
||||
|
||||
Store store = mStores.get(uri);
|
||||
Store store = mLocalStores.get(account.getUuid());
|
||||
if (store == null)
|
||||
{
|
||||
store = new LocalStore(account, application);
|
||||
|
||||
if (store != null)
|
||||
{
|
||||
mStores.put(uri, store);
|
||||
}
|
||||
mLocalStores.put(account.getUuid(), store);
|
||||
}
|
||||
|
||||
if (store == null)
|
||||
{
|
||||
throw new MessagingException("Unable to locate an applicable Store for " + uri);
|
||||
}
|
||||
|
||||
return (LocalStore)store;
|
||||
return (LocalStore) store;
|
||||
}
|
||||
|
||||
public abstract Folder getFolder(String name) throws MessagingException;
|
||||
|
@ -2,6 +2,8 @@
|
||||
package com.fsck.k9.mail.internet;
|
||||
|
||||
import com.fsck.k9.mail.*;
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
|
||||
import org.apache.james.mime4j.BodyDescriptor;
|
||||
import org.apache.james.mime4j.ContentHandler;
|
||||
import org.apache.james.mime4j.EOLConvertingInputStream;
|
||||
@ -340,7 +342,7 @@ public class MimeMessage extends Message
|
||||
return "<"+UUID.randomUUID().toString()+"@email.android.com>";
|
||||
}
|
||||
|
||||
public void setMessageId(String messageId)
|
||||
public void setMessageId(String messageId) throws UnavailableStorageException
|
||||
{
|
||||
setHeader("Message-ID", messageId);
|
||||
mMessageId = messageId;
|
||||
@ -438,31 +440,31 @@ public class MimeMessage extends Message
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value)
|
||||
public void addHeader(String name, String value) throws UnavailableStorageException
|
||||
{
|
||||
mHeader.addHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
public void setHeader(String name, String value) throws UnavailableStorageException
|
||||
{
|
||||
mHeader.setHeader(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getHeader(String name)
|
||||
public String[] getHeader(String name) throws UnavailableStorageException
|
||||
{
|
||||
return mHeader.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHeader(String name)
|
||||
public void removeHeader(String name) throws UnavailableStorageException
|
||||
{
|
||||
mHeader.removeHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getHeaderNames()
|
||||
public Set<String> getHeaderNames() throws UnavailableStorageException
|
||||
{
|
||||
return mHeader.getHeaderNames();
|
||||
}
|
||||
@ -486,7 +488,7 @@ public class MimeMessage extends Message
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncoding(String encoding)
|
||||
public void setEncoding(String encoding) throws UnavailableStorageException
|
||||
{
|
||||
if (mBody instanceof Multipart)
|
||||
{
|
||||
|
File diff suppressed because it is too large
Load Diff
830
src/com/fsck/k9/mail/store/StorageManager.java
Normal file
830
src/com/fsck/k9/mail/store/StorageManager.java
Normal file
@ -0,0 +1,830 @@
|
||||
package com.fsck.k9.mail.store;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.R;
|
||||
import com.fsck.k9.service.MailService;
|
||||
|
||||
/**
|
||||
* Manager for different {@link StorageProvider} -classes that abstract access
|
||||
* to sd-cards, additional internal memory and other storage-locations.
|
||||
*/
|
||||
public class StorageManager
|
||||
{
|
||||
|
||||
/**
|
||||
* Provides entry points (File objects) to an underlying storage,
|
||||
* alleviating the caller from having to know where that storage is located.
|
||||
*
|
||||
* <p>
|
||||
* Allow checking for the denoted storage availability since its lifecycle
|
||||
* can evolving (a storage might become unavailable at some time and be back
|
||||
* online later).
|
||||
* </p>
|
||||
*/
|
||||
public static interface StorageProvider
|
||||
{
|
||||
|
||||
/**
|
||||
* Retrieve the uniquely identifier for the current implementation.
|
||||
*
|
||||
* <p>
|
||||
* It is expected that the identifier doesn't change over reboots since
|
||||
* it'll be used to save settings and retrieve the provider at a later
|
||||
* time.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The returned identifier doesn't have to be user friendly.
|
||||
* </p>
|
||||
*
|
||||
* @return Never <code>null</code>.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Hook point for provider initialization.
|
||||
*
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
*/
|
||||
void init(Context context);
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
* @return A user displayable, localized name for this provider. Never
|
||||
* <code>null</code>.
|
||||
*/
|
||||
String getName(Context context);
|
||||
|
||||
/**
|
||||
* Some implementations may not be able to return valid File handles
|
||||
* because the device doesn't provide the denoted storage. You can check
|
||||
* the provider compatibility with this method to prevent from having to
|
||||
* invoke this provider ever again.
|
||||
*
|
||||
* @param context
|
||||
* TODO
|
||||
* @return Whether this provider supports the current device.
|
||||
* @see StorageManager#getAvailableProviders()
|
||||
*/
|
||||
boolean isSupported(Context context);
|
||||
|
||||
/**
|
||||
* Return the {@link File} to the choosen email database file. The
|
||||
* resulting {@link File} doesn't necessarily match an existing file on
|
||||
* the filesystem.
|
||||
*
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
* @param id
|
||||
* Never <code>null</code>.
|
||||
* @return Never <code>null</code>.
|
||||
*/
|
||||
File getDatabase(Context context, String id);
|
||||
|
||||
/**
|
||||
* Return the {@link File} to the choosen attachment directory. The
|
||||
* resulting {@link File} doesn't necessarily match an existing
|
||||
* directory on the filesystem.
|
||||
*
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
* @param id
|
||||
* Never <code>null</code>.
|
||||
* @return Never <code>null</code>.
|
||||
*/
|
||||
File getAttachmentDirectory(Context context, String id);
|
||||
|
||||
/**
|
||||
* Check for the underlying storage availability.
|
||||
*
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
* @return Whether the underlying storage returned by this provider is
|
||||
* ready for read/write operations at the time of invokation.
|
||||
*/
|
||||
boolean isReady(Context context);
|
||||
|
||||
/**
|
||||
* Retrieve the root of the underlying storage.
|
||||
*
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
* @return The root directory of the denoted storage. Never
|
||||
* <code>null</code>.
|
||||
*/
|
||||
File getRoot(Context context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for components wanting to be notified of storage availability
|
||||
* events.
|
||||
*/
|
||||
public static interface StorageListener
|
||||
{
|
||||
/**
|
||||
* Invoked on storage mount (with read/write access).
|
||||
*
|
||||
* @param providerId
|
||||
* Identifier (as returned by {@link StorageProvider#getId()}
|
||||
* of the newly mounted storage. Never <code>null</code>.
|
||||
*/
|
||||
void onMount(String providerId);
|
||||
|
||||
/**
|
||||
* Invoked when a storage is about to be unmounted.
|
||||
*
|
||||
* @param providerId
|
||||
* Identifier (as returned by {@link StorageProvider#getId()}
|
||||
* of the to-be-unmounted storage. Never <code>null</code>.
|
||||
*/
|
||||
void onUnmount(String providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base provider class for providers that rely on well-known path to check
|
||||
* for storage availability.
|
||||
*
|
||||
* <p>
|
||||
* Since solely checking for paths can be unsafe, this class allows to check
|
||||
* for device compatibility using {@link #supportsVendor()}. If the vendor
|
||||
* specific check fails, the provider won't be able to provide any valid
|
||||
* File handle, regardless of the path existence.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Moreover, this class validates the denoted storage path matches against
|
||||
* mount points using {@link StorageManager#isMountPoint(File)}.
|
||||
* </p>
|
||||
*/
|
||||
public abstract static class FixedStorageProviderBase implements StorageProvider
|
||||
{
|
||||
/**
|
||||
* The root of the denoted storage. Used for mount points checking.
|
||||
*/
|
||||
protected File mRoot;
|
||||
|
||||
/**
|
||||
* Choosen base directory
|
||||
*/
|
||||
protected File mApplicationDir;
|
||||
|
||||
@Override
|
||||
public void init(final Context context)
|
||||
{
|
||||
mRoot = computeRoot(context);
|
||||
// use <STORAGE_ROOT>/k9
|
||||
mApplicationDir = new File(mRoot, "k9");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor specific checks
|
||||
*
|
||||
* @return Whether this provider supports the underlying vendor specific
|
||||
* storage
|
||||
*/
|
||||
protected abstract boolean supportsVendor();
|
||||
|
||||
@Override
|
||||
public boolean isReady(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
final File root = mRoot.getCanonicalFile();
|
||||
return isMountPoint(root)
|
||||
&& Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.w(K9.LOG_TAG, "Specified root isn't ready: " + mRoot, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isSupported(Context context)
|
||||
{
|
||||
return mRoot.isDirectory() && supportsVendor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabase(Context context, String id)
|
||||
{
|
||||
return new File(mApplicationDir, id + ".db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAttachmentDirectory(Context context, String id)
|
||||
{
|
||||
return new File(mApplicationDir, id + ".db_att");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final File getRoot(Context context)
|
||||
{
|
||||
return mRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the well-known storage root directory from the actual
|
||||
* implementation.
|
||||
*
|
||||
* @param context
|
||||
* Never <code>null</code>.
|
||||
* @return Never <code>null</code>.
|
||||
*/
|
||||
protected abstract File computeRoot(Context context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy to access the always available internal storage.
|
||||
*
|
||||
* <p>
|
||||
* This implementation is expected to work on every device since it's based
|
||||
* on the regular Android API {@link Context#getDatabasePath(String)} and
|
||||
* uses the resul to retrieve the DB path and the attachment directory path.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The underlying storage has always been used by K-9.
|
||||
* </p>
|
||||
*/
|
||||
public static class InternalStorageProvider implements StorageProvider
|
||||
{
|
||||
|
||||
public static final String ID = "InternalStorage";
|
||||
|
||||
protected File mRoot;
|
||||
|
||||
@Override
|
||||
public String getId()
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Context context)
|
||||
{
|
||||
// XXX
|
||||
mRoot = new File("/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(Context context)
|
||||
{
|
||||
return context.getString(R.string.local_storage_provider_internal_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Context context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabase(Context context, String id)
|
||||
{
|
||||
return context.getDatabasePath(id + ".db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAttachmentDirectory(Context context, String id)
|
||||
{
|
||||
// we store attachments in the database directory
|
||||
return context.getDatabasePath(id + ".db_att");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady(Context context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRoot(Context context)
|
||||
{
|
||||
return mRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy for accessing the storage as returned by
|
||||
* {@link Environment#getExternalStorageDirectory()}. In order to be
|
||||
* compliant with Android recommendation regarding application uninstalling
|
||||
* and to prevent from cluttering the storage root, the choosen directory
|
||||
* will be
|
||||
* <code><STORAGE_ROOT>/Android/data/<APPLICATION_PACKAGE_NAME>/files/</code>
|
||||
*
|
||||
* <p>
|
||||
* The denoted storage is usually a SD card.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This provider is expected to work on all devices but the returned
|
||||
* underlying storage might not be always available, due to
|
||||
* mount/unmount/USB share events.
|
||||
* </p>
|
||||
*/
|
||||
public static class ExternalStorageProvider implements StorageProvider
|
||||
{
|
||||
|
||||
public static final String ID = "ExternalStorage";
|
||||
|
||||
/**
|
||||
* Root of the denoted storage.
|
||||
*/
|
||||
protected File mRoot;
|
||||
|
||||
/**
|
||||
* Choosen base directory.
|
||||
*/
|
||||
protected File mApplicationDirectory;
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Context context)
|
||||
{
|
||||
mRoot = Environment.getExternalStorageDirectory();
|
||||
mApplicationDirectory = new File(new File(new File(new File(mRoot, "Android"), "data"),
|
||||
context.getPackageName()), "files");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(Context context)
|
||||
{
|
||||
return context.getString(R.string.local_storage_provider_external_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Context context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDatabase(Context context, String id)
|
||||
{
|
||||
return new File(mApplicationDirectory, id + ".db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAttachmentDirectory(Context context, String id)
|
||||
{
|
||||
return new File(mApplicationDirectory, id + ".db_att");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady(Context context)
|
||||
{
|
||||
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRoot(Context context)
|
||||
{
|
||||
return mRoot;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage provider to allow access the /emmc directory on a HTC Incredible.
|
||||
*
|
||||
* <p>
|
||||
* This implementation is experimental and untested.
|
||||
* </p>
|
||||
*
|
||||
* See http://groups.google.com/group/android-developers/browse_frm/thread/96f15e57150ed173
|
||||
*
|
||||
* @see FixedStorageProviderBase
|
||||
*/
|
||||
public static class HtcIncredibleStorageProvider extends FixedStorageProviderBase
|
||||
{
|
||||
|
||||
public static final String ID = "HtcIncredibleStorage";
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(Context context)
|
||||
{
|
||||
return context.getString(R.string.local_storage_provider_samsunggalaxy_label,
|
||||
Build.MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsVendor()
|
||||
{
|
||||
return "inc".equals(Build.DEVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File computeRoot(Context context)
|
||||
{
|
||||
return new File("/emmc");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage provider to allow access the /emmc directory on a Samsung Galaxy S.
|
||||
*
|
||||
* <p>
|
||||
* This implementation is experimental and untested.
|
||||
* </p>
|
||||
*
|
||||
* See http://groups.google.com/group/android-developers/browse_frm/thread/a1adf7122a75a657
|
||||
*
|
||||
* @see FixedStorageProviderBase
|
||||
*/
|
||||
public static class SamsungGalaxySStorageProvider extends FixedStorageProviderBase
|
||||
{
|
||||
|
||||
public static final String ID = "SamsungGalaxySStorage";
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(Context context)
|
||||
{
|
||||
return context.getString(R.string.local_storage_provider_samsunggalaxy_label,
|
||||
Build.MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsVendor()
|
||||
{
|
||||
// FIXME
|
||||
return "GT-I5800".equals(Build.DEVICE) || "GT-I9000".equals(Build.DEVICE)
|
||||
|| "SGH-T959".equals(Build.DEVICE) || "SGH-I897".equals(Build.DEVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File computeRoot(Context context)
|
||||
{
|
||||
return Environment.getExternalStorageDirectory(); // was: new
|
||||
// File("/sdcard")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores storage provider locking informations
|
||||
*/
|
||||
public static class SynchronizationAid
|
||||
{
|
||||
/**
|
||||
* {@link Lock} has a thread semantic so it can't be released from
|
||||
* another thread - this flags act as a holder for the unmount state
|
||||
*/
|
||||
public boolean unmounting = false;
|
||||
|
||||
public final Lock readLock;
|
||||
|
||||
public final Lock writeLock;
|
||||
|
||||
{
|
||||
final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
|
||||
readLock = readWriteLock.readLock();
|
||||
writeLock = readWriteLock.writeLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The active storage providers.
|
||||
*/
|
||||
private final Map<String, StorageProvider> mProviders = new LinkedHashMap<String, StorageProvider>();
|
||||
|
||||
/**
|
||||
* Locking data for the active storage providers.
|
||||
*/
|
||||
private final Map<StorageProvider, SynchronizationAid> mProviderLocks = new IdentityHashMap<StorageProvider, SynchronizationAid>();
|
||||
|
||||
protected final Application mApplication;
|
||||
|
||||
/**
|
||||
* Listener to be notified for storage related events.
|
||||
*/
|
||||
private List<StorageListener> mListeners = new ArrayList<StorageListener>();
|
||||
|
||||
private static transient StorageManager instance;
|
||||
|
||||
public static synchronized StorageManager getInstance(final Application application)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new StorageManager(application);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file
|
||||
* Canonical file to match. Never <code>null</code>.
|
||||
* @return Whether the specified file matches a filesystem root.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static boolean isMountPoint(final File file) throws IOException
|
||||
{
|
||||
for (final File root : File.listRoots())
|
||||
{
|
||||
if (root.equals(file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param application
|
||||
* Never <code>null</code>.
|
||||
* @throws NullPointerException
|
||||
* If <tt>application</tt> is <code>null</code>.
|
||||
*/
|
||||
protected StorageManager(final Application application) throws NullPointerException
|
||||
{
|
||||
if (application == null)
|
||||
{
|
||||
throw new NullPointerException("No application instance given");
|
||||
}
|
||||
|
||||
mApplication = application;
|
||||
|
||||
/*
|
||||
* 20101113/fiouzy:
|
||||
*
|
||||
* Here is where we define which providers are used, currently we only
|
||||
* allow the internal storage and the regular external storage.
|
||||
*
|
||||
* HTC Incredible storage and Samsung Galaxy S are omitted on purpose
|
||||
* (they're experimental and I don't have those devices to test).
|
||||
*/
|
||||
final List<StorageProvider> allProviders = Arrays.asList(new InternalStorageProvider(),
|
||||
new ExternalStorageProvider());
|
||||
for (final StorageProvider provider : allProviders)
|
||||
{
|
||||
// check for provider compatibility
|
||||
if (provider.isSupported(mApplication))
|
||||
{
|
||||
// provider is compatible! proceeding
|
||||
|
||||
provider.init(application);
|
||||
mProviders.put(provider.getId(), provider);
|
||||
mProviderLocks.put(provider, new SynchronizationAid());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Never <code>null</code>.
|
||||
*/
|
||||
public String getDefaultProviderId()
|
||||
{
|
||||
// assume there is at least 1 provider defined
|
||||
return mProviders.entrySet().iterator().next().getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param providerId
|
||||
* Never <code>null</code>.
|
||||
* @return <code>null</code> if not found.
|
||||
*/
|
||||
protected StorageProvider getProvider(final String providerId)
|
||||
{
|
||||
return mProviders.get(providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dbName
|
||||
* Never <code>null</code>.
|
||||
* @param providerId
|
||||
* Never <code>null</code>.
|
||||
* @return The resolved database file for the given provider ID.
|
||||
*/
|
||||
public File getDatabase(final String dbName, final String providerId)
|
||||
{
|
||||
StorageProvider provider = getProvider(providerId);
|
||||
// TODO fallback to internal storage if no provider
|
||||
return provider.getDatabase(mApplication, dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dbName
|
||||
* Never <code>null</code>.
|
||||
* @param providerId
|
||||
* Never <code>null</code>.
|
||||
* @return The resolved attachement directory for the given provider ID.
|
||||
*/
|
||||
public File getAttachmentDirectory(final String dbName, final String providerId)
|
||||
{
|
||||
StorageProvider provider = getProvider(providerId);
|
||||
// TODO fallback to internal storage if no provider
|
||||
return provider.getAttachmentDirectory(mApplication, dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param providerId
|
||||
* Never <code>null</code>.
|
||||
* @return Whether the specified provider is ready for read/write operations
|
||||
*/
|
||||
public boolean isReady(final String providerId)
|
||||
{
|
||||
StorageProvider provider = getProvider(providerId);
|
||||
if (provider == null)
|
||||
{
|
||||
Log.w(K9.LOG_TAG, "Storage-Provider \"" + providerId + "\" does not exist");
|
||||
return false;
|
||||
}
|
||||
return provider.isReady(mApplication);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map of available providers names, indexed by their ID. Never
|
||||
* <code>null</code>.
|
||||
* @see StorageManager
|
||||
* @see StorageProvider#isSupported(Context)
|
||||
*/
|
||||
public Map<String, String> getAvailableProviders()
|
||||
{
|
||||
final Map<String, String> result = new LinkedHashMap<String, String>();
|
||||
for (final Map.Entry<String, StorageProvider> entry : mProviders.entrySet())
|
||||
{
|
||||
result.put(entry.getKey(), entry.getValue().getName(mApplication));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
*/
|
||||
public void onBeforeUnmount(final String path)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "storage path \"" + path + "\" unmounting");
|
||||
final StorageProvider provider = resolveProvider(path);
|
||||
if (provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (final StorageListener listener : mListeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onUnmount(provider.getId());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.w(K9.LOG_TAG, "Error while notifying StorageListener", e);
|
||||
}
|
||||
}
|
||||
final SynchronizationAid sync = mProviderLocks.get(resolveProvider(path));
|
||||
sync.writeLock.lock();
|
||||
sync.unmounting = true;
|
||||
sync.writeLock.unlock();
|
||||
}
|
||||
|
||||
public void onAfterUnmount(final String path)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "storage path \"" + path + "\" unmounted");
|
||||
final StorageProvider provider = resolveProvider(path);
|
||||
if (provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
final SynchronizationAid sync = mProviderLocks.get(resolveProvider(path));
|
||||
sync.writeLock.lock();
|
||||
sync.unmounting = false;
|
||||
sync.writeLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* @param readOnly
|
||||
*/
|
||||
public void onMount(final String path, final boolean readOnly)
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "storage path \"" + path + "\" mounted readOnly=" + readOnly);
|
||||
if (readOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final StorageProvider provider = resolveProvider(path);
|
||||
if (provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (final StorageListener listener : mListeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onMount(provider.getId());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.w(K9.LOG_TAG, "Error while notifying StorageListener", e);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX we should reset mail service ONLY if there are accounts using the storage (this is not done in a regular listener because it has to be invoked afterward)
|
||||
MailService.actionReset(mApplication, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* Never <code>null</code>.
|
||||
* @return The corresponding provider. <code>null</code> if no match.
|
||||
*/
|
||||
protected StorageProvider resolveProvider(final String path)
|
||||
{
|
||||
for (final StorageProvider provider : mProviders.values())
|
||||
{
|
||||
if (path.equals(provider.getRoot(mApplication).getAbsolutePath()))
|
||||
{
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addListener(final StorageListener listener)
|
||||
{
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(final StorageListener listener)
|
||||
{
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to lock the underlying storage to prevent concurrent unmount.
|
||||
*
|
||||
* <p>
|
||||
* You must invoke {@link #unlockProvider(String)} when you're done with the
|
||||
* storage.
|
||||
* </p>
|
||||
*
|
||||
* @param providerId
|
||||
* @throws UnavailableStorageException
|
||||
* If the storage can't be locked.
|
||||
*/
|
||||
public void lockProvider(final String providerId) throws UnavailableStorageException
|
||||
{
|
||||
final StorageProvider provider = getProvider(providerId);
|
||||
if (provider == null)
|
||||
{
|
||||
throw new UnavailableStorageException("StorageProvider not found: " + providerId);
|
||||
}
|
||||
// lock provider
|
||||
final SynchronizationAid sync = mProviderLocks.get(provider);
|
||||
final boolean locked = sync.readLock.tryLock();
|
||||
if (!locked || (locked && sync.unmounting))
|
||||
{
|
||||
if (locked)
|
||||
{
|
||||
sync.readLock.unlock();
|
||||
}
|
||||
throw new UnavailableStorageException("StorageProvider is unmounting");
|
||||
}
|
||||
else if (locked && !provider.isReady(mApplication))
|
||||
{
|
||||
sync.readLock.unlock();
|
||||
throw new UnavailableStorageException("StorageProvider not ready");
|
||||
}
|
||||
}
|
||||
|
||||
public void unlockProvider(final String providerId)
|
||||
{
|
||||
final StorageProvider provider = getProvider(providerId);
|
||||
final SynchronizationAid sync = mProviderLocks.get(provider);
|
||||
sync.readLock.unlock();
|
||||
}
|
||||
}
|
47
src/com/fsck/k9/mail/store/UnavailableAccountException.java
Normal file
47
src/com/fsck/k9/mail/store/UnavailableAccountException.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.fsck.k9.mail.store;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
|
||||
/**
|
||||
* An {@link Account} is not
|
||||
* {@link Account#isAvailable(android.content.Context)}.<br/>
|
||||
* The operation may be retried later.
|
||||
*/
|
||||
public class UnavailableAccountException extends RuntimeException
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -1827283277120501465L;
|
||||
|
||||
public UnavailableAccountException()
|
||||
{
|
||||
super("please try again later");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param detailMessage
|
||||
* @param throwable
|
||||
*/
|
||||
public UnavailableAccountException(String detailMessage, Throwable throwable)
|
||||
{
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param detailMessage
|
||||
*/
|
||||
public UnavailableAccountException(String detailMessage)
|
||||
{
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param throwable
|
||||
*/
|
||||
public UnavailableAccountException(Throwable throwable)
|
||||
{
|
||||
super(throwable);
|
||||
}
|
||||
}
|
32
src/com/fsck/k9/mail/store/UnavailableStorageException.java
Normal file
32
src/com/fsck/k9/mail/store/UnavailableStorageException.java
Normal file
@ -0,0 +1,32 @@
|
||||
package com.fsck.k9.mail.store;
|
||||
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
|
||||
public class UnavailableStorageException extends MessagingException
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 1348267375054620792L;
|
||||
|
||||
public UnavailableStorageException(String message)
|
||||
{
|
||||
// consider this exception as a permanent failure by default
|
||||
this(message, true);
|
||||
}
|
||||
|
||||
public UnavailableStorageException(String message, boolean perm)
|
||||
{
|
||||
super(message, perm);
|
||||
}
|
||||
|
||||
public UnavailableStorageException(String message, Throwable throwable)
|
||||
{
|
||||
// consider this exception as permanent failure by default
|
||||
this(message, true, throwable);
|
||||
}
|
||||
|
||||
public UnavailableStorageException(String message, boolean perm, Throwable throwable)
|
||||
{
|
||||
super(message, perm, throwable);
|
||||
}
|
||||
|
||||
}
|
@ -10,17 +10,22 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.Preferences;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import com.fsck.k9.mail.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.LocalStore.AttachmentInfo;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* A simple ContentProvider that allows file access to Email's attachments.
|
||||
/**
|
||||
* A simple ContentProvider that allows file access to Email's attachments.<br/>
|
||||
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an {@link Account} here.
|
||||
*/
|
||||
public class AttachmentProvider extends ContentProvider
|
||||
{
|
||||
@ -39,13 +44,13 @@ public class AttachmentProvider extends ContentProvider
|
||||
|
||||
public static Uri getAttachmentUri(Account account, long id)
|
||||
{
|
||||
return getAttachmentUri(account.getUuid() + ".db" , id);
|
||||
return getAttachmentUri(account.getUuid(), id);
|
||||
}
|
||||
|
||||
public static Uri getAttachmentThumbnailUri(Account account, long id, int width, int height)
|
||||
{
|
||||
return CONTENT_URI.buildUpon()
|
||||
.appendPath(account.getUuid() + ".db")
|
||||
.appendPath(account.getUuid())
|
||||
.appendPath(Long.toString(id))
|
||||
.appendPath(FORMAT_THUMBNAIL)
|
||||
.appendPath(Integer.toString(width))
|
||||
@ -53,7 +58,7 @@ public class AttachmentProvider extends ContentProvider
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getAttachmentUri(String db, long id)
|
||||
private static Uri getAttachmentUri(String db, long id)
|
||||
{
|
||||
return CONTENT_URI.buildUpon()
|
||||
.appendPath(db)
|
||||
@ -113,43 +118,17 @@ public class AttachmentProvider extends ContentProvider
|
||||
}
|
||||
else
|
||||
{
|
||||
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
|
||||
SQLiteDatabase db = null;
|
||||
Cursor cursor = null;
|
||||
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
||||
|
||||
try
|
||||
{
|
||||
db = SQLiteDatabase.openDatabase(path, null, 0);
|
||||
cursor = db.query(
|
||||
"attachments",
|
||||
new String[] { "mime_type", "name" },
|
||||
"id = ?",
|
||||
new String[] { id },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
cursor.moveToFirst();
|
||||
String type = cursor.getString(0);
|
||||
String name = cursor.getString(1);
|
||||
cursor.close();
|
||||
db.close();
|
||||
|
||||
if (MimeUtility.DEFAULT_ATTACHMENT_MIME_TYPE.equals(type))
|
||||
{
|
||||
type = MimeUtility.getMimeTypeByExtension(name);
|
||||
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
|
||||
return localStore.getAttachmentType(id);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
finally
|
||||
catch (MessagingException e)
|
||||
{
|
||||
if (cursor != null)
|
||||
{
|
||||
cursor.close();
|
||||
}
|
||||
if (db != null)
|
||||
{
|
||||
db.close();
|
||||
}
|
||||
|
||||
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,15 +138,14 @@ public class AttachmentProvider extends ContentProvider
|
||||
{
|
||||
try
|
||||
{
|
||||
File attachmentsDir = getContext().getDatabasePath(dbName + "_att");
|
||||
File file = new File(attachmentsDir, id);
|
||||
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
||||
final File attachmentsDir;
|
||||
attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
|
||||
account.getLocalStorageProviderId());
|
||||
final File file = new File(attachmentsDir, id);
|
||||
if (!file.exists())
|
||||
{
|
||||
file = new File(Environment.getExternalStorageDirectory() + attachmentsDir.getCanonicalPath().substring("/data".length()), id);
|
||||
if (!file.exists())
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
throw new FileNotFoundException(file.getAbsolutePath());
|
||||
}
|
||||
return file;
|
||||
}
|
||||
@ -182,7 +160,7 @@ public class AttachmentProvider extends ContentProvider
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
|
||||
{
|
||||
List<String> segments = uri.getPathSegments();
|
||||
String dbName = segments.get(0);
|
||||
String dbName = segments.get(0); // "/sdcard/..." is URL-encoded and makes up only 1 segment
|
||||
String id = segments.get(1);
|
||||
String format = segments.get(2);
|
||||
if (FORMAT_THUMBNAIL.equals(format))
|
||||
@ -190,6 +168,11 @@ public class AttachmentProvider extends ContentProvider
|
||||
int width = Integer.parseInt(segments.get(3));
|
||||
int height = Integer.parseInt(segments.get(4));
|
||||
String filename = "thmb_" + dbName + "_" + id + ".tmp";
|
||||
int index = dbName.lastIndexOf('/');
|
||||
if (index >= 0)
|
||||
{
|
||||
filename = /*dbName.substring(0, index + 1) + */"thmb_" + dbName.substring(index + 1) + "_" + id + ".tmp";
|
||||
}
|
||||
File dir = getContext().getCacheDir();
|
||||
File file = new File(dir, filename);
|
||||
if (!file.exists())
|
||||
@ -199,13 +182,22 @@ public class AttachmentProvider extends ContentProvider
|
||||
try
|
||||
{
|
||||
FileInputStream in = new FileInputStream(getFile(dbName, id));
|
||||
try
|
||||
{
|
||||
Bitmap thumbnail = createThumbnail(type, in);
|
||||
if (thumbnail != null)
|
||||
{
|
||||
thumbnail = Bitmap.createScaledBitmap(thumbnail, width, height, true);
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
return null;
|
||||
@ -251,40 +243,17 @@ public class AttachmentProvider extends ContentProvider
|
||||
String dbName = segments.get(0);
|
||||
String id = segments.get(1);
|
||||
//String format = segments.get(2);
|
||||
String path = getContext().getDatabasePath(dbName).getAbsolutePath();
|
||||
String name = null;
|
||||
int size = -1;
|
||||
SQLiteDatabase db = null;
|
||||
Cursor cursor = null;
|
||||
final AttachmentInfo attachmentInfo;
|
||||
try
|
||||
{
|
||||
db = SQLiteDatabase.openDatabase(path, null, 0);
|
||||
cursor = db.query(
|
||||
"attachments",
|
||||
new String[] { "name", "size" },
|
||||
"id = ?",
|
||||
new String[] { id },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
if (!cursor.moveToFirst())
|
||||
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
|
||||
attachmentInfo = LocalStore.getLocalInstance(account, K9.app).getAttachmentInfo(id);
|
||||
}
|
||||
catch (MessagingException e)
|
||||
{
|
||||
Log.e(K9.LOG_TAG, "Uname to retrieve attachment info from local store for ID: " + id, e);
|
||||
return null;
|
||||
}
|
||||
name = cursor.getString(0);
|
||||
size = cursor.getInt(1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cursor != null)
|
||||
{
|
||||
cursor.close();
|
||||
}
|
||||
if (db != null)
|
||||
{
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
MatrixCursor ret = new MatrixCursor(projection);
|
||||
Object[] values = new Object[projection.length];
|
||||
@ -301,11 +270,11 @@ public class AttachmentProvider extends ContentProvider
|
||||
}
|
||||
else if (AttachmentProviderColumns.DISPLAY_NAME.equals(column))
|
||||
{
|
||||
values[i] = name;
|
||||
values[i] = attachmentInfo.name;
|
||||
}
|
||||
else if (AttachmentProviderColumns.SIZE.equals(column))
|
||||
{
|
||||
values[i] = size;
|
||||
values[i] = attachmentInfo.size;
|
||||
}
|
||||
}
|
||||
ret.addRow(values);
|
||||
|
@ -426,7 +426,7 @@ public class MessageProvider extends ContentProvider
|
||||
|
||||
Object[] values = new Object[2];
|
||||
|
||||
for (Account account : Preferences.getPreferences(getContext()).getAccounts())
|
||||
for (Account account : Preferences.getPreferences(getContext()).getAvailableAccounts())
|
||||
{
|
||||
if (account.getAccountNumber()==accountNumber)
|
||||
{
|
||||
@ -435,7 +435,15 @@ public class MessageProvider extends ContentProvider
|
||||
{
|
||||
myAccountStats = account.getStats(getContext());
|
||||
values[0] = myAccount.getDescription();
|
||||
if (myAccountStats == null)
|
||||
{
|
||||
values[1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
values[1] = myAccountStats.unreadMessageCount;
|
||||
}
|
||||
|
||||
ret.addRow(values);
|
||||
}
|
||||
catch (MessagingException e)
|
||||
@ -1024,6 +1032,11 @@ public class MessageProvider extends ContentProvider
|
||||
if (account.getAccountNumber() == accountId)
|
||||
{
|
||||
myAccount = account;
|
||||
if (!account.isAvailable(getContext()))
|
||||
{
|
||||
Log.w(K9.LOG_TAG, "not deleting messages because account is unavailable at the moment");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,27 +30,28 @@ public class BootReceiver extends CoreReceiver
|
||||
if (K9.DEBUG)
|
||||
Log.i(K9.LOG_TAG, "BootReceiver.onReceive" + intent);
|
||||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()))
|
||||
final String action = intent.getAction();
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(action))
|
||||
{
|
||||
//K9.setServicesEnabled(context, tmpWakeLockId);
|
||||
//tmpWakeLockId = null;
|
||||
}
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction()))
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action))
|
||||
{
|
||||
MailService.actionCancel(context, tmpWakeLockId);
|
||||
tmpWakeLockId = null;
|
||||
}
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction()))
|
||||
else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action))
|
||||
{
|
||||
MailService.actionReset(context, tmpWakeLockId);
|
||||
tmpWakeLockId = null;
|
||||
}
|
||||
else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction()))
|
||||
else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action))
|
||||
{
|
||||
MailService.connectivityChange(context, tmpWakeLockId);
|
||||
tmpWakeLockId = null;
|
||||
}
|
||||
else if (AutoSyncHelper.SYNC_CONN_STATUS_CHANGE.equals(intent.getAction()))
|
||||
else if (AutoSyncHelper.SYNC_CONN_STATUS_CHANGE.equals(action))
|
||||
{
|
||||
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
|
||||
if (bOps == K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC)
|
||||
@ -59,7 +60,7 @@ public class BootReceiver extends CoreReceiver
|
||||
tmpWakeLockId = null;
|
||||
}
|
||||
}
|
||||
else if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(intent.getAction()))
|
||||
else if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(action))
|
||||
{
|
||||
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
|
||||
if (bOps == K9.BACKGROUND_OPS.WHEN_CHECKED || bOps == K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC)
|
||||
@ -68,7 +69,7 @@ public class BootReceiver extends CoreReceiver
|
||||
tmpWakeLockId = null;
|
||||
}
|
||||
}
|
||||
else if (FIRE_INTENT.equals(intent.getAction()))
|
||||
else if (FIRE_INTENT.equals(action))
|
||||
{
|
||||
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
|
||||
String alarmedAction = alarmedIntent.getAction();
|
||||
@ -81,7 +82,7 @@ public class BootReceiver extends CoreReceiver
|
||||
context.startService(alarmedIntent);
|
||||
}
|
||||
}
|
||||
else if (SCHEDULE_INTENT.equals(intent.getAction()))
|
||||
else if (SCHEDULE_INTENT.equals(action))
|
||||
{
|
||||
long atTime = intent.getLongExtra(AT_TIME, -1);
|
||||
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
|
||||
@ -93,7 +94,7 @@ public class BootReceiver extends CoreReceiver
|
||||
|
||||
alarmMgr.set(AlarmManager.RTC_WAKEUP, atTime, pi);
|
||||
}
|
||||
else if (CANCEL_INTENT.equals(intent.getAction()))
|
||||
else if (CANCEL_INTENT.equals(action))
|
||||
{
|
||||
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
|
||||
if (K9.DEBUG)
|
||||
@ -146,4 +147,24 @@ public class BootReceiver extends CoreReceiver
|
||||
context.sendBroadcast(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel any scheduled alarm.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public static void purgeSchedule(final Context context)
|
||||
{
|
||||
final AlarmManager alarmService = (AlarmManager) context
|
||||
.getSystemService(Context.ALARM_SERVICE);
|
||||
alarmService.cancel(PendingIntent.getBroadcast(context, 0, new Intent()
|
||||
{
|
||||
@Override
|
||||
public boolean filterEquals(final Intent other)
|
||||
{
|
||||
// we want to match all intents
|
||||
return true;
|
||||
}
|
||||
}, 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -432,8 +432,15 @@ public class MailService extends CoreService
|
||||
{
|
||||
if (K9.DEBUG)
|
||||
Log.i(K9.LOG_TAG, "Setting up pushers for account " + account.getDescription());
|
||||
if (account.isAvailable(getApplicationContext()))
|
||||
{
|
||||
pushing |= MessagingController.getInstance(getApplication()).setupPushing(account);
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: setupPushing of unavailable accounts when they become available (sd-card inserted)
|
||||
}
|
||||
}
|
||||
if (pushing)
|
||||
{
|
||||
PushService.startService(MailService.this);
|
||||
|
@ -36,6 +36,7 @@ public class RemoteControlReceiver extends CoreReceiver
|
||||
String[] descriptions = new String[accounts.length];
|
||||
for (int i = 0; i < accounts.length; i++)
|
||||
{
|
||||
//warning: account may not be isAvailable()
|
||||
Account account = accounts[i];
|
||||
|
||||
uuids[i] = account.getUuid();
|
||||
|
@ -86,6 +86,7 @@ public class RemoteControlService extends CoreService
|
||||
Account[] accounts = preferences.getAccounts();
|
||||
for (Account account : accounts)
|
||||
{
|
||||
//warning: account may not be isAvailable()
|
||||
if (allAccounts || account.getUuid().equals(uuid))
|
||||
{
|
||||
|
||||
|
44
src/com/fsck/k9/service/ShutdownReceiver.java
Normal file
44
src/com/fsck/k9/service/ShutdownReceiver.java
Normal file
@ -0,0 +1,44 @@
|
||||
package com.fsck.k9.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
|
||||
/**
|
||||
* Capture the system shutdown event in order to properly free resources.
|
||||
*
|
||||
* <p>
|
||||
* It is advised not to statically register (from AndroidManifest.xml) this
|
||||
* receiver in order to avoid unecessary K-9 launch (which would defeat the
|
||||
* purpose of that receiver). Using AndroidManifest.xml instructs Android to
|
||||
* launch K-9 if not running, defeating the purpose of this receiver. <br>
|
||||
* The recommended way is to register this receiver using
|
||||
* {@link Context#registerReceiver(BroadcastReceiver, android.content.IntentFilter)}
|
||||
* </p>
|
||||
*/
|
||||
public class ShutdownReceiver extends BroadcastReceiver
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent)
|
||||
{
|
||||
if (Intent.ACTION_SHUTDOWN.equals(intent.getAction()))
|
||||
{
|
||||
Log.i(K9.LOG_TAG, "System is shutting down, releasing resources");
|
||||
|
||||
// prevent any scheduled intent from waking up K-9
|
||||
BootReceiver.purgeSchedule(context);
|
||||
|
||||
/*
|
||||
* TODO invoke proper shutdown methods (stop any running thread)
|
||||
*
|
||||
* 20101111: this can't be done now as we don't have proper
|
||||
* startup/shutdown sequences
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
src/com/fsck/k9/service/StorageGoneReceiver.java
Normal file
51
src/com/fsck/k9/service/StorageGoneReceiver.java
Normal file
@ -0,0 +1,51 @@
|
||||
package com.fsck.k9.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
|
||||
/**
|
||||
* That BroadcastReceiver is only interested in UNMOUNT events.
|
||||
*
|
||||
* <p>
|
||||
* Code was separated from {@link StorageReceiver} because we don't want that
|
||||
* receiver to be statically defined in manifest.
|
||||
* </p>
|
||||
*/
|
||||
public class StorageGoneReceiver extends BroadcastReceiver
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent)
|
||||
{
|
||||
final String action = intent.getAction();
|
||||
final Uri uri = intent.getData();
|
||||
|
||||
if (uri == null || uri.getPath() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (K9.DEBUG)
|
||||
{
|
||||
Log.v(K9.LOG_TAG, "StorageGoneReceiver: " + intent.toString());
|
||||
}
|
||||
|
||||
final String path = uri.getPath();
|
||||
|
||||
if (Intent.ACTION_MEDIA_EJECT.equals(action))
|
||||
{
|
||||
StorageManager.getInstance(K9.app).onBeforeUnmount(path);
|
||||
}
|
||||
else if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action))
|
||||
{
|
||||
StorageManager.getInstance(K9.app).onAfterUnmount(path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
src/com/fsck/k9/service/StorageReceiver.java
Normal file
43
src/com/fsck/k9/service/StorageReceiver.java
Normal file
@ -0,0 +1,43 @@
|
||||
package com.fsck.k9.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.mail.store.StorageManager;
|
||||
|
||||
/**
|
||||
* That BroadcastReceiver is only interested in MOUNT events.
|
||||
*/
|
||||
public class StorageReceiver extends BroadcastReceiver
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent)
|
||||
{
|
||||
final String action = intent.getAction();
|
||||
final Uri uri = intent.getData();
|
||||
|
||||
if (uri == null || uri.getPath() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (K9.DEBUG)
|
||||
{
|
||||
Log.v(K9.LOG_TAG, "StorageReceiver: " + intent.toString());
|
||||
}
|
||||
|
||||
final String path = uri.getPath();
|
||||
|
||||
if (Intent.ACTION_MEDIA_MOUNTED.equals(action))
|
||||
{
|
||||
StorageManager.getInstance(K9.app).onMount(path,
|
||||
intent.getBooleanExtra("read-only", true));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user