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