MoveMessageToDialog.java revision 56034cab2022fe78ccbf4635617b1a54b4cc16b8
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity; 18 19import com.android.email.R; 20import com.android.email.Utility; 21import com.android.email.provider.EmailContent.Account; 22import com.android.email.provider.EmailContent.Mailbox; 23import com.android.email.provider.EmailContent.Message; 24 25import android.app.Activity; 26import android.app.AlertDialog; 27import android.app.Dialog; 28import android.app.DialogFragment; 29import android.app.Fragment; 30import android.app.LoaderManager; 31import android.content.AsyncTaskLoader; 32import android.content.Context; 33import android.content.DialogInterface; 34import android.content.Loader; 35import android.database.Cursor; 36import android.os.Bundle; 37 38import java.security.InvalidParameterException; 39 40/** 41 * "Move (messages) to" dialog. 42 * 43 * TODO The check logic in MessageCheckerCallback is not efficient. It shouldn't restore full 44 * Message objects. But we don't bother at this point as the UI is still temporary. 45 */ 46public class MoveMessageToDialog extends DialogFragment implements DialogInterface.OnClickListener { 47 private static final String BUNDLE_MESSAGE_IDS = "message_ids"; 48 49 /** Message IDs passed to {@link #newInstance} */ 50 private long[] mMessageIds; 51 private MailboxesAdapter mAdapter; 52 53 /** Account ID is restored by {@link MailboxesLoaderCallbacks} */ 54 private long mAccountId; 55 56 private boolean mDestroyed; 57 58 /** 59 * Callback that target fragments, or the owner activity should implement. 60 */ 61 public interface Callback { 62 public void onMoveToMailboxSelected(long newMailboxId, long[] messageIds); 63 } 64 65 /** 66 * Create and return a new instance. 67 * 68 * @param parent owner activity. 69 * @param messageIds IDs of the messages to be moved. 70 * @param callbackFragment Fragment that gets a callback. The fragment must implement 71 * {@link Callback}. If null is passed, then the owner activity is used instead, in which case 72 * it must implement {@link Callback} instead. 73 */ 74 public static MoveMessageToDialog newInstance(Activity parent, 75 long[] messageIds, Fragment callbackFragment) { 76 if (messageIds.length == 0) { 77 throw new InvalidParameterException(); 78 } 79 MoveMessageToDialog dialog = new MoveMessageToDialog(); 80 Bundle args = new Bundle(); 81 args.putLongArray(BUNDLE_MESSAGE_IDS, messageIds); 82 dialog.setArguments(args); 83 if (callbackFragment != null) { 84 dialog.setTargetFragment(callbackFragment, 0); 85 } 86 return dialog; 87 } 88 89 @Override 90 public void onCreate(Bundle savedInstanceState) { 91 super.onCreate(savedInstanceState); 92 mMessageIds = getArguments().getLongArray(BUNDLE_MESSAGE_IDS); 93 setStyle(STYLE_NORMAL, android.R.style.Theme_Light_Holo); 94 } 95 96 @Override 97 public void onDestroy() { 98 LoaderManager lm = getActivity().getLoaderManager(); 99 lm.stopLoader(ActivityHelper.GLOBAL_LOADER_ID_MOVE_TO_DIALOG_MESSAGE_CHECKER); 100 lm.stopLoader(ActivityHelper.GLOBAL_LOADER_ID_MOVE_TO_DIALOG_MAILBOX_LOADER); 101 mDestroyed = true; 102 super.onDestroy(); 103 } 104 105 @Override 106 public Dialog onCreateDialog(Bundle savedInstanceState) { 107 final Activity activity = getActivity(); 108 109 // Build adapter & dialog 110 // Make sure to pass Builder's context to the adapter, so that it'll get the correct theme. 111 AlertDialog.Builder builder = new AlertDialog.Builder(activity) 112 .setTitle(activity.getResources().getString(R.string.move_to_folder_dialog_title)); 113 114 mAdapter = new MailboxesAdapter(builder.getContext(), MailboxesAdapter.MODE_MOVE_TO_TARGET); 115 builder.setSingleChoiceItems(mAdapter, -1, this); 116 117 activity.getLoaderManager().initLoader( 118 ActivityHelper.GLOBAL_LOADER_ID_MOVE_TO_DIALOG_MESSAGE_CHECKER, 119 null, new MessageCheckerCallback()); 120 121 return builder.show(); 122 } 123 124 @Override 125 public void onClick(DialogInterface dialog, int position) { 126 final long mailboxId = mAdapter.getItemId(position); 127 128 getCallback().onMoveToMailboxSelected(mailboxId, mMessageIds); 129 dismiss(); 130 } 131 132 private Callback getCallback() { 133 Fragment targetFragment = getTargetFragment(); 134 if (targetFragment != null) { 135 // If a target is set, it MUST implement Callback. 136 return (Callback) targetFragment; 137 } 138 // If not the parent activity MUST implement Callback. 139 return (Callback) getActivity(); 140 } 141 142 /** 143 * Loader callback for {@link MessageChecker} 144 */ 145 private class MessageCheckerCallback implements LoaderManager.LoaderCallbacks<Long> { 146 @Override 147 public Loader<Long> onCreateLoader(int id, Bundle args) { 148 return new MessageChecker(getActivity(), mMessageIds); 149 } 150 151 @Override 152 public void onLoadFinished(Loader<Long> loader, Long accountId) { 153 if (mDestroyed) { 154 return; 155 } 156 // accountId shouldn't be null, but I'm paranoia. 157 if ((accountId == null) || (accountId == -1)) { 158 dismiss(); // Some of the messages can't be moved. 159 return; 160 } 161 mAccountId = accountId; 162 getActivity().getLoaderManager().initLoader( 163 ActivityHelper.GLOBAL_LOADER_ID_MOVE_TO_DIALOG_MAILBOX_LOADER, 164 null, new MailboxesLoaderCallbacks()); 165 } 166 } 167 168 /** 169 * Loader callback for destination mailbox list. 170 */ 171 private class MailboxesLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> { 172 @Override 173 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 174 return MailboxesAdapter.createLoader(getActivity().getApplicationContext(), mAccountId, 175 MailboxesAdapter.MODE_MOVE_TO_TARGET); 176 } 177 178 @Override 179 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 180 if (mDestroyed) { 181 return; 182 } 183 mAdapter.changeCursor(data); 184 } 185 } 186 187 /** 188 * A loader that checks if the messages can be moved, and return the Id of the account that owns 189 * the messages. (If any of the messages can't be moved, return -1.) 190 */ 191 private static class MessageChecker extends AsyncTaskLoader<Long> { 192 private final Activity mActivity; 193 private final long[] mMessageIds; 194 195 public MessageChecker(Activity activity, long[] messageIds) { 196 super(activity); 197 mActivity = activity; 198 mMessageIds = messageIds; 199 } 200 201 @Override 202 public Long loadInBackground() { 203 final Context c = getContext(); 204 205 long accountId = -1; // -1 == account not found yet. 206 207 for (long messageId : mMessageIds) { 208 // TODO This shouln't restore a full Message object. 209 final Message message = Message.restoreMessageWithId(c, messageId); 210 if (message == null) { 211 continue; // Skip removed messages. 212 } 213 214 // First, check account. 215 if (accountId == -1) { 216 // First message -- see if the account supports move. 217 accountId = message.mAccountKey; 218 if (!Account.supportsMoveMessages(c, accountId)) { 219 Utility.showToast( 220 mActivity, R.string.cannot_move_protocol_not_supported_toast); 221 return -1L; 222 } 223 } else { 224 // Following messages -- have to belong to the same account 225 if (message.mAccountKey != accountId) { 226 Utility.showToast(mActivity, R.string.cannot_move_multiple_accounts_toast); 227 return -1L; 228 } 229 } 230 // Second, check mailbox. 231 if (!Mailbox.canMoveFrom(c, message.mMailboxKey)) { 232 Utility.showToast(mActivity, R.string.cannot_move_special_messages); 233 return -1L; 234 } 235 } 236 237 // If all messages have been removed, accountId remains -1, which is what we should 238 // return here. 239 return accountId; 240 } 241 242 @Override 243 public void startLoading() { 244 cancelLoad(); 245 forceLoad(); 246 } 247 248 @Override 249 public void stopLoading() { 250 cancelLoad(); 251 } 252 253 @Override 254 public void destroy() { 255 stopLoading(); 256 } 257 } 258} 259