From 6c22507dfbf9dedb0b2d2aced2e4a79e8c07b63c Mon Sep 17 00:00:00 2001 From: Daniel Applebaum Date: Thu, 14 Jan 2010 04:33:50 +0000 Subject: [PATCH] Implementation of a Receiver and Service to provide for the capability to accept control from other Android applications. Allows for changing both Account-level and global settings. Account-level settings can be applied to a single Account or to all Accounts. The file class file derived from src/com/fsck/k9/K9RemoteControl.java will be bundled into a JAR file for use by external applications. This facility will be used for: Issue 215 Issue 730 Issue 864 Issue 884 --- AndroidManifest.xml | 25 +++ res/values/strings.xml | 4 +- src/com/fsck/k9/K9RemoteControl.java | 108 +++++++++++ .../k9/service/RemoteControlReceiver.java | 57 ++++++ .../fsck/k9/service/RemoteControlService.java | 167 ++++++++++++++++++ 5 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 src/com/fsck/k9/K9RemoteControl.java create mode 100644 src/com/fsck/k9/service/RemoteControlReceiver.java create mode 100644 src/com/fsck/k9/service/RemoteControlService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index cdf8d16bb..06541944a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -25,6 +25,12 @@ android:label="@string/read_attachment_label" android:description="@string/read_attachment_desc"/> + + + + + + + + + + + + @@ -241,6 +260,12 @@ android:enabled="true" > + + Animation Use gaudy visual effects - + + K-9 Mail remote control + Allows this application to control K-9 Mail activities and settings. diff --git a/src/com/fsck/k9/K9RemoteControl.java b/src/com/fsck/k9/K9RemoteControl.java new file mode 100644 index 000000000..33001f76a --- /dev/null +++ b/src/com/fsck/k9/K9RemoteControl.java @@ -0,0 +1,108 @@ +package com.fsck.k9; +/** + * Utillity definitions for Android applications to control the behavior of K-9 Mail. All such applications must declare the following permission: + * + * in their AndroidManifest.xml + * + * An application that wishes to act on a particular Account in K-9 needs to fetch the list of configured Accounts by broadcasting an + * {@link Intent} using K9_REQUEST_ACCOUNTS as the Action. The broadcast must be made using the {@link ContextWrapper} + * sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, + * Handler scheduler, int initialCode, String initialData, Bundle initialExtras).sendOrderedBroadcast} + * method in order to receive the list of Account UUIDs and descriptions that K-9 will provide. + * + * @author Daniel I. Applebaum + * + */ +public class K9RemoteControl +{ + /** + * {@link Intent} Action to be sent to K-9 using {@link ContextWrapper.sendOrderedBroadcast} in order to fetch the list of configured Accounts. + * The responseData will contain two String[] with keys K9_ACCOUNT_UUIDS and K9_ACCOUNT_DESCRIPTIONS + */ + public final static String K9_REQUEST_ACCOUNTS = "com.fsck.k9.K9RemoteControl.requestAccounts"; + public final static String K9_ACCOUNT_UUIDS = "com.fsck.k9.K9RemoteControl.accountUuids"; + public final static String K9_ACCOUNT_DESCRIPTIONS = "com.fsck.k9.K9RemoteControl.accountDescriptions"; + + /** + * The {@link {@link Intent}} Action to set in order to cause K-9 to check mail. (Not yet implemented) + */ + //public final static String K9_CHECK_MAIL = "com.fsck.k9.K9RemoteControl.checkMail"; + + /** + * The {@link {@link Intent}} Action to set when remotely changing K-9 Mail settings + */ + public final static String K9_SET = "com.fsck.k9.K9RemoteControl.set"; + /** + * The key of the {@link Intent} Extra to set to hold the UUID of a single Account's settings to change. Used only if K9_ALL_ACCOUNTS + * is absent or false. + */ + public final static String K9_ACCOUNT_UUID = "com.fsck.k9.K9RemoteControl.accountUuid"; + /** + * The key of the {@link Intent} Extra to set to control if the settings will apply to all Accounts, or to the one + * specified with K9_ACCOUNT_UUID + */ + public final static String K9_ALL_ACCOUNTS = "com.fsck.k9.K9RemoteControl.allAccounts"; + + public final static String K9_ENABLED = "true"; + public final static String K9_DISABLED = "false"; + + /* + * Key for the {@link Intent} Extra for controlling whether notifications will be generated for new unread mail. + * Acceptable values are K9_ENABLED and K9_DISABLED + */ + public final static String K9_NOTIFICATION_ENABLED = "com.fsck.k9.K9RemoteControl.notificationEnabled"; + /* + * Key for the {@link Intent} Extra for controlling whether K-9 will sound the ringtone for new unread mail. + * Acceptable values are K9_ENABLED and K9_DISABLED + */ + public final static String K9_RING_ENABLED = "com.fsck.k9.K9RemoteControl.ringEnabled"; + /* + * Key for the {@link Intent} Extra for controlling whether K-9 will activate the vibrator for new unread mail. + * Acceptable values are K9_ENABLED and K9_DISABLED + */ + public final static String K9_VIBRATE_ENABLED = "com.fsck.k9.K9RemoteControl.notificationEnabled"; + + public final static String K9_FOLDERS_NONE = "NONE"; + public final static String K9_FOLDERS_ALL = "ALL"; + public final static String K9_FOLDERS_FIRST_CLASS = "FIRST_CLASS"; + public final static String K9_FOLDERS_FIRST_AND_SECOND_CLASS = "FIRST_AND_SECOND_CLASS"; + public final static String K9_FOLDERS_NOT_SECOND_CLASS = "NOT_SECOND_CLASS"; + /** + * Key for the {@link Intent} Extra to set for controlling which folders to be synchronized with Push. + * Acceptable values are K9_FOLDERS_ALL, K9_FOLDERS_FIRST_CLASS, K9_FOLDERS_FIRST_AND_SECOND_CLASS, + * K9_FOLDERS_NOT_SECOND_CLASS, K9_FOLDERS_NONE + */ + public final static String K9_PUSH_CLASSES = "com.fsck.k9.K9RemoteControl.pushClasses"; + /** + * Key for the {@link Intent} Extra to set for controlling which folders to be synchronized with Poll. + * Acceptable values are K9_FOLDERS_ALL, K9_FOLDERS_FIRST_CLASS, K9_FOLDERS_FIRST_AND_SECOND_CLASS, + * K9_FOLDERS_NOT_SECOND_CLASS, K9_FOLDERS_NONE + */ + public final static String K9_POLL_CLASSES = "com.fsck.k9.K9RemoteControl.pollClasses"; + + public final static String[] K9_POLL_FREQUENCIES = { "-1", "1", "5", "10", "15", "30", "60", "120", "180", "360", "720", "1440"}; + /** + * Key for the {@link Intent} Extra to set with the desired poll frequency. The value is a String representing a number of minutes. + * Acceptable values are available in K9_POLL_FREQUENCIES + */ + public final static String K9_POLL_FREQUENCY = "com.fsck.k9.K9RemoteControl.pollFrequency"; + + /** + * Key for the {@link Intent} Extra to set for controlling K-9's global "Background sync" setting. + * Acceptable values are K9_BACKGROUND_OPERATIONS_ALWAYS, K9_BACKGROUND_OPERATIONS_NEVER + * K9_BACKGROUND_OPERATIONS_WHEN_CHECKED + */ + public final static String K9_BACKGROUND_OPERATIONS = "com.fsck.k9.K9RemoteControl.backgroundOperations"; + public final static String K9_BACKGROUND_OPERATIONS_WHEN_CHECKED = "WHEN_CHECKED"; + public final static String K9_BACKGROUND_OPERATIONS_ALWAYS = "ALWAYS"; + public final static String K9_BACKGROUND_OPERATIONS_NEVER = "NEVER"; + + /** + * Key for the {@link Intent} Extra to set for controlling which display theme K-9 will use. Acceptable values are + * K9_THEME_LIGHT, K9_THEME_DARK + */ + public final static String K9_THEME = "com.fsck.k9.K9RemoteControl.theme"; + public final static String K9_THEME_LIGHT = "LIGHT"; + public final static String K9_THEME_DARK = "DARK"; + +} diff --git a/src/com/fsck/k9/service/RemoteControlReceiver.java b/src/com/fsck/k9/service/RemoteControlReceiver.java new file mode 100644 index 000000000..ff42b911c --- /dev/null +++ b/src/com/fsck/k9/service/RemoteControlReceiver.java @@ -0,0 +1,57 @@ + +package com.fsck.k9.service; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import com.fsck.k9.Account; +import com.fsck.k9.K9; +import com.fsck.k9.K9RemoteControl; +import com.fsck.k9.Preferences; + +import static com.fsck.k9.K9RemoteControl.*; + +public class RemoteControlReceiver extends CoreReceiver +{ + public Integer receive(Context context, Intent intent, Integer tmpWakeLockId) + { + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "RemoteControlReceiver.onReceive" + intent); + + if (K9RemoteControl.K9_SET.equals(intent.getAction())) + { + RemoteControlService.set(context, intent, tmpWakeLockId); + tmpWakeLockId = null; + } + else if (K9RemoteControl.K9_REQUEST_ACCOUNTS.equals(intent.getAction())) + { + try + { + Preferences preferences = Preferences.getPreferences(context); + Account[] accounts = preferences.getAccounts(); + String[] uuids = new String[accounts.length]; + String[] descriptions = new String[accounts.length]; + for (int i = 0; i < accounts.length; i++) + { + Account account = accounts[i]; + + uuids[i] = account.getUuid(); + descriptions[i] = account.getDescription(); + } + Bundle bundle = getResultExtras(true); + bundle.putStringArray(K9_ACCOUNT_UUIDS, uuids); + bundle.putStringArray(K9_ACCOUNT_DESCRIPTIONS, descriptions); + } + catch (Exception e) + { + Log.e(K9.LOG_TAG, "Could not handle K9_RESPONSE_INTENT", e); + } + + } + + return tmpWakeLockId; + } + +} diff --git a/src/com/fsck/k9/service/RemoteControlService.java b/src/com/fsck/k9/service/RemoteControlService.java new file mode 100644 index 000000000..ffb88befd --- /dev/null +++ b/src/com/fsck/k9/service/RemoteControlService.java @@ -0,0 +1,167 @@ +package com.fsck.k9.service; + +import com.fsck.k9.Account; +import com.fsck.k9.K9; +import com.fsck.k9.K9RemoteControl; +import com.fsck.k9.Preferences; +import com.fsck.k9.R; +import com.fsck.k9.Account.FolderMode; + +import static com.fsck.k9.K9RemoteControl.*; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.util.Log; +import android.widget.Toast; + +public class RemoteControlService extends CoreService +{ + private final static String RESCHEDULE_ACTION = "com.fsck.k9.service.RemoteControlService.RESCHEDULE_ACTION"; + + private final static String SET_ACTION = "com.fsck.k9.service.RemoteControlService.SET_ACTION"; + + public static void set(Context context, Intent i, Integer wakeLockId) + { + // Intent i = new Intent(); + i.setClass(context, RemoteControlService.class); + i.setAction(RemoteControlService.SET_ACTION); + addWakeLockId(i, wakeLockId); + if (wakeLockId == null) + { + addWakeLock(context, i); + } + context.startService(i); + } + + public static final int REMOTE_CONTROL_SERVICE_WAKE_LOCK_TIMEOUT = 20000; + + @Override + public void startService(final Intent intent, final int startId) + { + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "RemoteControlService started with startId = " + startId); + final Preferences preferences = Preferences.getPreferences(this); + + if (RESCHEDULE_ACTION.equals(intent.getAction())) + { + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "RemoteControlService requesting MailService reschedule"); + MailService.actionReschedule(this, null); + } + else if (RemoteControlService.SET_ACTION.equals(intent.getAction())) + { + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "RemoteControlService got request to change settings"); + execute(getApplication(), new Runnable() + { + public void run() + { + try + { + String uuid = intent.getStringExtra(K9_ACCOUNT_UUID); + boolean allAccounts = intent.getBooleanExtra(K9_ALL_ACCOUNTS, false); + if (K9.DEBUG) + { + if (allAccounts) + { + Log.i(K9.LOG_TAG, "RemoteControlService changing settings for all accounts"); + } + else + { + Log.i(K9.LOG_TAG, "RemoteControlService changing settings for account with UUID " + uuid); + } + } + Account[] accounts = preferences.getAccounts(); + for (Account account : accounts) + { + if (allAccounts || account.getUuid().equals(uuid)) + { + + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "RemoteControlService changing settings for account " + account.getDescription()); + + String notificationEnabled = intent.getStringExtra(K9_NOTIFICATION_ENABLED); + String ringEnabled = intent.getStringExtra(K9_RING_ENABLED); + String vibrateEnabled = intent.getStringExtra(K9_VIBRATE_ENABLED); + String pushClasses = intent.getStringExtra(K9_PUSH_CLASSES); + String pollClasses = intent.getStringExtra(K9_POLL_CLASSES); + String pollFrequency = intent.getStringExtra(K9_POLL_FREQUENCY); + + if (notificationEnabled != null) + { + account.setNotifyNewMail(Boolean.parseBoolean(notificationEnabled)); + } + if (ringEnabled != null) + { + account.setRing(Boolean.parseBoolean(ringEnabled)); + } + if (vibrateEnabled != null) + { + account.setVibrate(Boolean.parseBoolean(vibrateEnabled)); + } + if (pushClasses != null) + { + account.setFolderPushMode(FolderMode.valueOf(pushClasses)); + } + if (pollClasses != null) + { + account.setFolderSyncMode(FolderMode.valueOf(pollClasses)); + } + if (pollFrequency != null) + { + String[] allowedFrequencies = getResources().getStringArray(R.array.account_settings_check_frequency_values); + for (String allowedFrequency : allowedFrequencies) + { + if (allowedFrequency.equals(pollFrequency)) + { + account.setAutomaticCheckIntervalMinutes(Integer.parseInt(allowedFrequency)); + } + } + } + account.save(Preferences.getPreferences(RemoteControlService.this)); + } + } + if (K9.DEBUG) + Log.i(K9.LOG_TAG, "RemoteControlService changing global settings"); + + String backgroundOps = intent.getStringExtra(K9_BACKGROUND_OPERATIONS); + if (K9RemoteControl.K9_BACKGROUND_OPERATIONS_ALWAYS.equals(backgroundOps) + || K9RemoteControl.K9_BACKGROUND_OPERATIONS_NEVER.equals(backgroundOps) + || K9RemoteControl.K9_BACKGROUND_OPERATIONS_WHEN_CHECKED.equals(backgroundOps)) + { + K9.setBackgroundOps(backgroundOps); + } + + String theme = intent.getStringExtra(K9_THEME); + if (theme != null) + { + K9.setK9Theme(K9RemoteControl.K9_THEME_DARK.equals(theme) ? android.R.style.Theme : android.R.style.Theme_Light); + } + + SharedPreferences sPrefs = preferences.getPreferences(); + + Editor editor = sPrefs.edit(); + K9.save(editor); + editor.commit(); + + Intent i = new Intent(); + i.setClassName(getApplication().getPackageName(), "com.fsck.k9.service.RemoteControlService"); + i.setAction(RESCHEDULE_ACTION); + long nextTime = System.currentTimeMillis() + 10000; + BootReceiver.scheduleIntent(RemoteControlService.this, nextTime, i); + } + catch (Exception e) + { + Log.e(K9.LOG_TAG, "Could not handle K9_SET", e); + Toast toast = Toast.makeText(RemoteControlService.this, e.getMessage(), Toast.LENGTH_LONG); + toast.show(); + } + } + } + , RemoteControlService.REMOTE_CONTROL_SERVICE_WAKE_LOCK_TIMEOUT, startId); + } + } + +}