Home | History | Annotate | Download | only in app
      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 
     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.
    117     private static void getAppliedFilters(Path path, int[] result) {
    118         getAppliedFilters(path, result, false);
    119     }
    120 
    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 
    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 
    165     private static void setMenuItemApplied(
    166             GalleryActionBar model, int id, boolean applied, boolean updateTitle) {
    167         model.setClusterItemEnabled(id, !applied);
    168     }
    169 
    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.
    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.
    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.
    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.
    223     private static String removeOneClusterFromPath(String base) {
    224         boolean[] done = new boolean[1];
    225         return removeOneClusterFromPath(base, done);
    226     }
    227 
    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