Added quick responses.

Added "Insert quick response" button to MessageCompose's action bar. Clicking
it opens dialog with available quick responses. Selecting one of the responses
will insert it into message body at the current cursor location. Also added
menu in account preferences to create, edit, and delete quick responses.

Change-Id: I85f3f6b36801cf112ec9d7c31135a917456173d7
This commit is contained in:
Jorge Lugo 2011-06-01 10:09:26 -07:00
parent cb24e515b7
commit 5a3888f35b
15 changed files with 1133 additions and 11 deletions

View File

@ -1299,6 +1299,13 @@ public abstract class EmailContent {
public static final String POLICY_KEY = "policyKey";
}
public interface QuickResponseColumns {
// The QuickResponse text
static final String TEXT = "quickResponse";
// A foreign key into the Account table owning the QuickResponse
static final String ACCOUNT_KEY = "accountKey";
}
public interface MailboxColumns {
public static final String ID = "_id";
// The display name of this mailbox [INDEX]

View File

@ -0,0 +1,217 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.provider;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
import com.google.common.base.Objects;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A user-modifiable message that may be quickly inserted into the body while user is composing
* a message. Tied to a specific account.
*/
public final class QuickResponse extends EmailContent
implements QuickResponseColumns, Parcelable {
public static final String TABLE_NAME = "QuickResponse";
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI
+ "/quickresponse");
public static final Uri ACCOUNT_ID_URI = Uri.parse(
EmailContent.CONTENT_URI + "/quickresponse/account");
private String mText;
private long mAccountKey;
private static final int CONTENT_ID_COLUMN = 0;
private static final int CONTENT_QUICK_RESPONSE_COLUMN = 1;
private static final int CONTENT_ACCOUNT_KEY_COLUMN = 2;
public static final String[] CONTENT_PROJECTION = new String[] {
RECORD_ID,
QuickResponseColumns.TEXT,
QuickResponseColumns.ACCOUNT_KEY
};
/**
* Creates an empty QuickResponse. Restore should be called after.
*/
private QuickResponse() {
// empty
}
/**
* Constructor used by CREATOR for parceling.
*/
private QuickResponse(Parcel in) {
mBaseUri = CONTENT_URI;
mId = in.readLong();
mText = in.readString();
mAccountKey = in.readLong();
}
/**
* Creates QuickResponse associated with a particular account using the given string.
*/
public QuickResponse(long accountKey, String quickResponse) {
mBaseUri = CONTENT_URI;
mAccountKey = accountKey;
mText = quickResponse;
}
/**
* @see com.android.emailcommon.provider.EmailContent#restore(android.database.Cursor)
*/
@Override
public void restore(Cursor cursor) {
mBaseUri = CONTENT_URI;
mId = cursor.getLong(CONTENT_ID_COLUMN);
mText = cursor.getString(CONTENT_QUICK_RESPONSE_COLUMN);
mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
}
/**
* @see com.android.emailcommon.provider.EmailContent#toContentValues()
*/
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(QuickResponseColumns.TEXT, mText);
values.put(QuickResponseColumns.ACCOUNT_KEY, mAccountKey);
return values;
}
@Override
public String toString() {
return mText;
}
/**
* Given an array of QuickResponses, returns the an array of the String values
* corresponding to each QuickResponse.
*/
public static String[] getQuickResponseStrings(QuickResponse[] quickResponses) {
int count = quickResponses.length;
String[] quickResponseStrings = new String[count];
for (int i = 0; i < count; i++) {
quickResponseStrings[i] = quickResponses[i].toString();
}
return quickResponseStrings;
}
/**
* @param context
* @param accountId
* @return array of QuickResponses for the account with id accountId
*/
public static QuickResponse[] restoreQuickResponsesWithAccountId(Context context,
long accountId) {
Uri uri = ContentUris.withAppendedId(ACCOUNT_ID_URI, accountId);
Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
null, null, null);
try {
int count = c.getCount();
QuickResponse[] quickResponses = new QuickResponse[count];
for (int i = 0; i < count; ++i) {
c.moveToNext();
QuickResponse quickResponse = new QuickResponse();
quickResponse.restore(c);
quickResponses[i] = quickResponse;
}
return quickResponses;
} finally {
c.close();
}
}
/**
* Returns the base URI for this QuickResponse
*/
public Uri getBaseUri() {
return mBaseUri;
}
/**
* Returns the unique id for this QuickResponse
*/
public long getId() {
return mId;
}
@Override
public boolean equals(Object objectThat) {
if (this == objectThat) return true;
if (!(objectThat instanceof QuickResponse)) return false;
QuickResponse that = (QuickResponse) objectThat;
return
mText.equals(that.mText) &&
mId == that.mId &&
mAccountKey == that.mAccountKey;
}
@Override
public int hashCode() {
return Objects.hashCode(mId, mText, mAccountKey);
}
/**
* Implements Parcelable. Not used.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Implements Parcelable.
*/
public void writeToParcel(Parcel dest, int flags) {
// mBaseUri is not parceled
dest.writeLong(mId);
dest.writeString(mText);
dest.writeLong(mAccountKey);
}
/**
* Implements Parcelable
*/
public static final Parcelable.Creator<QuickResponse> CREATOR
= new Parcelable.Creator<QuickResponse>() {
@Override
public QuickResponse createFromParcel(Parcel in) {
return new QuickResponse(in);
}
@Override
public QuickResponse[] newArray(int size) {
return new QuickResponse[size];
}
};
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="@dimen/settings_fragment_padding_top"
android:paddingLeft="@dimen/settings_fragment_padding_left"
android:paddingRight="@dimen/settings_fragment_padding_right"
>
<!-- List of quick responses -->
<ListView
android:id="@+id/account_settings_quick_responses_list"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:listSelector="@android:drawable/list_selector_background"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<Button
android:id="@+id/create_new"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="@string/create_action" />
<Button
android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="@string/done_action" />
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/line_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="7dip"
android:focusable="true"
android:clickable="true"
>
<Button
android:id="@+id/delete_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:gravity="center_vertical"
android:text="@string/delete_quick_response_action"
/>
<TextView
android:id="@+id/quick_response_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/delete_button"
style="@style/accountSetupInfoText"
android:focusable="true"
android:gravity="center_vertical"
android:lines="2"
android:ellipsize="end"
/>
</RelativeLayout>

