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