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