View File

@ -35,4 +35,9 @@
android:icon="@drawable/ic_menu_trash_holo_light"
android:alphabeticShortcut="q"
/>
<item
android:id="@+id/show_quick_text_list_dialog"
android:title="@string/show_quick_text_list_dialog_action"
android:alphabeticShortcut="r"
/>
</menu>

View File

@ -99,10 +99,17 @@
<string name="forward_action">Forward</string>
<!-- Button name used to complete a multi-step process -->
<string name="done_action">Done</string>
<!-- Button name used to create a new quick response [CHAR_LIMIT=16] -->
<string name="create_action">Create new</string>
<!-- Button name used to delete a quick response [CHAR_LIMIT=16] -->
<string name="delete_quick_response_action">Delete</string>
<!-- Menu item/button name -->
<string name="discard_action">Discard</string>
<!-- Menu item/button name [CHAR_LIMIT=16] -->
<string name="save_draft_action">Save draft</string>
<!-- Menu item/button name. Pressing will insert previously saved text
into message body. [CHAR_LIMIT=24] -->
<string name="show_quick_text_list_dialog_action">Insert quick response</string>
<!-- Menu item/button name -->
<string name="read_action">Mark read</string>
<!-- Menu item/button name -->
@ -332,6 +339,9 @@
<string name="message_compose_error_invalid_email">Some email addresses are invalid.</string>
<!-- Toast that appears when an attachment is too big to send. -->
<string name="message_compose_attachment_size">File too large to attach.</string>
<!-- Title for dialog containing list of quick responses that user may insert
into message body. [CHAR_LIMIT=30] -->
<string name="message_compose_insert_quick_response_list_title">Insert quick response</string>
<!-- Display name for composed message, indicating the destination of the message.
e.g. "John and 2 others" -->
<string name="message_compose_display_name"><xliff:g id="name" example="John">%1$s</xliff:g> and <xliff:g id="number" example="27">%2$d</xliff:g> others</string>
@ -935,11 +945,22 @@ save attachment.</string>
<string name="account_settings_name_label">Your name</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_signature_label">Signature</string>
<!-- On Settings screen, setting option name. Allows user to modify saved quick responses
for insertion in message body. [CHAR_LIMIT=24]-->
<string name="account_settings_edit_quick_responses_label">Quick responses</string>
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<string name="account_settings_edit_quick_responses_summary">
Edit text that you frequently insert when composing emails</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_signature_hint">Append text to messages you send</string>
<!-- On Settings screen, section heading -->
<string name="account_settings_notifications">Notification settings</string>
<!-- On settings screen, dialog heading informing user to edit a quick response -->
<string name="edit_quick_response_dialog">Edit quick response</string>
<!-- On settings screen, edit quick response dialog's "save" button -->
<string name="save_action">Save</string>
<!-- On settings screen, sync contacts check box label [CHAR LIMIT=20]-->
<string name="account_settings_sync_contacts_enable">Sync Contacts</string>
<!-- On settings screen, sync contacts summary text [CHAR LIMIT=35] -->

View File

