MoveMessageToDialog.java revision 81a153463bd9d2e4f71a0985a102548b16a88ec0
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 android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Dialog; 22import android.app.DialogFragment; 23import android.app.Fragment; 24import android.app.LoaderManager; 25import android.content.AsyncTaskLoader; 26import android.content.Context; 27import android.content.DialogInterface; 28import android.content.Loader; 29import android.database.Cursor; 30import android.os.Bundle; 31import android.os.Handler; 32import android.util.Log; 33 34import com.android.email.Email; 35import com.android.email.R; 36import com.android.emailcommon.Logging; 37import com.android.emailcommon.provider.Account; 38import com.android.emailcommon.provider.EmailContent.Message; 39import com.android.emailcommon.provider.Mailbox; 40import com.android.emailcommon.utility.Utility; 41 42/** 43 * "Move (messages) to" dialog. 44 * 45 * TODO The check logic in MessageCheckerCallback is not efficient. It shouldn't restore full 46 * Message objects. But we don't bother at this point as the UI is still temporary. 47 */ 48public class MoveMessageToDialog extends DialogFragment implements DialogInterface.OnClickListener { 49 private static final String BUNDLE_MESSAGE_IDS = "message_ids"; 50 51 private static final int LOADER_ID_MOVE_TO_DIALOG_MAILBOX_LOADER = 1; 52 private static final int LOADER_ID_MOVE_TO_DIALOG_MESSAGE_CHECKER = 2; 53 54 /** Message IDs passed to {@link #newInstance} */ 55 private long[] mMessageIds; 56 private MailboxMoveToAdapter mAdapter; 57 58 /** ID of the account that contains all of the messages to move */ 59 private long mAccountId; 60 /** ID of the mailbox that contains all of the messages to move */ 61 private long mMailboxId; 62 63 private boolean mDestroyed; 64 65 /** 66 * Callback that target fragments, or the owner activity should implement. 67 */ 68 public interface Callback { 69 public void onMoveToMailboxSelected(long newMailboxId, long[] messageIds); 70 } 71 72 /** 73 * Create and return a new instance. 74 * 75 * @param messageIds IDs of the messages to be moved. 76 * @param callbackFragment Fragment that gets a callback. The fragment must implement 77 * {@link Callback}. 78 */ 79 public static <T extends Fragment & Callback> MoveMessageToDialog newInstance(long[] messageIds, 80 T callbackFragment) { 81 if (messageIds.length == 0) { 82 throw new IllegalArgumentException(); 83 } 84 if (callbackFragment == null) { 85 throw new IllegalArgumentException(); // fail fast 86 } 87 MoveMessageToDialog dialog = new MoveMessageToDialog(); 88 Bundle args = new Bundle(); 89 args.putLongArray(BUNDLE_MESSAGE_IDS, messageIds); 90 dialog.setArguments(args); 91 dialog.setTargetFragment(callbackFragment, 0); 92 return dialog; 93 } 94 95 @Override 96 public void onCreate(Bundle savedInstanceState) { 97 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 98 Log.d(Logging.LOG_TAG, "" + this + " onCreate target=" + getTargetFragment()); 99 } 100 super.onCreate(savedInstanceState); 101 mMessageIds = getArguments().getLongArray(BUNDLE_MESSAGE_IDS); 102 setStyle(STYLE_NORMAL, android.R.style.Theme_Holo_Light); 103 } 104 105 @Override 106 public void onDestroy() { 107 mDestroyed = true; 108 super.onDestroy(); 109 } 110 111 @Override 112 public Dialog onCreateDialog(Bundle savedInstanceState) { 113 final Activity activity = getActivity(); 114 115 // Build adapter & dialog 116 // Make sure to pass Builder's context to the adapter, so that it'll get the correct theme. 117 AlertDialog.Builder builder = new AlertDialog.Builder(activity) 118 .setTitle(activity.getResources().getString(R.string.move_to_folder_dialog_title)); 119 120 mAdapter = new MailboxMoveToAdapter(builder.getContext()); 121 builder.setSingleChoiceItems(mAdapter, -1, this); 122 123 getLoaderManager().initLoader( 124 LOADER_ID_MOVE_TO_DIALOG_MESSAGE_CHECKER, 125 null, new MessageCheckerCallback()); 126 127 return builder.show(); 128 } 129 130 @Override 131 public void onClick(DialogInterface dialog, int position) { 132 final long mailboxId = mAdapter.getItemId(position); 133 134 ((Callback) getTargetFragment()).onMoveToMailboxSelected(mailboxId, mMessageIds); 135 dismiss(); 136 } 137 138 /** 139 * Delay-call {@link #dismissAllowingStateLoss()} using a {@link Handler}. Calling 140 * {@link #dismissAllowingStateLoss()} from {@link LoaderManager.LoaderCallbacks#onLoadFinished} 141 * is not allowed, so we use it instead. 142 */ 143 private void dismissAsync() { 144 new Handler().post(new Runnable() { 145 @Override 146 public void run() { 147 if (!mDestroyed) { 148 dismissAllowingStateLoss(); 149 } 150 } 151 }); 152 } 153 154 /** 155 * Loader callback for {@link MessageChecker} 156 */ 157 private class MessageCheckerCallback implements LoaderManager.LoaderCallbacks<IdContainer> { 158 @Override 159 public Loader<IdContainer> onCreateLoader(int id, Bundle args) { 160 return new MessageChecker(getActivity(), mMessageIds); 161 } 162 163 @Override 164 public void onLoadFinished(Loader<IdContainer> loader, IdContainer idSet) { 165 if (mDestroyed) { 166 return; 167 } 168 // accountId shouldn't be null, but I'm paranoia. 169 if (idSet == null || idSet.mAccountId == Account.NO_ACCOUNT 170 || idSet.mMailboxId == Mailbox.NO_MAILBOX) { 171 // Some of the messages can't be moved. Close the dialog. 172 dismissAsync(); 173 return; 174 } 175 mAccountId = idSet.mAccountId; 176 mMailboxId = idSet.mMailboxId; 177 getLoaderManager().initLoader( 178 LOADER_ID_MOVE_TO_DIALOG_MAILBOX_LOADER, 179 null, new MailboxesLoaderCallbacks()); 180 } 181 182 @Override 183 public void onLoaderReset(Loader<IdContainer> loader) { 184 } 185 } 186 187 /** 188 * Loader callback for destination mailbox list. 189 */ 190 private class MailboxesLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> { 191 @Override 192 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 193 return MailboxMoveToAdapter.createLoader(getActivity().getApplicationContext(), 194 mAccountId, mMailboxId); 195 } 196 197 @Override 198 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 199 if (mDestroyed) { 200 return; 201 } 202 mAdapter.swapCursor(data); 203 } 204 205 @Override 206 public void onLoaderReset(Loader<Cursor> loader) { 207 mAdapter.swapCursor(null); 208 } 209 } 210 211 /** 212 * A loader that checks if the messages can be moved. If messages can be moved, it returns 213 * the account and mailbox IDs where the messages are currently located. If any the messages 214 * cannot be moved (such as the messages belong to different accounts), the IDs returned 215 * will be {@link Account#NO_ACCOUNT} and {@link Mailbox#NO_MAILBOX}. 216 */ 217 private static class MessageChecker extends AsyncTaskLoader<IdContainer> { 218 private final Activity mActivity; 219 private final long[] mMessageIds; 220 221 public MessageChecker(Activity activity, long[] messageIds) { 222 super(activity); 223 mActivity = activity; 224 mMessageIds = messageIds; 225 } 226 227 @Override 228 public IdContainer loadInBackground() { 229 final Context c = getContext(); 230 231 long accountId = Account.NO_ACCOUNT; 232 long mailboxId = Mailbox.NO_MAILBOX; 233 234 for (long messageId : mMessageIds) { 235 // TODO This shouln't restore a full Message object. 236 final Message message = Message.restoreMessageWithId(c, messageId); 237 if (message == null) { 238 continue; // Skip removed messages. 239 } 240 241 // First, check account. 242 if (accountId == Account.NO_ACCOUNT) { 243 // First, check if the account supports move 244 accountId = message.mAccountKey; 245 if (!Account.restoreAccountWithId(c, accountId).supportsMoveMessages(c)) { 246 Utility.showToast( 247 mActivity, R.string.cannot_move_protocol_not_supported_toast); 248 accountId = Account.NO_ACCOUNT; 249 break; 250 } 251 mailboxId = message.mMailboxKey; 252 // Second, check if the mailbox supports move 253 if (!Mailbox.restoreMailboxWithId(c, mailboxId).canHaveMessagesMoved()) { 254 Utility.showToast(mActivity, R.string.cannot_move_special_mailboxes_toast); 255 accountId = Account.NO_ACCOUNT; 256 mailboxId = Mailbox.NO_MAILBOX; 257 break; 258 } 259 } else { 260 // Subsequent messages; all messages must to belong to the same mailbox 261 if (message.mAccountKey != accountId || message.mMailboxKey != mailboxId) { 262 Utility.showToast(mActivity, R.string.cannot_move_multiple_accounts_toast); 263 accountId = Account.NO_ACCOUNT; 264 mailboxId = Mailbox.NO_MAILBOX; 265 break; 266 } 267 } 268 } 269 return new IdContainer(accountId, mailboxId); 270 } 271 272 @Override 273 protected void onStartLoading() { 274 cancelLoad(); 275 forceLoad(); 276 } 277 278 @Override 279 protected void onStopLoading() { 280 cancelLoad(); 281 } 282 283 @Override 284 protected void onReset() { 285 stopLoading(); 286 } 287 } 288 289 /** Container for multiple types of IDs */ 290 private static class IdContainer { 291 private final long mAccountId; 292 private final long mMailboxId; 293 294 private IdContainer(long accountId, long mailboxId) { 295 mAccountId = accountId; 296 mMailboxId = mailboxId; 297 } 298 } 299} 300