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 17 package com.android.gallery3d.app; 18 19 import com.android.gallery3d.R; 20 import com.android.gallery3d.data.MediaObject; 21 import 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 // 56 public class FilterUtils { 57 private static final String TAG = "FilterUtils"; 58 59 public static final int CLUSTER_BY_ALBUM = 1; 60 public static final int CLUSTER_BY_TIME = 2; 61 public static final int CLUSTER_BY_LOCATION = 4; 62 public static final int CLUSTER_BY_TAG = 8; 63 public static final int CLUSTER_BY_SIZE = 16; 64 public static final int CLUSTER_BY_FACE = 32; 65 66 public static final int FILTER_IMAGE_ONLY = 1; 67 public static final int FILTER_VIDEO_ONLY = 2; 68 public static final int FILTER_ALL = 4; 69 70 // These are indices of the return values of getAppliedFilters(). 71 // The _F suffix means "fixed". 72 private static final int CLUSTER_TYPE = 0; 73 private static final int FILTER_TYPE = 1; 74 private static final int CLUSTER_TYPE_F = 2; 75 private static final int FILTER_TYPE_F = 3; 76 private static final int CLUSTER_CURRENT_TYPE = 4; 77 private static final int FILTER_CURRENT_TYPE = 5; 78 setupMenuItems(GalleryActionBar model, Path path, boolean inAlbum)79 public static void setupMenuItems(GalleryActionBar model, Path path, boolean inAlbum) { 80 int[] result = new int[6]; 81 getAppliedFilters(path, result); 82 int ctype = result[CLUSTER_TYPE]; 83 int ftype = result[FILTER_TYPE]; 84 int ftypef = result[FILTER_TYPE_F]; 85 int ccurrent = result[CLUSTER_CURRENT_TYPE]; 86 int fcurrent = result[FILTER_CURRENT_TYPE]; 87 88 setMenuItemApplied(model, CLUSTER_BY_TIME, 89 (ctype & CLUSTER_BY_TIME) != 0, (ccurrent & CLUSTER_BY_TIME) != 0); 90 setMenuItemApplied(model, CLUSTER_BY_LOCATION, 91 (ctype & CLUSTER_BY_LOCATION) != 0, (ccurrent & CLUSTER_BY_LOCATION) != 0); 92 setMenuItemApplied(model, CLUSTER_BY_TAG, 93 (ctype & CLUSTER_BY_TAG) != 0, (ccurrent & CLUSTER_BY_TAG) != 0); 94 setMenuItemApplied(model, CLUSTER_BY_FACE, 95 (ctype & CLUSTER_BY_FACE) != 0, (ccurrent & CLUSTER_BY_FACE) != 0); 96 97 model.setClusterItemVisibility(CLUSTER_BY_ALBUM, !inAlbum || ctype == 0); 98 99 setMenuItemApplied(model, R.id.action_cluster_album, ctype == 0, 100 ccurrent == 0); 101 102 // A filtering is available if it's not applied, and the old filtering 103 // (if any) is not fixed. 104 setMenuItemAppliedEnabled(model, R.string.show_images_only, 105 (ftype & FILTER_IMAGE_ONLY) != 0, 106 (ftype & FILTER_IMAGE_ONLY) == 0 && ftypef == 0, 107 (fcurrent & FILTER_IMAGE_ONLY) != 0); 108 setMenuItemAppliedEnabled(model, R.string.show_videos_only, 109 (ftype & FILTER_VIDEO_ONLY) != 0, 110 (ftype & FILTER_VIDEO_ONLY) == 0 && ftypef == 0, 111 (fcurrent & FILTER_VIDEO_ONLY) != 0); 112 setMenuItemAppliedEnabled(model, R.string.show_all, 113 ftype == 0, ftype != 0 && ftypef == 0, fcurrent == 0); 114 } 115 116 // Gets the filters applied in the path. getAppliedFilters(Path path, int[] result)117 private static void getAppliedFilters(Path path, int[] result) { 118 getAppliedFilters(path, result, false); 119 } 120 getAppliedFilters(Path path, int[] result, boolean underCluster)121 private static void getAppliedFilters(Path path, int[] result, boolean underCluster) { 122 String[] segments = path.split(); 123 // Recurse into sub media sets. 124 for (int i = 0; i < segments.length; i++) { 125 if (segments[i].startsWith("{")) { 126 String[] sets = Path.splitSequence(segments[i]); 127 for (int j = 0; j < sets.length; j++) { 128 Path sub = Path.fromString(sets[j]); 129 getAppliedFilters(sub, result, underCluster); 130 } 131 } 132 } 133 134 // update current selection 135 if (segments[0].equals("cluster")) { 136 // if this is a clustered album, set underCluster to true. 137 if (segments.length == 4) { 138 underCluster = true; 139 } 140 141 int ctype = toClusterType(segments[2]); 142 result[CLUSTER_TYPE] |= ctype; 143 result[CLUSTER_CURRENT_TYPE] = ctype; 144 if (underCluster) { 145 result[CLUSTER_TYPE_F] |= ctype; 146 } 147 } 148 } 149 toClusterType(String s)150 private static int toClusterType(String s) { 151 if (s.equals("time")) { 152 return CLUSTER_BY_TIME; 153 } else if (s.equals("location")) { 154 return CLUSTER_BY_LOCATION; 155 } else if (s.equals("tag")) { 156 return CLUSTER_BY_TAG; 157 } else if (s.equals("size")) { 158 return CLUSTER_BY_SIZE; 159 } else if (s.equals("face")) { 160 return CLUSTER_BY_FACE; 161 } 162 return 0; 163 } 164 setMenuItemApplied( GalleryActionBar model, int id, boolean applied, boolean updateTitle)165 private static void setMenuItemApplied( 166 GalleryActionBar model, int id, boolean applied, boolean updateTitle) { 167 model.setClusterItemEnabled(id, !applied); 168 } 169 setMenuItemAppliedEnabled(GalleryActionBar model, int id, boolean applied, boolean enabled, boolean updateTitle)170 private static void setMenuItemAppliedEnabled(GalleryActionBar model, int id, boolean applied, boolean enabled, boolean updateTitle) { 171 model.setClusterItemEnabled(id, enabled); 172 } 173 174 // Add a specified filter to the path. newFilterPath(String base, int filterType)175 public static String newFilterPath(String base, int filterType) { 176 int mediaType; 177 switch (filterType) { 178 case FILTER_IMAGE_ONLY: 179 mediaType = MediaObject.MEDIA_TYPE_IMAGE; 180 break; 181 case FILTER_VIDEO_ONLY: 182 mediaType = MediaObject.MEDIA_TYPE_VIDEO; 183 break; 184 default: /* FILTER_ALL */ 185 return base; 186 } 187 188 return "/filter/mediatype/" + mediaType + "/{" + base + "}"; 189 } 190 191 // Add a specified clustering to the path. newClusterPath(String base, int clusterType)192 public static String newClusterPath(String base, int clusterType) { 193 String kind; 194 switch (clusterType) { 195 case CLUSTER_BY_TIME: 196 kind = "time"; 197 break; 198 case CLUSTER_BY_LOCATION: 199 kind = "location"; 200 break; 201 case CLUSTER_BY_TAG: 202 kind = "tag"; 203 break; 204 case CLUSTER_BY_SIZE: 205 kind = "size"; 206 break; 207 case CLUSTER_BY_FACE: 208 kind = "face"; 209 break; 210 default: /* CLUSTER_BY_ALBUM */ 211 return base; 212 } 213 214 return "/cluster/{" + base + "}/" + kind; 215 } 216 217 // Change the topmost clustering to the specified type. switchClusterPath(String base, int clusterType)218 public static String switchClusterPath(String base, int clusterType) { 219 return newClusterPath(removeOneClusterFromPath(base), clusterType); 220 } 221 222 // Remove the topmost clustering (if any) from the path. removeOneClusterFromPath(String base)223 private static String removeOneClusterFromPath(String base) { 224 boolean[] done = new boolean[1]; 225 return removeOneClusterFromPath(base, done); 226 } 227 removeOneClusterFromPath(String base, boolean[] done)228 private static String removeOneClusterFromPath(String base, boolean[] done) { 229 if (done[0]) return base; 230 231 String[] segments = Path.split(base); 232 if (segments[0].equals("cluster")) { 233 done[0] = true; 234 return Path.splitSequence(segments[1])[0]; 235 } 236 237 StringBuilder sb = new StringBuilder(); 238 for (int i = 0; i < segments.length; i++) { 239 sb.append("/"); 240 if (segments[i].startsWith("{")) { 241 sb.append("{"); 242 String[] sets = Path.splitSequence(segments[i]); 243 for (int j = 0; j < sets.length; j++) { 244 if (j > 0) { 245 sb.append(","); 246 } 247 sb.append(removeOneClusterFromPath(sets[j], done)); 248 } 249 sb.append("}"); 250 } else { 251 sb.append(segments[i]); 252 } 253 } 254 return sb.toString(); 255 } 256 } 257