@ -43,26 +43,32 @@
android:dialogTitle="@string/account_settings_signature_label"
android:inputType="textCapSentences|textMultiLine" />
<PreferenceScreen
android:key="account_quick_responses"
android:order="4"
android:title="@string/account_settings_edit_quick_responses_label"
android:summary="@string/account_settings_edit_quick_responses_summary" />
<ListPreference
android:key="account_check_frequency"
android:order="4"
android:order="5"
android:title="@string/account_settings_mail_check_frequency_label"
android:entries="@array/account_settings_check_frequency_entries"
android:entryValues="@array/account_settings_check_frequency_values"
android:dialogTitle="@string/account_settings_mail_check_frequency_label" />
<!-- Reserve order#5 here for window size (if inserted) -->
<!-- Reserve order#6 here for window size (if inserted) -->
<!-- (will hide on POP3 accounts) -->
<CheckBoxPreference
android:key="account_background_attachments"
android:order="6"
android:order="7"
android:title="@string/account_settings_background_attachments_label"
android:summary="@string/account_settings_background_attachments_summary" />
<CheckBoxPreference
android:key="account_default"
android:order="7"
android:order="8"
android:title="@string/account_settings_default_label"
android:summary="@string/account_settings_default_summary" />
@ -99,10 +105,10 @@
</PreferenceCategory>
<PreferenceCategory
<PreferenceCategory
android:key="account_servers"
android:title="@string/account_settings_servers">
<PreferenceScreen
android:key="incoming"
android:title="@string/account_settings_incoming_label"

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.R;
import com.android.email.activity.setup.
AccountSettingsEditQuickResponsesFragment.QuickResponseFinder;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.utility.EmailAsyncTask;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
/**
* Dialog which lists QuickResponses for the specified account. On user selection, will call
* Callback.onQuickResponseSelected() with the selected QuickResponse text.
*/
public class InsertQuickResponseDialog extends DialogFragment
implements DialogInterface.OnClickListener, OnItemClickListener {
private ListView mQuickResponsesView;
private EmailAsyncTask.Tracker mTaskTracker;
// Key for the Account object in the arguments bundle
private static final String ACCOUNT_KEY = "account";
/**
* Callback interface for when user selects a QuickResponse.
*/
public interface Callback {
/**
* Handles the text of the selected QuickResponse.
*/
public void onQuickResponseSelected(CharSequence quickResponse);
}
/**
* Create and returns new dialog.
*
* @param callbackFragment fragment that implements {@link Callback}. Or null, in which case
* the parent activity must implement {@link Callback}.
*/
public static InsertQuickResponseDialog
newInstance(Fragment callbackFragment, Account account) {
final InsertQuickResponseDialog dialog = new InsertQuickResponseDialog();
// If a target is set, it MUST implement Callback. Fail-fast if not.
final Callback callback;
if (callbackFragment != null) {
try {
callback = (Callback) callbackFragment;
} catch (ClassCastException e) {
throw new ClassCastException(callbackFragment.toString()
+ " must implement Callback");
}
dialog.setTargetFragment(callbackFragment, 0);
}
Bundle args = new Bundle();
args.putParcelable(ACCOUNT_KEY, account);
dialog.setArguments(args);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// If target not set, the parent activity MUST implement Callback. Fail-fast if not.
final Fragment targetFragment = getTargetFragment();
if (targetFragment != null) {
final Callback callback;
try {
callback = (Callback) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(getActivity().toString() + " must implement Callback");
}
}
// Now that Callback implementation is verified, build the dialog
final Context context = getActivity();
final AlertDialog.Builder b = new AlertDialog.Builder(context);
mQuickResponsesView = new ListView(context);
Account account = (Account) getArguments().getParcelable(ACCOUNT_KEY);
mTaskTracker = new EmailAsyncTask.Tracker();
new QuickResponseFinder(mTaskTracker, account.mId, mQuickResponsesView,
context, null, this, false).executeParallel();
b.setTitle(getResources()
.getString(R.string.message_compose_insert_quick_response_list_title))
.setView(mQuickResponsesView)
.setNegativeButton(R.string.cancel_action, this);
return b.create();
}
@Override
public void onDestroy() {
mTaskTracker.cancellAllInterrupt();
super.onDestroy();
}
/**
* Implements OnItemClickListener.
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
getCallback().onQuickResponseSelected(
mQuickResponsesView.getItemAtPosition(position).toString());
dismiss();
}
/**
* Implements DialogInterface.OnClickListener
*/
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
dialog.cancel();
}
}
private Callback getCallback() {
Fragment targetFragment = getTargetFragment();
if (targetFragment != null) {
return (Callback) targetFragment;
}
return (Callback) getActivity();
}
}

