1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/* 2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2010 The Android Open Source Project 3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License"); 5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License. 6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at 7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * http://www.apache.org/licenses/LICENSE-2.0 9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * 10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software 11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS, 12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and 14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License. 15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */ 16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.data; 18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.Context; 20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.text.format.DateFormat; 21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.text.format.DateUtils; 22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 232b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.common.Utils; 242b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Linimport com.android.gallery3d.util.GalleryUtils; 252b3ee0ea07246b859a5b75d8a6102a7cce7ec838Owen Lin 26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.ArrayList; 27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.Collections; 28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.Comparator; 29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class TimeClustering extends Clustering { 317817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @SuppressWarnings("unused") 32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TAG = "TimeClustering"; 33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // If 2 items are greater than 25 miles apart, they will be in different 35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // clusters. 36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int GEOGRAPHIC_DISTANCE_CUTOFF_IN_MILES = 20; 37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Do not want to split based on anything under 1 min. 39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final long MIN_CLUSTER_SPLIT_TIME_IN_MS = 60000L; 40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Disregard a cluster split time of anything over 2 hours. 42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final long MAX_CLUSTER_SPLIT_TIME_IN_MS = 7200000L; 43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Try and get around 9 clusters (best-effort for the common case). 45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int NUM_CLUSTERS_TARGETED = 9; 46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Try and merge 2 clusters if they are both smaller than min cluster size. 48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // The min cluster size can range from 8 to 15. 49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MIN_MIN_CLUSTER_SIZE = 8; 50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MAX_MIN_CLUSTER_SIZE = 15; 51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Try and split a cluster if it is bigger than max cluster size. 53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // The max cluster size can range from 20 to 50. 54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MIN_MAX_CLUSTER_SIZE = 20; 55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MAX_MAX_CLUSTER_SIZE = 50; 56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Initially put 2 items in the same cluster as long as they are within 58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // 3 cluster frequencies of each other. 59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static int CLUSTER_SPLIT_MULTIPLIER = 3; 60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // The minimum change factor in the time between items to consider a 62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // partition. 63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Example: (Item 3 - Item 2) / (Item 2 - Item 1). 64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int MIN_PARTITION_CHANGE_FACTOR = 2; 65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Make the cluster split time of a large cluster half that of a regular 67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // cluster. 68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final int PARTITION_CLUSTER_SPLIT_TIME_FACTOR = 2; 69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Context mContext; 71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ArrayList<Cluster> mClusters; 72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private String[] mNames; 73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private Cluster mCurrCluster; 74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private long mClusterSplitTime = 76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin (MIN_CLUSTER_SPLIT_TIME_IN_MS + MAX_CLUSTER_SPLIT_TIME_IN_MS) / 2; 77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private long mLargeClusterSplitTime = 78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusterSplitTime / PARTITION_CLUSTER_SPLIT_TIME_FACTOR; 79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int mMinClusterSize = (MIN_MIN_CLUSTER_SIZE + MAX_MIN_CLUSTER_SIZE) / 2; 80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int mMaxClusterSize = (MIN_MAX_CLUSTER_SIZE + MAX_MAX_CLUSTER_SIZE) / 2; 81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final Comparator<SmallItem> sDateComparator = 84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin new DateComparator(); 85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static class DateComparator implements Comparator<SmallItem> { 877817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public int compare(SmallItem item1, SmallItem item2) { 89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return -Utils.compare(item1.dateInMs, item2.dateInMs); 90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public TimeClustering(Context context) { 94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mContext = context; 95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters = new ArrayList<Cluster>(); 96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster = new Cluster(); 97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void run(MediaSet baseSet) { 101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin final int total = baseSet.getTotalMediaItemCount(); 102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin final SmallItem[] buf = new SmallItem[total]; 103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin final double[] latLng = new double[2]; 104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin baseSet.enumerateTotalMediaItems(new MediaSet.ItemConsumer() { 1067817979db0c52ffeacb951625b1e821eba303285Ahbong Chang @Override 107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void consume(int index, MediaItem item) { 108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (index < 0 || index >= total) return; 109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin SmallItem s = new SmallItem(); 110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin s.path = item.getPath(); 111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin s.dateInMs = item.getDateInMs(); 112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin item.getLatLong(latLng); 113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin s.lat = latLng[0]; 114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin s.lng = latLng[1]; 115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin buf[index] = s; 116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin }); 118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<SmallItem> items = new ArrayList<SmallItem>(total); 120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < total; i++) { 121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (buf[i] != null) { 122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin items.add(buf[i]); 123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Collections.sort(items, sDateComparator); 127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int n = items.size(); 129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long minTime = 0; 130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long maxTime = 0; 131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < n; i++) { 132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long t = items.get(i).dateInMs; 133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (t == 0) continue; 134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (minTime == 0) { 135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin minTime = maxTime = t; 136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin minTime = Math.min(minTime, t); 138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin maxTime = Math.max(maxTime, t); 139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin setTimeRange(maxTime - minTime, n); 143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < n; i++) { 145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin compute(items.get(i)); 146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin compute(null); 149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int m = mClusters.size(); 151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mNames = new String[m]; 152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < m; i++) { 153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mNames[i] = mClusters.get(i).generateCaption(mContext); 154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public int getNumberOfClusters() { 159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mClusters.size(); 160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public ArrayList<Path> getCluster(int index) { 164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<SmallItem> items = mClusters.get(index).getItems(); 165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<Path> result = new ArrayList<Path>(items.size()); 166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0, n = items.size(); i < n; i++) { 167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin result.add(items.get(i).path); 168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return result; 170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @Override 173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String getClusterName(int index) { 174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mNames[index]; 175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private void setTimeRange(long timeRange, int numItems) { 178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (numItems != 0) { 179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int meanItemsPerCluster = numItems / NUM_CLUSTERS_TARGETED; 180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Heuristic to get min and max cluster size - half and double the 181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // desired items per cluster. 182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mMinClusterSize = meanItemsPerCluster / 2; 183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mMaxClusterSize = meanItemsPerCluster * 2; 184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusterSplitTime = timeRange / numItems * CLUSTER_SPLIT_MULTIPLIER; 185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusterSplitTime = Utils.clamp(mClusterSplitTime, MIN_CLUSTER_SPLIT_TIME_IN_MS, MAX_CLUSTER_SPLIT_TIME_IN_MS); 187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mLargeClusterSplitTime = mClusterSplitTime / PARTITION_CLUSTER_SPLIT_TIME_FACTOR; 188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mMinClusterSize = Utils.clamp(mMinClusterSize, MIN_MIN_CLUSTER_SIZE, MAX_MIN_CLUSTER_SIZE); 189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mMaxClusterSize = Utils.clamp(mMaxClusterSize, MIN_MAX_CLUSTER_SIZE, MAX_MAX_CLUSTER_SIZE); 190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private void compute(SmallItem currentItem) { 193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (currentItem != null) { 194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numClusters = mClusters.size(); 195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numCurrClusterItems = mCurrCluster.size(); 196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin boolean geographicallySeparateItem = false; 197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin boolean itemAddedToCurrentCluster = false; 198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Determine if this item should go in the current cluster or be the 200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // start of a new cluster. 201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (numCurrClusterItems == 0) { 202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster.addItem(currentItem); 203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin SmallItem prevItem = mCurrCluster.getLastItem(); 205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (isGeographicallySeparated(prevItem, currentItem)) { 206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(mCurrCluster); 207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin geographicallySeparateItem = true; 208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else if (numCurrClusterItems > mMaxClusterSize) { 209f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin splitAndAddCurrentCluster(); 210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else if (timeDistance(prevItem, currentItem) < mClusterSplitTime) { 211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster.addItem(currentItem); 212f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin itemAddedToCurrentCluster = true; 213f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else if (numClusters > 0 && numCurrClusterItems < mMinClusterSize 214f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin && !mCurrCluster.mGeographicallySeparatedFromPrevCluster) { 215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mergeAndAddCurrentCluster(); 216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(mCurrCluster); 218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Creating a new cluster and adding the current item to it. 221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!itemAddedToCurrentCluster) { 222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster = new Cluster(); 223f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (geographicallySeparateItem) { 224f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster.mGeographicallySeparatedFromPrevCluster = true; 225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster.addItem(currentItem); 227f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 228f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 229f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 230f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (mCurrCluster.size() > 0) { 231f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numClusters = mClusters.size(); 232f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numCurrClusterItems = mCurrCluster.size(); 233f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 234f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // The last cluster may potentially be too big or too small. 235f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (numCurrClusterItems > mMaxClusterSize) { 236f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin splitAndAddCurrentCluster(); 237f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else if (numClusters > 0 && numCurrClusterItems < mMinClusterSize 238f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin && !mCurrCluster.mGeographicallySeparatedFromPrevCluster) { 239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mergeAndAddCurrentCluster(); 240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(mCurrCluster); 242f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 243f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mCurrCluster = new Cluster(); 244f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 245f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 246f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 247f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 248f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private void splitAndAddCurrentCluster() { 249f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<SmallItem> currClusterItems = mCurrCluster.getItems(); 250f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numCurrClusterItems = mCurrCluster.size(); 251f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int secondPartitionStartIndex = getPartitionIndexForCurrentCluster(); 252f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (secondPartitionStartIndex != -1) { 253f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cluster partitionedCluster = new Cluster(); 254f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int j = 0; j < secondPartitionStartIndex; j++) { 255f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin partitionedCluster.addItem(currClusterItems.get(j)); 256f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 257f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(partitionedCluster); 258f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin partitionedCluster = new Cluster(); 259f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int j = secondPartitionStartIndex; j < numCurrClusterItems; j++) { 260f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin partitionedCluster.addItem(currClusterItems.get(j)); 261f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 262f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(partitionedCluster); 263f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 264f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(mCurrCluster); 265f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 266f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 267f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 268f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private int getPartitionIndexForCurrentCluster() { 269f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int partitionIndex = -1; 270f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin float largestChange = MIN_PARTITION_CHANGE_FACTOR; 271f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<SmallItem> currClusterItems = mCurrCluster.getItems(); 272f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numCurrClusterItems = mCurrCluster.size(); 273f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int minClusterSize = mMinClusterSize; 274f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 275f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Could be slightly more efficient here but this code seems cleaner. 276f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (numCurrClusterItems > minClusterSize + 1) { 277f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = minClusterSize; i < numCurrClusterItems - minClusterSize; i++) { 278f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin SmallItem prevItem = currClusterItems.get(i - 1); 279f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin SmallItem currItem = currClusterItems.get(i); 280f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin SmallItem nextItem = currClusterItems.get(i + 1); 281f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 282f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long timeNext = nextItem.dateInMs; 283f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long timeCurr = currItem.dateInMs; 284f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long timePrev = prevItem.dateInMs; 285f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (timeNext == 0 || timeCurr == 0 || timePrev == 0) continue; 287f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 288f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long diff1 = Math.abs(timeNext - timeCurr); 289f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long diff2 = Math.abs(timeCurr - timePrev); 290f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 291f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin float change = Math.max(diff1 / (diff2 + 0.01f), diff2 / (diff1 + 0.01f)); 292f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (change > largestChange) { 293f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (timeDistance(currItem, prevItem) > mLargeClusterSplitTime) { 294f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin partitionIndex = i; 295f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin largestChange = change; 296f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else if (timeDistance(nextItem, currItem) > mLargeClusterSplitTime) { 297f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin partitionIndex = i + 1; 298f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin largestChange = change; 299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 300f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 301f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 302f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 303f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return partitionIndex; 304f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 305f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 306f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private void mergeAndAddCurrentCluster() { 307f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numClusters = mClusters.size(); 308f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Cluster prevCluster = mClusters.get(numClusters - 1); 309f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin ArrayList<SmallItem> currClusterItems = mCurrCluster.getItems(); 310f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int numCurrClusterItems = mCurrCluster.size(); 311f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (prevCluster.size() < mMinClusterSize) { 312f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < numCurrClusterItems; i++) { 313f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin prevCluster.addItem(currClusterItems.get(i)); 314f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 315f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.set(numClusters - 1, prevCluster); 316f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 317f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mClusters.add(mCurrCluster); 318f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 319f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 320f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 321f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Returns true if a, b are sufficiently geographically separated. 322f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static boolean isGeographicallySeparated(SmallItem itemA, SmallItem itemB) { 323f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!GalleryUtils.isValidLocation(itemA.lat, itemA.lng) 324f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin || !GalleryUtils.isValidLocation(itemB.lat, itemB.lng)) { 325f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return false; 326f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 327f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 328f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin double distance = GalleryUtils.fastDistanceMeters( 329f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Math.toRadians(itemA.lat), 330f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Math.toRadians(itemA.lng), 331f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Math.toRadians(itemB.lat), 332f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Math.toRadians(itemB.lng)); 333f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return (GalleryUtils.toMile(distance) > GEOGRAPHIC_DISTANCE_CUTOFF_IN_MILES); 334f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 335f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 336f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Returns the time interval between the two items in milliseconds. 337f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static long timeDistance(SmallItem a, SmallItem b) { 338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return Math.abs(a.dateInMs - b.dateInMs); 339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 341f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 342f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linclass SmallItem { 343f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin Path path; 344f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long dateInMs; 345f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin double lat, lng; 346f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 347f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 348f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linclass Cluster { 349f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin @SuppressWarnings("unused") 350f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String TAG = "Cluster"; 351f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private static final String MMDDYY_FORMAT = "MMddyy"; 352f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 353f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // This is for TimeClustering only. 354f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public boolean mGeographicallySeparatedFromPrevCluster = false; 355f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 356f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin private ArrayList<SmallItem> mItems = new ArrayList<SmallItem>(); 357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 358f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public Cluster() { 359f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 360f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 361f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public void addItem(SmallItem item) { 362f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin mItems.add(item); 363f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 364f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 365f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public int size() { 366f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mItems.size(); 367f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 368f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 369f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public SmallItem getLastItem() { 370f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int n = mItems.size(); 371f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return (n == 0) ? null : mItems.get(n - 1); 372f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 373f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 374f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public ArrayList<SmallItem> getItems() { 375f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return mItems; 376f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 377f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 378f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin public String generateCaption(Context context) { 379f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int n = mItems.size(); 380f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long minTimestamp = 0; 381f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long maxTimestamp = 0; 382f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 383f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin for (int i = 0; i < n; i++) { 384f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long t = mItems.get(i).dateInMs; 385f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (t == 0) continue; 386f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (minTimestamp == 0) { 387f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin minTimestamp = maxTimestamp = t; 388f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 389f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin minTimestamp = Math.min(minTimestamp, t); 390f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin maxTimestamp = Math.max(maxTimestamp, t); 391f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 392f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 393f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (minTimestamp == 0) return ""; 394f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 395f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String caption; 396f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String minDay = DateFormat.format(MMDDYY_FORMAT, minTimestamp) 397f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .toString(); 398f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String maxDay = DateFormat.format(MMDDYY_FORMAT, maxTimestamp) 399f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin .toString(); 400f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 401f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (minDay.substring(4).equals(maxDay.substring(4))) { 402f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // The items are from the same year - show at least as 403f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // much granularity as abbrev_all allows. 404f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin caption = DateUtils.formatDateRange(context, minTimestamp, 405f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin maxTimestamp, DateUtils.FORMAT_ABBREV_ALL); 406f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 407f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Get a more granular date range string if the min and 408f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // max timestamp are on the same day and from the 409f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // current year. 410f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (minDay.equals(maxDay)) { 411f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int flags = DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE; 412f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Contains the year only if the date does not 413f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // correspond to the current year. 414f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String dateRangeWithOptionalYear = DateUtils.formatDateTime( 415f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin context, minTimestamp, flags); 416f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin String dateRangeWithYear = DateUtils.formatDateTime( 417f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin context, minTimestamp, flags | DateUtils.FORMAT_SHOW_YEAR); 418f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin if (!dateRangeWithOptionalYear.equals(dateRangeWithYear)) { 419f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // This means both dates are from the same year 420f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // - show the time. 421f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Not enough room to display the time range. 422f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // Pick the mid-point. 423f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin long midTimestamp = (minTimestamp + maxTimestamp) / 2; 424f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin caption = DateUtils.formatDateRange(context, midTimestamp, 425f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin midTimestamp, DateUtils.FORMAT_SHOW_TIME | flags); 426f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 427f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 428f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } else { 429f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // The items are not from the same year - only show 430f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin // month and year. 431f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin int flags = DateUtils.FORMAT_NO_MONTH_DAY 432f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin | DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_DATE; 433f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin caption = DateUtils.formatDateRange(context, minTimestamp, 434f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin maxTimestamp, flags); 435f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 436f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin 437f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin return caption; 438f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin } 439f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin} 440