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