View File

@ -106,7 +106,7 @@ import java.util.concurrent.ExecutionException;
* N: add attachment
*/
public class MessageCompose extends Activity implements OnClickListener, OnFocusChangeListener,
DeleteMessageConfirmationDialog.Callback {
DeleteMessageConfirmationDialog.Callback, InsertQuickResponseDialog.Callback {
private static final String ACTION_REPLY = "com.android.email.intent.action.REPLY";
private static final String ACTION_REPLY_ALL = "com.android.email.intent.action.REPLY_ALL";
@ -1298,6 +1298,21 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
}
}
private void showQuickResponseDialog() {
InsertQuickResponseDialog.newInstance(null, mAccount)
.show(getFragmentManager(), null);
}
/**
* Inserts the selected QuickResponse into the message body at the current cursor position.
*/
@Override
public void onQuickResponseSelected(CharSequence text) {
int start = mMessageContentView.getSelectionStart();
int end = mMessageContentView.getSelectionEnd();
mMessageContentView.getEditableText().replace(start, end, text);
}
private void onDiscard() {
DeleteMessageConfirmationDialog.newInstance(1, null).show(getFragmentManager(), "dialog");
}
@ -1546,6 +1561,9 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
case R.id.save:
onSave();
return true;
case R.id.show_quick_text_list_dialog:
showQuickResponseDialog();
return true;
case R.id.discard:
onDiscard();
return true;

View File

