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 @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 setupMenuItems(GalleryActionBar actionBar, Path path, boolean inAlbum)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. getAppliedFilters(Path path, int[] result)118 private static void getAppliedFilters(Path path, int[] result) { 119 getAppliedFilters(path, result, false); 120 } 121 getAppliedFilters(Path path, int[] result, boolean underCluster)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 toClusterType(String s)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 setMenuItemApplied( GalleryActionBar model, int id, boolean applied, boolean updateTitle)166 private static void setMenuItemApplied( 167 GalleryActionBar model, int id, boolean applied, boolean updateTitle) { 168 model.setClusterItemEnabled(id, !applied); 169 } 170 setMenuItemAppliedEnabled(GalleryActionBar model, int id, boolean applied, boolean enabled, boolean updateTitle)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. newFilterPath(String base, int filterType)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. newClusterPath(String base, int clusterType)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. switchClusterPath(String base, int clusterType)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. removeOneClusterFromPath(String base)224 private static String removeOneClusterFromPath(String base) { 225 boolean[] done = new boolean[1]; 226 return removeOneClusterFromPath(base, done); 227 } 228 removeOneClusterFromPath(String base, boolean[] done)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