1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-04 16:45:09 -05:00

Merge branch 'master' of github.com:k9mail/k-9

* 'master' of github.com:k9mail/k-9:
  Work around cases where mail was last checked in the future.
  Add progress indicator during account move.
  Disable account move menu items when account is at an end of the list.
  Working version of moving accounts within the Accounts view.
  Fixes Issue 3177
  Removed some doc annotations from chrisk's fix for the service stopping issue (2777)
  Stop service(s) if not in use - Fixes Issue 2777
  optimize cketti's pop3 space-splitting code to not make lots of extra copies of responses.
  Updated Japanese translation. catch up with 20d2942.
  Move Sender Address to end of Default Projection
  Expose SenderAddress through ContentProvider. Issue: 3665
This commit is contained in:
Andrew Chen 2011-09-06 22:48:41 -07:00
commit ef36300739
14 changed files with 470 additions and 146 deletions

View File

@ -21,6 +21,10 @@
android:title="@string/remove_account_action" />
<item android:id="@+id/clear_pending"
android:title="@string/clear_pending_action" />
<item android:id="@+id/move_up"
android:title="@string/manage_accounts_move_up_action" />
<item android:id="@+id/move_down"
android:title="@string/manage_accounts_move_down_action" />
</menu>
</item>
</menu>

View File

@ -109,6 +109,9 @@
<string name="view_hide_details_action">詳細の表示/非表示</string>
<string name="add_cc_bcc_action">Cc/Bcc追加</string>
<string name="edit_subject_action">件名編集</string>
<string name="read_receipt">開封確認</string>
<string name="read_receipt_enabled">開封確認を要求します</string>
<string name="read_receipt_disabled">開封確認を要求しません</string>
<string name="add_attachment_action">添付追加</string>
<string name="add_attachment_action_image">添付ファイル追加 (画像)</string>
<string name="add_attachment_action_video">添付ファイル追加 (動画)</string>
@ -267,6 +270,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="error_contact_address_not_found">メールアドレスが登録されていません</string>
<string name="message_compose_downloading_attachments_toast">一部の添付ファイルをダウンロードしていません。このメールが送信される前に自動的にダウンロードされます。</string>
<string name="message_compose_attachments_skipped_toast">ダウンロードしていないため、一部の添付ファイルを転送することはできません。</string>
<string name="message_compose_show_quoted_text_action">元のメッセージを引用する</string>
@ -559,6 +563,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_composition">メール送信</string>
<string name="account_settings_default_quoted_text_shown_label">返信時に元のメッセージを引用する</string>
<string name="account_settings_default_quoted_text_shown_summary">返信するとき、元のメッセージをメールに挿入する</string>
<string name="account_settings_reply_after_quote_label">引用テキストの後に返信を書く</string>
<string name="account_settings_reply_after_quote_summary">返信するときに元のメッセージを返信の上に表示</string>
@ -566,6 +573,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_message_format_text">テキスト(画像や書式は取り除く)</string>
<string name="account_settings_message_format_html">HTML (画像や書式を保持)</string>
<string name="account_settings_message_read_receipt_label">開封確認</string>
<string name="account_settings_message_read_receipt_summary">常に開封確認を要求する</string>
<string name="account_settings_quote_style_label">返信の引用スタイル</string>
<string name="account_settings_quote_style_prefix">引用記号 (Gmail, Pine形式)</string>
<string name="account_settings_quote_style_header">ヘッダ (Outlook, Yahoo!, Hotmail形式)</string>
@ -611,6 +621,8 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="account_settings_autodownload_message_size_512">512Kb</string>
<string name="account_settings_autodownload_message_size_1024">1Mb</string>
<string name="account_settings_autodownload_message_size_2048">2Mb</string>
<string name="account_settings_autodownload_message_size_5120">5Mb</string>
<string name="account_settings_autodownload_message_size_10240">10Mb</string>
<string name="account_settings_autodownload_message_size_any">上限なし</string>
<string name="account_settings_message_age_label">いつから同期するか</string>
@ -1019,13 +1031,8 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール
<string name="dialog_confirm_spam_title">迷惑メールフォルダへの移動の確認</string>
<plurals name="dialog_confirm_spam_message">
<item quantity="one">本当にこのメッセージを迷惑メールフォルダに移動しますか?</item>
<item quantity="other">本当にこのメッセージを迷惑メールフォルダに移動しますか?</item>
<item quantity="other">本当にこれら<xliff:g id="message_count">%1$d</xliff:g>のメッセージを迷惑メールフォルダに移動しますか?</item>
<!--
Translators:
Please review how to handle pluralization for your language at (1) and ajust
the <item> elements accordingly
Possible values for 'quantity': zero, one, two, few, many, other
(1) http://developer.android.com/guide/topics/resources/string-resource.html#Plurals