@ -64,7 +64,8 @@ import java.util.List;
* sense to use a loader for the accounts list, because it would provide better support for
* dealing with accounts being added/deleted and triggering the header reload.
*/
public class AccountSettings extends PreferenceActivity {
public class AccountSettings extends PreferenceActivity
implements AccountSettingsEditQuickResponsesFragment.Callback {
/*
* Intent to open account settings for account=1
adb shell am start -a android.intent.action.EDIT \
@ -83,6 +84,9 @@ public class AccountSettings extends PreferenceActivity {
// NOTE: This constant should eventually be defined in android.accounts.Constants
private static final String EXTRA_ACCOUNT_MANAGER_ACCOUNT = "account";
// Key for arguments bundle for QuickResponse editing
private static final String QUICK_RESPONSE_ACCOUNT_KEY = "account";
// Key codes used to open a debug settings fragment.
private static final int[] SECRET_KEY_CODES = {
KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U,
@ -568,6 +572,10 @@ public class AccountSettings extends PreferenceActivity {
AccountSettings.this.onSettingsChanged(account, preference, value);
}
@Override
public void onEditQuickResponses(Account account) {
AccountSettings.this.onEditQuickResponses(account);
}
@Override
public void onIncomingSettings(Account account) {
AccountSettings.this.onIncomingSettings(account);
}
@ -633,6 +641,28 @@ public class AccountSettings extends PreferenceActivity {
}
}
/**
* Dispatch to edit quick responses.
*/
public void onEditQuickResponses(Account account) {
try {
Bundle args = new Bundle();
args.putParcelable(QUICK_RESPONSE_ACCOUNT_KEY, account);
startPreferencePanel(AccountSettingsEditQuickResponsesFragment.class.getName(), args,
R.string.account_settings_edit_quick_responses_label, null, null, 0);
} catch (Exception e) {
Log.d(Logging.LOG_TAG, "Error while trying to invoke edit quick responses.", e);
}
}
/**
* Implements AccountSettingsEditQuickResponsesFragment.Callback
*/
@Override
public void onEditQuickResponsesDone() {
getFragmentManager().popBackStack();
}
/**
* Dispatch to edit incoming settings.
*

View File

@ -0,0 +1,287 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity.setup;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.activity.UiUtilities;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.QuickResponse;
import com.android.emailcommon.utility.EmailAsyncTask;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
/**
* Lists quick responses associated with the specified email account. Allows users to create,
* edit, and delete quick responses. Owning activity must:
* <ul>
* <li>Implement Callback to properly dismiss this fragment.</li>
* <li>Provide an Account as an argument named "account". This account's quick responses
* will be read and potentially modified.</li>
* </ul>
*
* <p>This fragment is run as a preference panel from AccountSettings.</p>
*/
public class AccountSettingsEditQuickResponsesFragment extends Fragment
implements OnClickListener {
private ListView mQuickResponsesView;
private Account mAccount;
private Context mContext;
private Callback mListen;
private EmailAsyncTask.Tracker mTaskTracker;
/**
* Allows this fragment to properly dismiss itself via the Callback's implementation.
*/
public interface Callback {
/**
* Dismisses the fragment.
*/
public void onEditQuickResponsesDone();
}
// Helper class to place a TextView alongside "Delete" button in the ListView
// displaying the QuickResponses
private static class ArrayAdapterWithButtons extends ArrayAdapter<QuickResponse> {
private QuickResponse[] mQuickResponses;
private final long mAccountId;
private final Context mContext;
private final FragmentManager mFragmentManager;
private OnClickListener mOnEditListener = new OnClickListener() {
@Override
public void onClick(View view) {
QuickResponse quickResponse = (QuickResponse) (view.getTag());
EditQuickResponseDialog
.newInstance(quickResponse, mAccountId)
.show(mFragmentManager, null);
}
};
private OnClickListener mOnDeleteListener = new OnClickListener() {
@Override
public void onClick(View view) {
final QuickResponse quickResponse = (QuickResponse) view.getTag();
// Delete the QuickResponse from the database. Content watchers used to
// update the ListView of QuickResponses upon deletion.
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
EmailContent.delete(mContext, quickResponse.getBaseUri(),
quickResponse.getId());
}
});
}
};
private static final int resourceId = R.layout.quick_response_item;
private static final int textViewId = R.id.quick_response_text;
/**
* Instantiates the custom ArrayAdapter, allowing editing and deletion of QuickResponses.
* @param context - context of owning activity
* @param quickResponses - the QuickResponses to represent in the ListView.
* @param fragmentManager - fragmentManager to which an EditQuickResponseDialog will
* attach itself.
* @param accountId - accountId of the QuickResponses
*/
public ArrayAdapterWithButtons(
Context context, QuickResponse[] quickResponses,
FragmentManager fragmentManager, long accountId) {
super(context, resourceId, textViewId, quickResponses);
mQuickResponses = quickResponses;
mAccountId = accountId;
mContext = context;
mFragmentManager = fragmentManager;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
convertView = super.getView(position, convertView, parent);
convertView.setTag(mQuickResponses[position]);
convertView.setOnClickListener(mOnEditListener);
Button deleteButton = (Button) convertView.findViewById(R.id.delete_button);
deleteButton.setTag(mQuickResponses[position]);
deleteButton.setOnClickListener(mOnDeleteListener);
return convertView;
}
}
/**
* Finds existing QuickResponses for the specified account and attaches the contents to
* a ListView. Optionally allows for editing and deleting of QuickResposnes from ListView.
*/
public static class QuickResponseFinder extends EmailAsyncTask<Void, Void, QuickResponse[]> {
private final long mAccountId;
private final ListView mQuickResponsesView;
private final Context mContext;
private final FragmentManager mFragmentManager;
private final OnItemClickListener mListener;
private final boolean mIsEditable;
/**
* Finds all QuickResponses for the given account. Creates either a standard ListView
* with a caller-implemented listener or one with a custom adapter that allows deleting
* and editing of QuickResponses via EditQuickResponseDialog.
*
* @param tracker - tracks the finding and listing of QuickResponses. Should be canceled
* onDestroy() or when the results are no longer needed.
* @param accountId - id of the account whose QuickResponses are to be returned
* @param quickResponsesView - ListView to which an ArrayAdapter with the QuickResponses
* will be attached.
* @param context - context of the owning activity
* @param fragmentManager - required when isEditable is true so that an EditQuickResponse
* dialog may properly attach itself. Unused when isEditable is false.
* @param listener - optional when isEditable is true, unused when false.
* @param isEditable - specifies whether the ListView will allow for user editing of
* QuickResponses
*/
public QuickResponseFinder(EmailAsyncTask.Tracker tracker, long accountId,
ListView quickResponsesView, Context context, FragmentManager fragmentManager,
OnItemClickListener listener, boolean isEditable) {
super(tracker);
mAccountId = accountId;
mQuickResponsesView = quickResponsesView;
mContext = context;
mFragmentManager = fragmentManager;
mListener = listener;
mIsEditable = isEditable;
}
@Override
protected QuickResponse[] doInBackground(Void... params) {
QuickResponse[] quickResponses = QuickResponse.restoreQuickResponsesWithAccountId(
mContext, mAccountId);
return quickResponses;
}
@Override
protected void onPostExecute(QuickResponse[] quickResponseItems) {
ArrayAdapter<QuickResponse> adapter;
if (mIsEditable) {
adapter = new ArrayAdapterWithButtons(
mContext,
quickResponseItems,
mFragmentManager,
mAccountId);
} else {
adapter = new ArrayAdapter<QuickResponse>(
mContext,
android.R.layout.simple_selectable_list_item,
quickResponseItems
);
mQuickResponsesView.setOnItemClickListener(mListener);
}
mQuickResponsesView.setAdapter(adapter);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListen = (Callback) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement Callback");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, "AccountSettingsEditQuickResponsesFragment onCreate");
}
super.onCreate(savedInstanceState);
Bundle args = getArguments();
mAccount = args.getParcelable("account");
mTaskTracker = new EmailAsyncTask.Tracker();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Logging.LOG_TAG, "AccountSettingsEditQuickResponsesFragment onCreateView");
}
int layoutId = R.layout.account_settings_edit_quick_responses_fragment;
View view = inflater.inflate(layoutId, container, false);
mContext = getActivity();
mQuickResponsesView = UiUtilities.getView(view,
R.id.account_settings_quick_responses_list);
new QuickResponseFinder(mTaskTracker, mAccount.mId, mQuickResponsesView,
mContext, getFragmentManager(), null, true)
.executeParallel();
this.getActivity().getContentResolver().registerContentObserver(
QuickResponse.CONTENT_URI, false, new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
new QuickResponseFinder(mTaskTracker, mAccount.mId, mQuickResponsesView,
mContext, getFragmentManager(), null, true)
.executeParallel();
}
});
UiUtilities.getView(view, R.id.done).setOnClickListener(this);
UiUtilities.getView(view, R.id.create_new).setOnClickListener(this);
return view;
}
@Override
public void onDestroy() {
mTaskTracker.cancellAllInterrupt();
super.onDestroy();
}
/**
* Implements OnClickListener
*/
@Override
public void onClick(View v) {
if (v.getId() == R.id.done) {
mListen.onEditQuickResponsesDone();
} else if (v.getId() == R.id.create_new) {
EditQuickResponseDialog
.newInstance(null, mAccount.mId)
.show(getFragmentManager(), null);
}
}
}

