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.gallery3d.ui;
18
19import com.android.gallery3d.app.AbstractGalleryActivity;
20import com.android.gallery3d.data.DataManager;
21import com.android.gallery3d.data.MediaItem;
22import com.android.gallery3d.data.MediaSet;
23import com.android.gallery3d.data.Path;
24
25import java.util.ArrayList;
26import java.util.HashSet;
27import java.util.Set;
28
29public class SelectionManager {
30    @SuppressWarnings("unused")
31    private static final String TAG = "SelectionManager";
32
33    public static final int ENTER_SELECTION_MODE = 1;
34    public static final int LEAVE_SELECTION_MODE = 2;
35    public static final int SELECT_ALL_MODE = 3;
36
37    private Set<Path> mClickedSet;
38    private MediaSet mSourceMediaSet;
39    private SelectionListener mListener;
40    private DataManager mDataManager;
41    private boolean mInverseSelection;
42    private boolean mIsAlbumSet;
43    private boolean mInSelectionMode;
44    private boolean mAutoLeave = true;
45    private int mTotal;
46
47    public interface SelectionListener {
48        public void onSelectionModeChange(int mode);
49        public void onSelectionChange(Path path, boolean selected);
50    }
51
52    public SelectionManager(AbstractGalleryActivity activity, boolean isAlbumSet) {
53        mDataManager = activity.getDataManager();
54        mClickedSet = new HashSet<Path>();
55        mIsAlbumSet = isAlbumSet;
56        mTotal = -1;
57    }
58
59    // Whether we will leave selection mode automatically once the number of
60    // selected items is down to zero.
61    public void setAutoLeaveSelectionMode(boolean enable) {
62        mAutoLeave = enable;
63    }
64
65    public void setSelectionListener(SelectionListener listener) {
66        mListener = listener;
67    }
68
69    public void selectAll() {
70        mInverseSelection = true;
71        mClickedSet.clear();
72        mTotal = -1;
73        enterSelectionMode();
74        if (mListener != null) mListener.onSelectionModeChange(SELECT_ALL_MODE);
75    }
76
77    public void deSelectAll() {
78        leaveSelectionMode();
79        mInverseSelection = false;
80        mClickedSet.clear();
81    }
82
83    public boolean inSelectAllMode() {
84        return mInverseSelection;
85    }
86
87    public boolean inSelectionMode() {
88        return mInSelectionMode;
89    }
90
91    public void enterSelectionMode() {
92        if (mInSelectionMode) return;
93
94        mInSelectionMode = true;
95        if (mListener != null) mListener.onSelectionModeChange(ENTER_SELECTION_MODE);
96    }
97
98    public void leaveSelectionMode() {
99        if (!mInSelectionMode) return;
100
101        mInSelectionMode = false;
102        mInverseSelection = false;
103        mClickedSet.clear();
104        if (mListener != null) mListener.onSelectionModeChange(LEAVE_SELECTION_MODE);
105    }
106
107    public boolean isItemSelected(Path itemId) {
108        return mInverseSelection ^ mClickedSet.contains(itemId);
109    }
110
111    private int getTotalCount() {
112        if (mSourceMediaSet == null) return -1;
113
114        if (mTotal < 0) {
115            mTotal = mIsAlbumSet
116                    ? mSourceMediaSet.getSubMediaSetCount()
117                    : mSourceMediaSet.getMediaItemCount();
118        }
119        return mTotal;
120    }
121
122    public int getSelectedCount() {
123        int count = mClickedSet.size();
124        if (mInverseSelection) {
125            count = getTotalCount() - count;
126        }
127        return count;
128    }
129
130    public void toggle(Path path) {
131        if (mClickedSet.contains(path)) {
132            mClickedSet.remove(path);
133        } else {
134            enterSelectionMode();
135            mClickedSet.add(path);
136        }
137
138        // Convert to inverse selection mode if everything is selected.
139        int count = getSelectedCount();
140        if (count == getTotalCount()) {
141            selectAll();
142        }
143
144        if (mListener != null) mListener.onSelectionChange(path, isItemSelected(path));
145        if (count == 0 && mAutoLeave) {
146            leaveSelectionMode();
147        }
148    }
149
150    private static boolean expandMediaSet(ArrayList<Path> items, MediaSet set, int maxSelection) {
151        int subCount = set.getSubMediaSetCount();
152        for (int i = 0; i < subCount; i++) {
153            if (!expandMediaSet(items, set.getSubMediaSet(i), maxSelection)) {
154                return false;
155            }
156        }
157        int total = set.getMediaItemCount();
158        int batch = 50;
159        int index = 0;
160
161        while (index < total) {
162            int count = index + batch < total
163                    ? batch
164                    : total - index;
165            ArrayList<MediaItem> list = set.getMediaItem(index, count);
166            if (list != null
167                    && list.size() > (maxSelection - items.size())) {
168                return false;
169            }
170            for (MediaItem item : list) {
171                items.add(item.getPath());
172            }
173            index += batch;
174        }
175        return true;
176    }
177
178    public ArrayList<Path> getSelected(boolean expandSet) {
179        return getSelected(expandSet, Integer.MAX_VALUE);
180    }
181
182    public ArrayList<Path> getSelected(boolean expandSet, int maxSelection) {
183        ArrayList<Path> selected = new ArrayList<Path>();
184        if (mIsAlbumSet) {
185            if (mInverseSelection) {
186                int total = getTotalCount();
187                for (int i = 0; i < total; i++) {
188                    MediaSet set = mSourceMediaSet.getSubMediaSet(i);
189                    Path id = set.getPath();
190                    if (!mClickedSet.contains(id)) {
191                        if (expandSet) {
192                            if (!expandMediaSet(selected, set, maxSelection)) {
193                                return null;
194                            }
195                        } else {
196                            selected.add(id);
197                            if (selected.size() > maxSelection) {
198                                return null;
199                            }
200                        }
201                    }
202                }
203            } else {
204                for (Path id : mClickedSet) {
205                    if (expandSet) {
206                        if (!expandMediaSet(selected, mDataManager.getMediaSet(id),
207                                maxSelection)) {
208                            return null;
209                        }
210                    } else {
211                        selected.add(id);
212                        if (selected.size() > maxSelection) {
213                            return null;
214                        }
215                    }
216                }
217            }
218        } else {
219            if (mInverseSelection) {
220                int total = getTotalCount();
221                int index = 0;
222                while (index < total) {
223                    int count = Math.min(total - index, MediaSet.MEDIAITEM_BATCH_FETCH_COUNT);
224                    ArrayList<MediaItem> list = mSourceMediaSet.getMediaItem(index, count);
225                    for (MediaItem item : list) {
226                        Path id = item.getPath();
227                        if (!mClickedSet.contains(id)) {
228                            selected.add(id);
229                            if (selected.size() > maxSelection) {
230                                return null;
231                            }
232                        }
233                    }
234                    index += count;
235                }
236            } else {
237                for (Path id : mClickedSet) {
238                    selected.add(id);
239                    if (selected.size() > maxSelection) {
240                        return null;
241                    }
242                }
243            }
244        }
245        return selected;
246    }
247
248    public void setSourceMediaSet(MediaSet set) {
249        mSourceMediaSet = set;
250        mTotal = -1;
251    }
252}
253