View File

@ -1073,4 +1073,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="attachment_save_title">Save attachment</string>
<string name="attachment_save_desc">No file browser found. Where would you like to save this attachment?</string>
<string name="manage_accounts_move_up_action">Move up</string>
<string name="manage_accounts_move_down_action">Move down</string>
</resources>

View File

@ -21,7 +21,9 @@ import com.fsck.k9.view.ColorChip;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
@ -377,19 +379,29 @@ public class Account implements BaseAccount {
mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false);
}
protected synchronized void delete(Preferences preferences) {
String[] uuids = preferences.getPreferences().getString("accountUuids", "").split(",");
private String combineUuids(String[] uuids) {
StringBuffer sb = new StringBuffer();
for (int i = 0, length = uuids.length; i < length; i++) {
if (!uuids[i].equals(mUuid)) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(uuids[i]);
}
}
String accountUuids = sb.toString();
return accountUuids;
}
protected synchronized void delete(Preferences preferences) {
String[] uuids = preferences.getPreferences().getString("accountUuids", "").split(",");
String[] newUuids = new String[uuids.length - 1];
int i = 0;
for (String uuid : uuids) {
if (uuid.equals(mUuid) == false) {
newUuids[i++] = uuid;
}
}
String accountUuids = combineUuids(newUuids);
SharedPreferences.Editor editor = preferences.getPreferences().edit();
editor.putString("accountUuids", accountUuids);
@ -455,6 +467,64 @@ public class Account implements BaseAccount {
editor.commit();
}
public static int findNewAccountNumber(List<Integer> accountNumbers) {
int newAccountNumber = -1;
Collections.sort(accountNumbers);
for (int accountNumber : accountNumbers) {
if (accountNumber > newAccountNumber + 1) {
break;
}
newAccountNumber = accountNumber;
}
newAccountNumber++;
return newAccountNumber;
}
public static List<Integer> getExistingAccountNumbers(Preferences preferences) {
Account[] accounts = preferences.getAccounts();
List<Integer> accountNumbers = new LinkedList<Integer>();
for (int i = 0; i < accounts.length; i++) {
accountNumbers.add(accounts[i].getAccountNumber());
}
return accountNumbers;
}
public static int generateAccountNumber(Preferences preferences) {
List<Integer> accountNumbers = getExistingAccountNumbers(preferences);
return findNewAccountNumber(accountNumbers);
}
public void move(Preferences preferences, boolean moveUp) {
String[] uuids = preferences.getPreferences().getString("accountUuids", "").split(",");
SharedPreferences.Editor editor = preferences.getPreferences().edit();
String[] newUuids = new String[uuids.length];
if (moveUp) {
for (int i = 0; i < uuids.length; i++) {
if (i > 0 && uuids[i].equals(mUuid)) {
newUuids[i] = newUuids[i-1];
newUuids[i-1] = mUuid;
}
else {
newUuids[i] = uuids[i];
}
}
}
else {
for (int i = uuids.length - 1; i >= 0; i--) {
if (i < uuids.length - 1 && uuids[i].equals(mUuid)) {
newUuids[i] = newUuids[i+1];
newUuids[i+1] = mUuid;
}
else {
newUuids[i] = uuids[i];
}
}
}
String accountUuids = combineUuids(newUuids);
editor.putString("accountUuids", accountUuids);
editor.commit();
preferences.refreshAccounts();
}
public synchronized void save(Preferences preferences) {
SharedPreferences.Editor editor = preferences.getPreferences().edit();

View File

@ -3,7 +3,11 @@ package com.fsck.k9;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Config;
@ -29,7 +33,8 @@ public class Preferences {
private Storage mStorage;
private List<Account> accounts;
private Map<String, Account> accounts = null;
private List<Account> accountsInOrder = null;
private Account newAccount;
private Context mContext;
@ -45,17 +50,36 @@ public class Preferences {
}
private synchronized void loadAccounts() {
accounts = new HashMap<String, Account>();
refreshAccounts();
}
public synchronized void refreshAccounts() {
Map<String, Account> newAccountMap = new HashMap<String, Account>();
accountsInOrder = new LinkedList<Account>();
String accountUuids = getPreferences().getString("accountUuids", null);
if ((accountUuids != null) && (accountUuids.length() != 0)) {
String[] uuids = accountUuids.split(",");
accounts = new ArrayList<Account>(uuids.length);
for (String uuid : uuids) {
accounts.add(new Account(this, uuid));
}
Account account = accounts.get(uuid);
if (account != null) {
newAccountMap.put(uuid, account);
accountsInOrder.add(account);
} else {
accounts = new ArrayList<Account>();
Account newAccount = new Account(this, uuid);
newAccountMap.put(uuid, newAccount);
accountsInOrder.add(newAccount);
}
}
}
if ((newAccount != null) && newAccount.getAccountNumber() != -1) {
newAccountMap.put(newAccount.getUuid(), newAccount);
accountsInOrder.add(newAccount);
newAccount = null;
}
accounts = newAccountMap;
}
/**
* Returns an array of the accounts on the system. If no accounts are
@ -67,12 +91,7 @@ public class Preferences {
loadAccounts();
}
if ((newAccount != null) && newAccount.getAccountNumber() != -1) {
accounts.add(newAccount);
newAccount = null;
}
return accounts.toArray(EMPTY_ACCOUNT_ARRAY);
return accountsInOrder.toArray(EMPTY_ACCOUNT_ARRAY);
}
/**
@ -81,16 +100,9 @@ public class Preferences {
* @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;
}
Account[] allAccounts = getAccounts();
Collection<Account> retval = new ArrayList<Account>(accounts.size());
for (Account account : accounts) {
for (Account account : allAccounts) {
if (account.isAvailable(mContext)) {
retval.add(account);
}
@ -103,28 +115,22 @@ public class Preferences {
if (accounts == null) {
loadAccounts();
}
Account account = accounts.get(uuid);
for (Account account : accounts) {
if (account.getUuid().equals(uuid)) {
return account;
}
}
if ((newAccount != null) && newAccount.getUuid().equals(uuid)) {
return newAccount;
}
return null;
}
public synchronized Account newAccount() {
newAccount = new Account(K9.app);
accounts.put(newAccount.getUuid(), newAccount);
accountsInOrder.add(newAccount);
return newAccount;
}
public synchronized void deleteAccount(Account account) {
accounts.remove(account);
accounts.remove(account.getUuid());
accountsInOrder.remove(account);
account.delete(this);
if (newAccount == account) {

View File

@ -1,6 +1,15 @@
package com.fsck.k9.activity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
@ -12,27 +21,45 @@ import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.TypedValue;
import android.view.*;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.widget.*;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import com.fsck.k9.*;
import com.fsck.k9.helper.SizeFormatter;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.SearchAccount;
import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.AccountSetupBasics;
import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.helper.SizeFormatter;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.ColorChip;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener {
/**
@ -324,8 +351,28 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
}
private BaseAccount[] accounts = new BaseAccount[0];
private enum ACCOUNT_LOCATION {
TOP, MIDDLE, BOTTOM;
}
private EnumSet<ACCOUNT_LOCATION> accountLocation(BaseAccount account) {
EnumSet<ACCOUNT_LOCATION> accountLocation = EnumSet.of(ACCOUNT_LOCATION.MIDDLE);
if (accounts.length > 0) {
if (accounts[0].equals(account)) {
accountLocation.remove(ACCOUNT_LOCATION.MIDDLE);
accountLocation.add(ACCOUNT_LOCATION.TOP);
}
if (accounts[accounts.length - 1].equals(account)) {
accountLocation.remove(ACCOUNT_LOCATION.MIDDLE);
accountLocation.add(ACCOUNT_LOCATION.BOTTOM);
}
}
return accountLocation;
}
private void refresh() {
BaseAccount[] accounts = Preferences.getPreferences(this).getAccounts();
accounts = Preferences.getPreferences(this).getAccounts();
List<BaseAccount> newAccounts;
if (!K9.isHideSpecialAccounts()
@ -584,6 +631,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
case R.id.recreate:
onRecreate(realAccount);
break;
case R.id.move_up:
onMove(realAccount, true);
break;
case R.id.move_down:
onMove(realAccount, false);
break;
}
return true;
}
@ -602,7 +655,24 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
private void onRecreate(Account account) {
showDialog(DIALOG_RECREATE_ACCOUNT);
}
private void onMove(final Account account, final boolean up) {
mHandler.progress(true);
AsyncUIProcessor.getInstance(getApplication()).execute(
new Runnable()
{
@Override
public void run() {
account.move(Preferences.getPreferences(Accounts.this), up);
runOnUiThread(new Runnable() {
@Override
public void run() {
refresh();
mHandler.progress(false);
}
});
}
});
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BaseAccount account = (BaseAccount)parent.getItemAtPosition(position);
@ -742,6 +812,21 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
}
}
}
else {
EnumSet<ACCOUNT_LOCATION> accountLocation = accountLocation(account);
if (accountLocation.contains(ACCOUNT_LOCATION.TOP)) {
menu.findItem(R.id.move_up).setEnabled(false);
}
else {
menu.findItem(R.id.move_up).setEnabled(true);
}
if (accountLocation.contains(ACCOUNT_LOCATION.BOTTOM)) {
menu.findItem(R.id.move_down).setEnabled(false);
}
else {
menu.findItem(R.id.move_down).setEnabled(true);
}
}
}
class AccountsAdapter extends ArrayAdapter<BaseAccount> {

View File

@ -0,0 +1,33 @@
package com.fsck.k9.activity;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.app.Application;
/**
* The class should be used to run long-running processes invoked from the UI that
* do not affect the Stores. There are probably pieces of MessagingController
* that can be moved here. There is no wakelock used here. Any network activity, or
* true background activity, that is invoked from here should wakelock itself. UI-centric
* activity does not need to be wakelocked, as it will simply continue when the phone wakes
* without disruption.
*
*/
public class AsyncUIProcessor {
private final ExecutorService threadPool = Executors.newCachedThreadPool();
private static AsyncUIProcessor inst = null;
private AsyncUIProcessor() {
}
public synchronized static AsyncUIProcessor getInstance(Application application) {
if (inst == null) {
inst = new AsyncUIProcessor();
}
return inst;
}
public void execute(Runnable runnable) {
threadPool.execute(runnable);
}
}

View File

@ -889,6 +889,8 @@ public class FolderList extends K9ListActivity {
int unreadMessageCount = localFolder.getUnreadMessageCount();
FolderInfoHolder folderHolder = getFolder(folderName);
if (folderHolder != null) {
int oldUnreadMessageCount = folderHolder.unreadMessageCount;
mUnreadMessageCount += unreadMessageCount - oldUnreadMessageCount;
folderHolder.populate(context, localFolder, mAccount, unreadMessageCount);
mHandler.dataChanged();
}
@ -959,6 +961,7 @@ public class FolderList extends K9ListActivity {
public void folderStatusChanged(Account account, String folderName, int unreadMessageCount) {
if (account.equals(mAccount)) {
refreshFolder(account, folderName);
informUserOfStatus();
}
}

View File

@ -459,9 +459,7 @@ public class Pop3Store extends Store {
/*
* Yet another work-around for buggy server software:
* Replace every occurence of multiple spaces with exactly one space. This way
* the String.split() call below will have the desired effect, i.e. split the
* response into message number and unique identifier.
* split the response into message number and unique identifier, no matter how many spaces it has
*
* Example for a malformed response:
* 1 2011071307115510400ae3e9e00bmu9
@ -469,9 +467,8 @@ public class Pop3Store extends Store {
* Note the three spaces between message number and unique identifier.
* See issue 3546
*/
String cleanedResponse = response.replaceAll(" +", " ");
String[] uidParts = cleanedResponse.split(" ");
String[] uidParts = response.split(" +");
if ((uidParts.length >= 3) && "+OK".equals(uidParts[0])) {
/*
* At least one server software places a "+OK" in

View File

@ -61,6 +61,11 @@ public class MessageProvider extends ContentProvider {
*/
String SENDER = "sender";
/**
* <P>Type: TEXT</P>
*/
String SENDER_ADDRESS = "senderAddress";
/**
* <P>Type: TEXT</P>
*/
@ -179,6 +184,12 @@ public class MessageProvider extends ContentProvider {
return source.sender;
}
}
public static class SenderAddressExtractor implements FieldExtractor<MessageInfoHolder, String> {
@Override
public String getField(final MessageInfoHolder source) {
return source.senderAddress;
}
}
public static class AccountExtractor implements FieldExtractor<MessageInfoHolder, String> {
@Override
public String getField(final MessageInfoHolder source) {
@ -290,6 +301,8 @@ public class MessageProvider extends ContentProvider {
extractors.put(field, new SubjectExtractor());
} else if (MessageColumns.SENDER.equals(field)) {
extractors.put(field, new SenderExtractor());
} else if (MessageColumns.SENDER_ADDRESS.equals(field)) {
extractors.put(field, new SenderAddressExtractor());
} else if (MessageColumns.SEND_DATE.equals(field)) {
extractors.put(field, new SendDateExtractor());
} else if (MessageColumns.PREVIEW.equals(field)) {
@ -818,7 +831,8 @@ public class MessageProvider extends ContentProvider {
MessageColumns.PREVIEW,
MessageColumns.ACCOUNT,
MessageColumns.URI,
MessageColumns.DELETE_URI
MessageColumns.DELETE_URI,
MessageColumns.SENDER_ADDRESS
};
/**

View File

@ -17,14 +17,64 @@ import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
/**
* Note: All documentation in this file market <CK:RE> is documentation written by Christian Knecht by reverse engineering.
* This documentation is without warranty and may not be accurate nor reflect the author's original intent.
*
* <CK:RE>
* CoreService is the base class for all K9 Services.
*
* An Android service is a way to model a part of an application that needs to accomplish certain tasks without the
* UI part of the application being necessarily active (of course an application could also be a pure service, without
* any UI; this is not the case of K9). By declaring a service and starting it, the OS knows that the application has
* work to do and should avoid killing the process.
*
* A service's main purpose is to do some task (usually in the background) which requires one of more threads. The
* thread that starts the service is the same as the UI thread of the process. It should thus not be used to run
* the tasks.
*
* CoreService is providing the execution plumbing for background tasks including the required thread and task queuing
* for all K9 services to use.
*
* A service is supposed to run only as long as it has some work to do whether that work is active processing or some
* just some monitoring, like listening on a network port for incoming connections or listing on an open network
* connection for incoming data (push mechanism).
*
* To make sure the service is running only when required, is must be shutdown after tasks are done. As the
* execution of tasks is abstracted away in this class, it also proper shutdown handling if approriate. If
* the Service requires this is should call enableAutoShutdown(true) in it's onCreate() method.
*
* While a service is running it's tasks, it is usually not a good idea to let the device go to sleep more.
* WakeLocks are used to avoid this. CoreService provides a central registry (singleton) that can be used
* application-wide to store WakeLocks.
*
* In short, CoreService provides the following features to K9 Services:
* - task execution and queuing
* - Service life cycle management (insures the service is stopped when not needed anymore); disabled by default
* - WakeLock registry and management
*
* </CK:RE>
*/
public abstract class CoreService extends Service {
public static String WAKE_LOCK_ID = "com.fsck.k9.service.CoreService.wakeLockId";
private static ConcurrentHashMap<Integer, TracingWakeLock> wakeLocks = new ConcurrentHashMap<Integer, TracingWakeLock>();
private static AtomicInteger wakeLockSeq = new AtomicInteger(0);
private ExecutorService threadPool = null;
public static String WAKE_LOCK_ID = "com.fsck.k9.service.CoreService.wakeLockId"; // CK:Intent attribute ID
private static ConcurrentHashMap<Integer, TracingWakeLock> wakeLocks = new ConcurrentHashMap<Integer, TracingWakeLock>(); // CK:WakeLocks registry
private static AtomicInteger wakeLockSeq = new AtomicInteger(0); // CK:WakeLock registry
private ExecutorService threadPool = null; // CK:Threadpool with a single thread; used to execute and queue background actions inside the service
private final String className = getClass().getName();
private volatile boolean mShutdown = false;
private volatile boolean mShutdown = false; // CK:A:Seems to be used only when the service is "officially" shutdown to make sure that an exception raise because of the shutdown gets ignored.
/**
* Controls the auto-shutdown mechanism of the service. The default service life-cycle model is that the service should run
* only as long as a task is running. If a service should behave differently, disable auto-shutdown.
*/
private boolean mAutoShutdown = true;
/**
* This variable is part of the auto-shutdown feature and determines whether the service has to be shutdown at the
* end of the onStart() method or not.
*/
protected boolean mImmediateShutdown = true; //
@Override
public void onCreate() {
@ -35,32 +85,66 @@ public abstract class CoreService extends Service {
}
protected static void addWakeLockId(Intent i, Integer wakeLockId) {
/**
* Adds an existing WakeLock identified by it's WakeLock-ID to the specified Intent.
* @param i
* @param wakeLockId
*/
protected static void addWakeLockId(Context context, Intent i, Integer wakeLockId, boolean createIfNotExists) {
if (wakeLockId != null) {
i.putExtra(BootReceiver.WAKE_LOCK_ID, wakeLockId);
return;
}
if (createIfNotExists)
addWakeLock(context,i);
}
/**
* Adds a new WakeLock to the intent.
* This will add the WakeLock to the central WakeLock registry managed by this class.
* @param context Required to be able to create a new wake-lock.
* @param i Intent to which to add the WakeLock (CK:Q:still unclear why we need to link Intents and WakeLocks)
*/
protected static void addWakeLock(Context context, Intent i) {
TracingPowerManager pm = TracingPowerManager.getPowerManager(context);
TracingWakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CoreService addWakeLock");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
Integer tmpWakeLockId = wakeLockSeq.getAndIncrement();
wakeLocks.put(tmpWakeLockId, wakeLock);
TracingWakeLock wakeLock = acquireWakeLock(context,"CoreService addWakeLock",K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT); // CK:Q: What this timeout? 30secs seems a bizarre choice. It it's a safeguard it should be longer, if it's to cover the real time required by some operation, it seems too short (though I say this before knowing really what the service is supposed to do)
Integer tmpWakeLockId = registerWakeLock(wakeLock);
i.putExtra(WAKE_LOCK_ID, tmpWakeLockId);
}
/**
* Register WakeLock and returns its registry-entry-ID
* @param wakeLock
* @return
* AUTHOR chrisk
*/
protected static Integer registerWakeLock(TracingWakeLock wakeLock) {
Integer tmpWakeLockId = wakeLockSeq.getAndIncrement();
wakeLocks.put(tmpWakeLockId, wakeLock);
return tmpWakeLockId;
}
/**
* Acquires a WakeLock in a K9 standard way
* @param context
* @return
* AUTHOR chrisk
*/
protected static TracingWakeLock acquireWakeLock(Context context, String tag, long timeout) {
TracingPowerManager pm = TracingPowerManager.getPowerManager(context);
TracingWakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(timeout);
return wakeLock;
}
@Override
public void onStart(Intent intent, int startId) {
TracingPowerManager pm = TracingPowerManager.getPowerManager(this);
TracingWakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CoreService onStart");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
// deprecated method but still used for backwards compatibility with Android version <2.0
// CK:DocAdded: Manage wake-locks, especially, release any wake-locks held so far and define a new "local" wake lock.
// Also, because we create a new wakelock, we re-initialize the wakelock timeout and give
// the service-start code a protection of up to MAIL_SERVICE_WAKE_LOCK_TIMEOUT (currently 30s).
TracingWakeLock wakeLock = acquireWakeLock(this,"CoreService onStart",K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "CoreService: " + className + ".onStart(" + intent + ", " + startId);
@ -81,20 +165,30 @@ public abstract class CoreService extends Service {
}
}
// Run the actual start-code of the service
mImmediateShutdown = true;
try {
super.onStart(intent, startId);
startService(intent, startId);
} finally {
wakeLock.release();
try{wakeLock.release();} catch (Exception e) {/* ignore */}
try{if (mAutoShutdown && mImmediateShutdown && startId != -1) stopSelf(startId);} catch (Exception e) {/* ignore */}
}
}
public void execute(Context context, final Runnable runner, int wakeLockTime, final Integer startId) {
/**
*
* @param context
* @param runner
* @param wakeLockTime
* @param startId
* @return returns whether service-shutdown will actually happen after the task has been executed (or has already been done).
*/
public boolean execute(Context context, final Runnable runner, int wakeLockTime, final Integer startId) {
TracingPowerManager pm = TracingPowerManager.getPowerManager(context);
final TracingWakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CoreService execute");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(wakeLockTime);
boolean serviceShutdownScheduled = false;
final TracingWakeLock wakeLock = acquireWakeLock(context,"CoreService execute",wakeLockTime);
final boolean autoShutdown = mAutoShutdown;
Runnable myRunner = new Runnable() {
public void run() {
@ -107,11 +201,14 @@ public abstract class CoreService extends Service {
MessagingController.getInstance(getApplication()).systemStatusChanged();
}
} finally {
try { // Making absolutely sure the service stopping command will be executed
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "CoreService (" + className + ") completed Runnable " + runner.hashCode() + " with startId " + startId);
wakeLock.release();
if (startId != null) {
stopSelf(startId);
} finally {
if (autoShutdown && startId != null) {
stopSelf(startId); // <-- this is what is meant with "serviceShutdownScheduled"; execution of this line assures proper shutdown of the service once finished
}
}
}
}
@ -121,12 +218,14 @@ public abstract class CoreService extends Service {
Log.e(K9.LOG_TAG, "CoreService.execute (" + className + ") called with no threadPool available; running Runnable " + runner.hashCode() + " in calling thread", new Throwable());
synchronized (this) {
myRunner.run();
serviceShutdownScheduled = startId != null; // In this case it's not actually scheduled, it's already done, but that should never happen anyway
}
} else {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "CoreService (" + className + ") queueing Runnable " + runner.hashCode() + " with startId " + startId);
try {
threadPool.execute(myRunner);
serviceShutdownScheduled = startId != null;
} catch (RejectedExecutionException e) {
if (!mShutdown) {
throw e;
@ -134,12 +233,23 @@ public abstract class CoreService extends Service {
Log.i(K9.LOG_TAG, "CoreService: " + className + " is shutting down, ignoring rejected execution exception: " + e.getMessage());
}
}
mImmediateShutdown = !serviceShutdownScheduled;
return serviceShutdownScheduled;
}
/**
* CK:Added
* To implement by sub-class instead of overriding onStart.
* This allows CoreService to do start and end operations around the sub-class's start code.
* Especially, CoreService will protect the start-code with a wake-lock to guarantee the service to have the required resources to do it's work.
* CK:Q: Is this really useful (the wakelock part)? The real work is happening in the worker-thread anyway. Maybe it is because this makes sure that whatever needs to be started by the service, it can be without being interrupted by the phone going to sleep.
* @param intent
* @param startId
*/
public abstract void startService(Intent intent, int startId);
@Override
public IBinder onBind(Intent arg0) {
public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
// TODO Auto-generated method stub
return null;
}
@ -158,4 +268,20 @@ public abstract class CoreService extends Service {
super.onDestroy();
// MessagingController.getInstance(getApplication()).removeListener(mListener);
}
/**
* @return True if auto-shutdown is enabled
*/
protected boolean isAutoShutdown() {
return mAutoShutdown;
}
/**
* Enable of disable auto-shutdown (enabled by default).
* See {@#mAutoShutdown} for more information.
* @param autoShutdown
*/
protected void setAutoShutdown(boolean autoShutdown) {
mAutoShutdown = autoShutdown;
}
}

View File

@ -21,8 +21,6 @@ import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.AutoSyncHelper;
import com.fsck.k9.mail.Pusher;
/**
*/
public class MailService extends CoreService {
private static final String ACTION_CHECK_MAIL = "com.fsck.k9.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESET = "com.fsck.k9.intent.action.MAIL_SERVICE_RESET";
@ -42,10 +40,7 @@ public class MailService extends CoreService {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESET);
addWakeLockId(i, wakeLockId);
if (wakeLockId == null) {
addWakeLock(context, i);
}
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}
@ -53,10 +48,7 @@ public class MailService extends CoreService {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESTART_PUSHERS);
addWakeLockId(i, wakeLockId);
if (wakeLockId == null) {
addWakeLock(context, i);
}
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}
@ -64,10 +56,7 @@ public class MailService extends CoreService {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESCHEDULE_POLL);
addWakeLockId(i, wakeLockId);
if (wakeLockId == null) {
addWakeLock(context, i);
}
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}
@ -75,7 +64,7 @@ public class MailService extends CoreService {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_CANCEL);
addWakeLockId(i, wakeLockId);
addWakeLockId(context, i, wakeLockId, false); // CK:Q: why should we not create a wake lock if one is not already existing like for example in actionReschedulePoll?
context.startService(i);
}
@ -83,7 +72,7 @@ public class MailService extends CoreService {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.CONNECTIVITY_CHANGE);
addWakeLockId(i, wakeLockId);
addWakeLockId(context, i, wakeLockId, false); // CK:Q: why should we not create a wake lock if one is not already existing like for example in actionReschedulePoll?
context.startService(i);
}
@ -96,7 +85,6 @@ public class MailService extends CoreService {
@Override
public void startService(Intent intent, int startId) {
Integer startIdObj = startId;
long startTime = System.currentTimeMillis();
try {
boolean oldIsSyncDisabled = isSyncDisabled();
@ -147,45 +135,33 @@ public class MailService extends CoreService {
if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "***** MailService *****: checking mail");
if (hasConnectivity && doBackground) {
PollService.startService(this);
}
reschedulePoll(hasConnectivity, doBackground, startIdObj, false);
startIdObj = null;
reschedulePoll(hasConnectivity, doBackground, startId, false);
} else if (ACTION_CANCEL.equals(intent.getAction())) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "***** MailService *****: cancel");
cancel();
} else if (ACTION_RESET.equals(intent.getAction())) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "***** MailService *****: reschedule");
rescheduleAll(hasConnectivity, doBackground, startIdObj);
startIdObj = null;
rescheduleAll(hasConnectivity, doBackground, startId);
} else if (ACTION_RESTART_PUSHERS.equals(intent.getAction())) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "***** MailService *****: restarting pushers");
reschedulePushers(hasConnectivity, doBackground, startIdObj);
startIdObj = null;
reschedulePushers(hasConnectivity, doBackground, startId);
} else if (ACTION_RESCHEDULE_POLL.equals(intent.getAction())) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "***** MailService *****: rescheduling poll");
reschedulePoll(hasConnectivity, doBackground, startIdObj, true);
startIdObj = null;
reschedulePoll(hasConnectivity, doBackground, startId, true);
} else if (ACTION_REFRESH_PUSHERS.equals(intent.getAction())) {
if (hasConnectivity && doBackground) {
refreshPushers(null);
schedulePushers(startIdObj);
startIdObj = null;
schedulePushers(startId);
}
} else if (CONNECTIVITY_CHANGE.equals(intent.getAction())) {
rescheduleAll(hasConnectivity, doBackground, startIdObj);
startIdObj = null;
rescheduleAll(hasConnectivity, doBackground, startId);
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Got connectivity action with hasConnectivity = " + hasConnectivity + ", doBackground = " + doBackground);
} else if (CANCEL_CONNECTIVITY_NOTICE.equals(intent.getAction())) {
@ -194,9 +170,7 @@ public class MailService extends CoreService {
MessagingController.getInstance(getApplication()).systemStatusChanged();
}
} finally {
if (startIdObj != null) {
stopSelf(startId);
}
/* nothing to do */
}
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "MailService.onStart took " + (System.currentTimeMillis() - startTime) + "ms");
@ -205,7 +179,6 @@ public class MailService extends CoreService {
private void rescheduleAll(final boolean hasConnectivity, final boolean doBackground, final Integer startId) {
reschedulePoll(hasConnectivity, doBackground, null, true);
reschedulePushers(hasConnectivity, doBackground, startId);
}
@ -228,7 +201,6 @@ public class MailService extends CoreService {
private final static String LAST_CHECK_END = "MailService.lastCheckEnd";
public static void saveLastCheckEnd(Context context) {
long lastCheckEnd = System.currentTimeMillis();
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Saving lastCheckEnd = " + new Date(lastCheckEnd));
@ -249,6 +221,13 @@ public class MailService extends CoreService {
SharedPreferences sPrefs = prefs.getPreferences();
int previousInterval = sPrefs.getInt(PREVIOUS_INTERVAL, -1);
long lastCheckEnd = sPrefs.getLong(LAST_CHECK_END, -1);
if (lastCheckEnd > System.currentTimeMillis()) {
Log.i(K9.LOG_TAG, "The database claims that the last time mail was checked was in the future. ("+lastCheckEnd+"). To try to get things back to normal, the last check time has been reset to "+System.currentTimeMillis());
lastCheckEnd = System.currentTimeMillis();
}
for (Account account : prefs.getAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1
&& account.getFolderSyncMode() != FolderMode.NONE
@ -320,7 +299,6 @@ public class MailService extends CoreService {
private void reschedulePushers(final boolean hasConnectivity, final boolean doBackground, final Integer startId) {
execute(getApplication(), new Runnable() {
public void run() {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Rescheduling pushers");
stopPushers(null);
@ -330,7 +308,6 @@ public class MailService extends CoreService {
} else {
if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "Not scheduling pushers: connectivity? " + hasConnectivity + " -- doBackground? " + doBackground);
}
}
@ -433,13 +410,11 @@ public class MailService extends CoreService {
@Override
public IBinder onBind(Intent intent) {
public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
return null;
}
public static long getNextPollTime() {
return nextCheck;
}
}

View File

@ -27,10 +27,7 @@ public class RemoteControlService extends CoreService {
// Intent i = new Intent();
i.setClass(context, RemoteControlService.class);
i.setAction(RemoteControlService.SET_ACTION);
addWakeLockId(i, wakeLockId);
if (wakeLockId == null) {
addWakeLock(context, i);
}
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}

View File

@ -114,12 +114,16 @@ public class SleepService extends CoreService {
@Override
public void startService(Intent intent, int startId) {
try {
if (intent.getAction().startsWith(ALARM_FIRED)) {
Integer id = intent.getIntExtra(LATCH_ID, -1);
endSleep(id);
}
}
finally {
stopSelf(startId);
}
}
private static class SleepDatum {
CountDownLatch latch;