View File

@ -71,6 +71,7 @@ public class AccountSettingsFragment extends PreferenceFragment {
public static final String PREFERENCE_DESCRIPTION = "account_description";
private static final String PREFERENCE_NAME = "account_name";
private static final String PREFERENCE_SIGNATURE = "account_signature";
private static final String PREFERENCE_QUICK_RESPONSES = "account_quick_responses";
private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
private static final String PREFERENCE_BACKGROUND_ATTACHMENTS =
"account_background_attachments";
@ -126,6 +127,7 @@ public class AccountSettingsFragment extends PreferenceFragment {
*/
public interface Callback {
public void onSettingsChanged(Account account, String preference, Object value);
public void onEditQuickResponses(Account account);
public void onIncomingSettings(Account account);
public void onOutgoingSettings(Account account);
public void abandonEdit();
@ -135,6 +137,7 @@ public class AccountSettingsFragment extends PreferenceFragment {
private static class EmptyCallback implements Callback {
public static final Callback INSTANCE = new EmptyCallback();
@Override public void onSettingsChanged(Account account, String preference, Object value) {}
@Override public void onEditQuickResponses(Account account) {}
@Override public void onIncomingSettings(Account account) {}
@Override public void onOutgoingSettings(Account account) {}
@Override public void abandonEdit() {}
@ -437,6 +440,16 @@ public class AccountSettingsFragment extends PreferenceFragment {
}
});
findPreference(PREFERENCE_QUICK_RESPONSES).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
mAccountDirty = true;
mCallback.onEditQuickResponses(mAccount);
return true;
}
});
// Add check window preference
mSyncWindow = null;
if (HostAuth.SCHEME_EAS.equals(protocol)) {
@ -446,7 +459,7 @@ public class AccountSettingsFragment extends PreferenceFragment {
mSyncWindow.setEntryValues(R.array.account_settings_mail_window_values);
mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback()));
mSyncWindow.setSummary(mSyncWindow.getEntry());
mSyncWindow.setOrder(4);
mSyncWindow.setOrder(5);
mSyncWindow.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();

View File

