1/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mail.ui;
19
20import android.app.AlertDialog;
21import android.app.LoaderManager;
22import android.content.Context;
23import android.content.CursorLoader;
24import android.content.DialogInterface;
25import android.content.Loader;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Bundle;
29
30import com.android.mail.R;
31import com.android.mail.providers.Conversation;
32import com.android.mail.providers.Folder;
33import com.android.mail.providers.UIProvider;
34import com.android.mail.providers.UIProvider.FolderType;
35import com.android.mail.ui.FolderSelectorAdapter.FolderRow;
36import com.android.mail.utils.Utils;
37import com.google.common.collect.ImmutableSet;
38
39import java.util.Arrays;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Map;
44import java.util.Set;
45
46/**
47 * Displays a folder selection dialog for the conversation provided. It allows
48 * the user to mark folders to assign that conversation to.
49 */
50public class MultiFoldersSelectionDialog extends FolderSelectionDialog {
51    private boolean mSingle;
52    private final HashMap<Uri, FolderOperation> mOperations;
53
54    public MultiFoldersSelectionDialog() {
55        mOperations = new HashMap<Uri, FolderOperation>();
56    }
57
58    private static final int FOLDER_LOADER_ID = 0;
59    private static final String FOLDER_QUERY_URI_TAG = "folderQueryUri";
60
61    private static final String SAVESTATE_OPERATIONS_TAG = "operations";
62
63    @Override
64    public void onCreate(Bundle savedInstanceState) {
65        super.onCreate(savedInstanceState);
66
67        mSingle = !mAccount
68                .supportsCapability(UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV);
69        mTitleId = R.string.change_folders_selection_dialog_title;
70
71        if (savedInstanceState != null) {
72            final FolderOperation[] savedOps = (FolderOperation[])
73                    savedInstanceState.getParcelableArray(SAVESTATE_OPERATIONS_TAG);
74            for (final FolderOperation op : savedOps) {
75                mOperations.put(op.mFolder.folderUri.fullUri, op);
76            }
77        }
78
79        final Bundle args = new Bundle(1);
80        args.putParcelable(FOLDER_QUERY_URI_TAG, !Utils.isEmpty(mAccount.fullFolderListUri) ?
81                mAccount.fullFolderListUri : mAccount.folderListUri);
82        final Context loaderContext = getActivity().getApplicationContext();
83        getLoaderManager().initLoader(FOLDER_LOADER_ID, args,
84                new LoaderManager.LoaderCallbacks<Cursor>() {
85                    @Override
86                    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
87                        final Uri queryUri = args.getParcelable(FOLDER_QUERY_URI_TAG);
88                        return new CursorLoader(loaderContext, queryUri,
89                                UIProvider.FOLDERS_PROJECTION, null, null, null);
90                    }
91
92                    @Override
93                    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
94                        final Context context = getActivity();
95                        if (data == null || context == null) {
96                            return;
97                        }
98                        final AlertDialog dialog = (AlertDialog) getDialog();
99                        if (dialog == null) {
100                            // This could happen if the dialog is dismissed just before the
101                            // load finishes.
102                            return;
103                        }
104                        // The number of view types changes here, so we have to reset the listview's
105                        // adapter.
106                        dialog.getListView().setAdapter(null);
107
108                        final HashSet<String> checked = new HashSet<String>();
109                        for (final Conversation conversation : mTarget) {
110                            final List<Folder> rawFolders = conversation.getRawFolders();
111                            if (rawFolders != null && rawFolders.size() > 0) {
112                                // Parse the raw folders and get all the uris.
113                                checked.addAll(Arrays.asList(Folder.getUriArray(rawFolders)));
114                            } else {
115                                // There are no folders for this conversation, so it must
116                                // belong to the folder we are currently looking at.
117                                checked.add(mCurrentFolder.folderUri.fullUri.toString());
118                            }
119                        }
120                        final Set<String> originalChecked = ImmutableSet.copyOf(checked);
121                        for (final Map.Entry<Uri, FolderOperation> entry : mOperations.entrySet()) {
122                            if (entry.getValue().mAdd) {
123                                checked.add(entry.getKey().toString());
124                            } else {
125                                checked.remove(entry.getKey().toString());
126                            }
127                        }
128                        mAdapter.clearSections();
129                        // TODO(mindyp) : bring this back in UR8 when Email providers
130                        // will have divided folder sections.
131                        /* final String[] headers = mContext.getResources()
132                                .getStringArray(R.array.moveto_folder_sections);
133                         // Currently, the number of adapters are assumed to match the
134                         // number of headers in the string array.
135                         mAdapter.addSection(new SystemFolderSelectorAdapter(mContext,
136                         foldersCursor, checked, R.layout.multi_folders_view, null));
137
138                        // TODO(mindyp): we currently do not support frequently moved to
139                        // folders, at headers[1]; need to define what that means.*/
140
141                        Cursor c = AddableFolderSelectorAdapter.filterFolders(data,
142                                ImmutableSet.of(FolderType.INBOX_SECTION), originalChecked,
143                                true /* includeOnlyInitiallySelected */);
144                        if (c.getCount() > 0) {
145                            mAdapter.addSection(new AddableFolderSelectorAdapter(context, c,
146                                    checked, R.layout.multi_folders_view));
147                        }
148
149                        c = AddableFolderSelectorAdapter.filterFolders(data,
150                                ImmutableSet.of(FolderType.INBOX_SECTION), originalChecked,
151                                false /* includeOnlyInitiallySelected */);
152                        if (c.getCount() > 0) {
153                            mAdapter.addSection(new AddableFolderSelectorAdapter(context, c,
154                                    checked, R.layout.multi_folders_view));
155                        }
156
157                        dialog.getListView().setAdapter(mAdapter);
158                    }
159
160                    @Override
161                    public void onLoaderReset(Loader<Cursor> loader) {
162                        mAdapter.clearSections();
163                    }
164                });
165    }
166
167    @Override
168    public void onSaveInstanceState(Bundle outState) {
169        super.onSaveInstanceState(outState);
170        outState.putParcelableArray(SAVESTATE_OPERATIONS_TAG,
171                mOperations.values().toArray(new FolderOperation[mOperations.size()]));
172    }
173
174    @Override
175    protected void onListItemClick(int position) {
176        final Object item = mAdapter.getItem(position);
177        if (item instanceof FolderRow) {
178            update((FolderRow) item);
179        }
180    }
181
182    /**
183     * Call this to update the state of folders as a result of them being
184     * selected / de-selected.
185     *
186     * @param row The item being updated.
187     */
188    private void update(FolderSelectorAdapter.FolderRow row) {
189        final boolean add = !row.isSelected();
190        if (mSingle) {
191            if (!add) {
192                // This would remove the check on a single radio button, so just
193                // return.
194                return;
195            }
196            // Clear any other checked items.
197            for (int i = 0, size = mAdapter.getCount(); i < size; i++) {
198                final Object item = mAdapter.getItem(i);
199                if (item instanceof FolderRow) {
200                   ((FolderRow)item).setIsSelected(false);
201                   final Folder folder = ((FolderRow)item).getFolder();
202                   mOperations.put(folder.folderUri.fullUri,
203                           new FolderOperation(folder, false));
204                }
205            }
206        }
207        row.setIsSelected(add);
208        mAdapter.notifyDataSetChanged();
209        final Folder folder = row.getFolder();
210        mOperations.put(folder.folderUri.fullUri, new FolderOperation(folder, add));
211    }
212
213    @Override
214    public void onClick(DialogInterface dialog, int which) {
215        switch (which) {
216            case DialogInterface.BUTTON_POSITIVE:
217                getConversationUpdater().assignFolder(mOperations.values(), mTarget, mBatch,
218                        true /* showUndo */, false /* isMoveTo */);
219                break;
220            case DialogInterface.BUTTON_NEGATIVE:
221                break;
222            default:
223                break;
224        }
225    }
226}
227