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.app;
18
19import com.android.gallery3d.R;
20import com.android.gallery3d.data.MediaObject;
21import com.android.gallery3d.data.Path;
22
23// This class handles filtering and clustering.
24//
25// We allow at most only one filter operation at a time (Currently it
26// doesn't make sense to use more than one). Also each clustering operation
27// can be applied at most once. In addition, there is one more constraint
28// ("fixed set constraint") described below.
29//
30// A clustered album (not including album set) and its base sets are fixed.
31// For example,
32//
33// /cluster/{base_set}/time/7
34//
35// This set and all sets inside base_set (recursively) are fixed because
36// 1. We can not change this set to use another clustering condition (like
37//    changing "time" to "location").
38// 2. Neither can we change any set in the base_set.
39// The reason is in both cases the 7th set may not exist in the new clustering.
40// ---------------------
41// newPath operation: create a new path based on a source path and put an extra
42// condition on top of it:
43//
44// T = newFilterPath(S, filterType);
45// T = newClusterPath(S, clusterType);
46//
47// Similar functions can be used to replace the current condition (if there is one).
48//
49// T = switchFilterPath(S, filterType);
50// T = switchClusterPath(S, clusterType);
51//
52// For all fixed set in the path defined above, if some clusterType and
53// filterType are already used, they cannot not be used as parameter for these
54// functions. setupMenuItems() makes sure those types cannot be selected.
55//
56public class FilterUtils {
57    @SuppressWarnings("unused")
58    private static final String TAG = "FilterUtils";
59
60    public static final int CLUSTER_BY_ALBUM = 1;
61    public static final int CLUSTER_BY_TIME = 2;
62    public static final int CLUSTER_BY_LOCATION = 4;
63    public static final int CLUSTER_BY_TAG = 8;
64    public static final int CLUSTER_BY_SIZE = 16;
65    public static final int CLUSTER_BY_FACE = 32;
66
67    public static final int FILTER_IMAGE_ONLY = 1;
68    public static final int FILTER_VIDEO_ONLY = 2;
69    public static final int FILTER_ALL = 4;
70
71    // These are indices of the return values of getAppliedFilters().
72    // The _F suffix means "fixed".
73    private static final int CLUSTER_TYPE = 0;
74    private static final int FILTER_TYPE = 1;
75    private static final int CLUSTER_TYPE_F = 2;
76    private static final int FILTER_TYPE_F = 3;
77    private static final int CLUSTER_CURRENT_TYPE = 4;
78    private static final int FILTER_CURRENT_TYPE = 5;
79
80    public static void setupMenuItems(GalleryActionBar actionBar, Path path, boolean inAlbum) {
81        int[] result = new int[6];
82        getAppliedFilters(path, result);
83        int ctype = result[CLUSTER_TYPE];
84        int ftype = result[FILTER_TYPE];
85        int ftypef = result[FILTER_TYPE_F];
86        int ccurrent = result[CLUSTER_CURRENT_TYPE];
87        int fcurrent = result[FILTER_CURRENT_TYPE];
88
89        setMenuItemApplied(actionBar, CLUSTER_BY_TIME,
90                (ctype & CLUSTER_BY_TIME) != 0, (ccurrent & CLUSTER_BY_TIME) != 0);
91        setMenuItemApplied(actionBar, CLUSTER_BY_LOCATION,
92                (ctype & CLUSTER_BY_LOCATION) != 0, (ccurrent & CLUSTER_BY_LOCATION) != 0);
93        setMenuItemApplied(actionBar, CLUSTER_BY_TAG,
94                (ctype & CLUSTER_BY_TAG) != 0, (ccurrent & CLUSTER_BY_TAG) != 0);
95        setMenuItemApplied(actionBar, CLUSTER_BY_FACE,
96                (ctype & CLUSTER_BY_FACE) != 0, (ccurrent & CLUSTER_BY_FACE) != 0);
97
98        actionBar.setClusterItemVisibility(CLUSTER_BY_ALBUM, !inAlbum || ctype == 0);
99
100        setMenuItemApplied(actionBar, R.id.action_cluster_album, ctype == 0,
101                ccurrent == 0);
102
103        // A filtering is available if it's not applied, and the old filtering
104        // (if any) is not fixed.
105        setMenuItemAppliedEnabled(actionBar, R.string.show_images_only,
106                (ftype & FILTER_IMAGE_ONLY) != 0,
107                (ftype & FILTER_IMAGE_ONLY) == 0 && ftypef == 0,
108                (fcurrent & FILTER_IMAGE_ONLY) != 0);
109        setMenuItemAppliedEnabled(actionBar, R.string.show_videos_only,
110                (ftype & FILTER_VIDEO_ONLY) != 0,
111                (ftype & FILTER_VIDEO_ONLY) == 0 && ftypef == 0,
112                (fcurrent & FILTER_VIDEO_ONLY) != 0);
113        setMenuItemAppliedEnabled(actionBar, R.string.show_all,
114                ftype == 0, ftype != 0 && ftypef == 0, fcurrent == 0);
115    }
116
117    // Gets the filters applied in the path.
118    private static void getAppliedFilters(Path path, int[] result) {
119        getAppliedFilters(path, result, false);
120    }
121
122    private static void getAppliedFilters(Path path, int[] result, boolean underCluster) {
123        String[] segments = path.split();
124        // Recurse into sub media sets.
125        for (int i = 0; i < segments.length; i++) {
126            if (segments[i].startsWith("{")) {
127                String[] sets = Path.splitSequence(segments[i]);
128                for (int j = 0; j < sets.length; j++) {
129                    Path sub = Path.fromString(sets[j]);
130                    getAppliedFilters(sub, result, underCluster);
131                }
132            }
133        }
134
135        // update current selection
136        if (segments[0].equals("cluster")) {
137            // if this is a clustered album, set underCluster to true.
138            if (segments.length == 4) {
139                underCluster = true;
140            }
141
142            int ctype = toClusterType(segments[2]);
143            result[CLUSTER_TYPE] |= ctype;
144            result[CLUSTER_CURRENT_TYPE] = ctype;
145            if (underCluster) {
146                result[CLUSTER_TYPE_F] |= ctype;
147            }
148        }
149    }
150
151    private static int toClusterType(String s) {
152        if (s.equals("time")) {
153            return CLUSTER_BY_TIME;
154        } else if (s.equals("location")) {
155            return CLUSTER_BY_LOCATION;
156        } else if (s.equals("tag")) {
157            return CLUSTER_BY_TAG;
158        } else if (s.equals("size")) {
159            return CLUSTER_BY_SIZE;
160        } else if (s.equals("face")) {
161            return CLUSTER_BY_FACE;
162        }
163        return 0;
164    }
165
166    private static void setMenuItemApplied(
167            GalleryActionBar model, int id, boolean applied, boolean updateTitle) {
168        model.setClusterItemEnabled(id, !applied);
169    }
170
171    private static void setMenuItemAppliedEnabled(GalleryActionBar model, int id, boolean applied, boolean enabled, boolean updateTitle) {
172        model.setClusterItemEnabled(id, enabled);
173    }
174
175    // Add a specified filter to the path.
176    public static String newFilterPath(String base, int filterType) {
177        int mediaType;
178        switch (filterType) {
179            case FILTER_IMAGE_ONLY:
180                mediaType = MediaObject.MEDIA_TYPE_IMAGE;
181                break;
182            case FILTER_VIDEO_ONLY:
183                mediaType = MediaObject.MEDIA_TYPE_VIDEO;
184                break;
185            default:  /* FILTER_ALL */
186                return base;
187        }
188
189        return "/filter/mediatype/" + mediaType + "/{" + base + "}";
190    }
191
192    // Add a specified clustering to the path.
193    public static String newClusterPath(String base, int clusterType) {
194        String kind;
195        switch (clusterType) {
196            case CLUSTER_BY_TIME:
197                kind = "time";
198                break;
199            case CLUSTER_BY_LOCATION:
200                kind = "location";
201                break;
202            case CLUSTER_BY_TAG:
203                kind = "tag";
204                break;
205            case CLUSTER_BY_SIZE:
206                kind = "size";
207                break;
208            case CLUSTER_BY_FACE:
209                kind = "face";
210                break;
211            default: /* CLUSTER_BY_ALBUM */
212                return base;
213        }
214
215        return "/cluster/{" + base + "}/" + kind;
216    }
217
218    // Change the topmost clustering to the specified type.
219    public static String switchClusterPath(String base, int clusterType) {
220        return newClusterPath(removeOneClusterFromPath(base), clusterType);
221    }
222
223    // Remove the topmost clustering (if any) from the path.
224    private static String removeOneClusterFromPath(String base) {
225        boolean[] done = new boolean[1];
226        return removeOneClusterFromPath(base, done);
227    }
228
229    private static String removeOneClusterFromPath(String base, boolean[] done) {
230        if (done[0]) return base;
231
232        String[] segments = Path.split(base);
233        if (segments[0].equals("cluster")) {
234            done[0] = true;
235            return Path.splitSequence(segments[1])[0];
236        }
237
238        StringBuilder sb = new StringBuilder();
239        for (int i = 0; i < segments.length; i++) {
240            sb.append("/");
241            if (segments[i].startsWith("{")) {
242                sb.append("{");
243                String[] sets = Path.splitSequence(segments[i]);
244                for (int j = 0; j < sets.length; j++) {
245                    if (j > 0) {
246                        sb.append(",");
247                    }
248                    sb.append(removeOneClusterFromPath(sets[j], done));
249                }
250                sb.append("}");
251            } else {
252                sb.append(segments[i]);
253            }
254        }
255        return sb.toString();
256    }
257}
258