@ -0,0 +1,135 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.email.activity.setup;
import com.android.email.R;
import com.android.emailcommon.provider.QuickResponse;
import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
import com.android.emailcommon.utility.EmailAsyncTask;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.EditText;
/**
* Dialog to edit the text of a given or new quick response
*/
public class EditQuickResponseDialog extends DialogFragment
implements DialogInterface.OnClickListener {
private EditText mQuickResponseEditText;
private QuickResponse mQuickResponse;
private static final String QUICK_RESPONSE_EDITED_STRING = "quick_response_edited_string";
private static final String QUICK_RESPONSE = "quick_response";
/**
* Creates a new dialog to edit an existing QuickResponse or create a new
* one.
*
* @param quickResponse - The QuickResponse fwhich the user is editing;
* null if user is creating a new QuickResponse.
* @param accountId - The accountId for the account which holds this QuickResponse
*/
public static EditQuickResponseDialog newInstance(
QuickResponse quickResponse, long accountId) {
final EditQuickResponseDialog dialog = new EditQuickResponseDialog();
Bundle args = new Bundle();
args.putLong("accountId", accountId);
if (quickResponse != null) {
args.putParcelable(QUICK_RESPONSE, quickResponse);
}
dialog.setArguments(args);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder b = new AlertDialog.Builder(context);
mQuickResponseEditText = new EditText(context);
mQuickResponse = (QuickResponse) getArguments().getParcelable(QUICK_RESPONSE);
if (savedInstanceState != null) {
String quickResponseSavedString =
savedInstanceState.getString(QUICK_RESPONSE_EDITED_STRING);
if (quickResponseSavedString != null) {
mQuickResponseEditText.setText(quickResponseSavedString);
}
} else if (mQuickResponse != null) {
mQuickResponseEditText.setText(mQuickResponse.toString());
}
mQuickResponseEditText.setSelection(mQuickResponseEditText.length());
b.setTitle(getResources().getString(R.string.edit_quick_response_dialog))
.setView(mQuickResponseEditText)
.setNegativeButton(R.string.cancel_action, this)
.setPositiveButton(R.string.save_action, this);
return b.create();
}
// Saves contents during orientation change
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(
QUICK_RESPONSE_EDITED_STRING, mQuickResponseEditText.getText().toString());
}
/**
* Implements DialogInterface.OnClickListener
*/
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
dialog.cancel();
break;
case DialogInterface.BUTTON_POSITIVE:
final long accountId = getArguments().getLong("accountId");
final String text = mQuickResponseEditText.getText().toString();
final Context context = getActivity();
if (mQuickResponse == null) {
mQuickResponse = new QuickResponse(accountId, text);
}
// Insert the new QuickResponse into the database. Content watchers used to
// update the ListView of QuickResponses upon insertion.
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
if (!mQuickResponse.isSaved()) {
mQuickResponse.save(context);
} else {
ContentValues values = new ContentValues();
values.put(QuickResponseColumns.TEXT, text);
mQuickResponse.update(context, values);
}
}
});
break;
}
}
}

View File

@ -29,6 +29,8 @@ import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
import com.android.emailcommon.provider.EmailContent.Body;
import com.android.emailcommon.provider.EmailContent.BodyColumns;
import com.android.emailcommon.provider.QuickResponse;
import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.EmailContent.Message;
@ -142,8 +144,9 @@ public class EmailProvider extends ContentProvider {
// Version 22: Upgrade path for IMAP/POP accounts to integrate with AccountManager
// Version 23: Add column to mailbox table for time of last access
// Version 24: Add column to hostauth table for client cert alias
// Version 25: Added QuickResponse table
public static final int DATABASE_VERSION = 24;
public static final int DATABASE_VERSION = 25;
// Any changes to the database format *must* include update-in-place code.
// Original version: 2
@ -191,8 +194,13 @@ public class EmailProvider extends ContentProvider {
private static final int POLICY = POLICY_BASE;
private static final int POLICY_ID = POLICY_BASE + 1;
private static final int QUICK_RESPONSE_BASE = 0x8000;
private static final int QUICK_RESPONSE = QUICK_RESPONSE_BASE;
private static final int QUICK_RESPONSE_ID = QUICK_RESPONSE_BASE + 1;
private static final int QUICK_RESPONSE_ACCOUNT_ID = QUICK_RESPONSE_BASE + 2;
// MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS
private static final int LAST_EMAIL_PROVIDER_DB_BASE = POLICY_BASE;
private static final int LAST_EMAIL_PROVIDER_DB_BASE = QUICK_RESPONSE_BASE;
// DO NOT CHANGE BODY_BASE!!
private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000;
@ -212,6 +220,7 @@ public class EmailProvider extends ContentProvider {
EmailContent.Message.UPDATED_TABLE_NAME,
EmailContent.Message.DELETED_TABLE_NAME,
Policy.TABLE_NAME,
QuickResponse.TABLE_NAME,
EmailContent.Body.TABLE_NAME
};
@ -225,6 +234,7 @@ public class EmailProvider extends ContentProvider {
null, // Updated message
null, // Deleted message
sCachePolicy,
null, // Quick response
null // Body
};
@ -363,6 +373,14 @@ public class EmailProvider extends ContentProvider {
matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
// All quick responses
matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
// A specific quick response
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
// All quick responses associated with a particular account id
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
QUICK_RESPONSE_ACCOUNT_ID);
}
@ -688,6 +706,14 @@ public class EmailProvider extends ContentProvider {
createAttachmentTable(db);
}
static void createQuickResponseTable(SQLiteDatabase db) {
String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
+ QuickResponseColumns.TEXT + " text, "
+ QuickResponseColumns.ACCOUNT_KEY + " integer"
+ ");";
db.execSQL("create table " + QuickResponse.TABLE_NAME + s);
}
static void createBodyTable(SQLiteDatabase db) {
String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
+ BodyColumns.MESSAGE_KEY + " integer, "
@ -859,6 +885,7 @@ public class EmailProvider extends ContentProvider {
createHostAuthTable(db);
createAccountTable(db);
createPolicyTable(db);
createQuickResponseTable(db);
}
@Override
@ -1090,6 +1117,10 @@ public class EmailProvider extends ContentProvider {
upgradeFromVersion23ToVersion24(db);
oldVersion = 24;
}
if (oldVersion == 24) {
upgradeFromVersion24ToVersion25(db);
oldVersion = 25;
}
}
@Override
@ -1148,6 +1179,7 @@ public class EmailProvider extends ContentProvider {
case ACCOUNT_ID:
case HOSTAUTH_ID:
case POLICY_ID:
case QUICK_RESPONSE_ID:
id = uri.getPathSegments().get(1);
if (match == SYNCED_MESSAGE_ID) {
// For synced messages, first copy the old message to the deleted table and
@ -1328,6 +1360,7 @@ public class EmailProvider extends ContentProvider {
case ACCOUNT:
case HOSTAUTH:
case POLICY:
case QUICK_RESPONSE:
longId = db.insert(TABLE_NAMES[table], "foo", values);
resultUri = ContentUris.withAppendedId(uri, longId);
// Clients shouldn't normally be adding rows to these tables, as they are
@ -1480,6 +1513,7 @@ public class EmailProvider extends ContentProvider {
case ACCOUNT:
case HOSTAUTH:
case POLICY:
case QUICK_RESPONSE:
c = db.query(tableName, projection,
selection, selectionArgs, null, null, sortOrder, limit);
break;
@ -1492,6 +1526,7 @@ public class EmailProvider extends ContentProvider {
case ACCOUNT_ID:
case HOSTAUTH_ID:
case POLICY_ID:
case QUICK_RESPONSE_ID:
id = uri.getPathSegments().get(1);
if (cache != null) {
c = cache.getCachedCursor(id, projection);
@ -1515,6 +1550,13 @@ public class EmailProvider extends ContentProvider {
whereWith(Attachment.MESSAGE_KEY + "=" + id, selection),
selectionArgs, null, null, sortOrder, limit);
break;
case QUICK_RESPONSE_ACCOUNT_ID:
// All quick responses for the given account
id = uri.getPathSegments().get(2);
c = db.query(QuickResponse.TABLE_NAME, projection,
whereWith(QuickResponse.ACCOUNT_KEY + "=" + id, selection),
selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
@ -1784,6 +1826,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX_ID:
case ACCOUNT_ID:
case HOSTAUTH_ID:
case QUICK_RESPONSE_ID:
id = uri.getPathSegments().get(1);
if (cache != null) {
cache.lock(id);
@ -2104,4 +2147,14 @@ public class EmailProvider extends ContentProvider {
Log.w(TAG, "Exception upgrading EmailProvider.db from 23 to 24 " + e);
}
}
/** Upgrades the database from v24 to v25 by creating table for quick responses */
private static void upgradeFromVersion24ToVersion25(SQLiteDatabase db) {
try {
createQuickResponseTable(db);
} catch (SQLException e) {
// Shouldn't be needed unless we're debugging and interrupt the process
Log.w(TAG, "Exception upgrading EmailProvider.db from 24 to 25 " + e);
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.provider;
import com.android.email.provider.ContentCache;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import com.android.emailcommon.provider.QuickResponse;
import com.android.emailcommon.utility.Utility;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.test.MoreAsserts;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import java.util.Arrays;
/**
* Unit tests for the QuickResponse class
*/
@SmallTest
public class QuickResponseTests extends ProviderTestCase2<EmailProvider> {
private Context mMockContext;
private EmailProvider mProvider;
public QuickResponseTests() {
super(EmailProvider.class, EmailContent.AUTHORITY);
}
@Override
public void setUp() throws Exception {
super.setUp();
mMockContext = getMockContext();
mProvider = getProvider();
// Invalidate all caches, since we reset the database for each test
ContentCache.invalidateAllCachesForTest();
}
public void testParcelling() {
QuickResponse original = new QuickResponse(7, "quick response text");
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
// Reset.
p.setDataPosition(0);
QuickResponse unparcelled = QuickResponse.CREATOR.createFromParcel(p);
assert(original.equals(unparcelled));
QuickResponse phony = new QuickResponse(17, "quick response text");
assert(!(phony.equals(unparcelled)));
QuickResponse phony2 = new QuickResponse(7, "different text");
assert(!(phony2.equals(unparcelled)));
p.recycle();
}
}