1 /* 2 * Copyright (C) 2009 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.cooliris.media; 18 19 import java.util.ArrayList; 20 21 public class MediaSet { 22 public static final int TYPE_SMART = 0; 23 public static final int TYPE_FOLDER = 1; 24 public static final int TYPE_USERDEFINED = 2; 25 26 public long mId; 27 public String mName; 28 29 public boolean mFlagForDelete; 30 31 public boolean mHasImages; 32 public boolean mHasVideos; 33 34 // The type of the media set. A smart media set is an automatically 35 // generated media set. For example, the most recently 36 // viewed items media set is a media set that gets populated by the contents 37 // of a folder. A user defined media set 38 // is a set that is made by the user. This would typically correspond to 39 // media items belonging to an event. 40 public int mType; 41 42 // The min and max date taken and added at timestamps. 43 public long mMinTimestamp = Long.MAX_VALUE; 44 public long mMaxTimestamp = 0; 45 public long mMinAddedTimestamp = Long.MAX_VALUE; 46 public long mMaxAddedTimestamp = 0; 47 48 // The latitude and longitude of the min latitude point. 49 public double mMinLatLatitude = LocationMediaFilter.LAT_MAX; 50 public double mMinLatLongitude; 51 // The latitude and longitude of the max latitude point. 52 public double mMaxLatLatitude = LocationMediaFilter.LAT_MIN; 53 public double mMaxLatLongitude; 54 // The latitude and longitude of the min longitude point. 55 public double mMinLonLatitude; 56 public double mMinLonLongitude = LocationMediaFilter.LON_MAX; 57 // The latitude and longitude of the max longitude point. 58 public double mMaxLonLatitude; 59 public double mMaxLonLongitude = LocationMediaFilter.LON_MIN; 60 61 // Reverse geocoding the latitude, longitude and getting an address or 62 // location. 63 public String mReverseGeocodedLocation; 64 // Set to true if at least one item in the set has a valid latitude and 65 // longitude. 66 public boolean mLatLongDetermined = false; 67 public boolean mReverseGeocodedLocationComputed = false; 68 public boolean mReverseGeocodedLocationRequestMade = false; 69 70 public String mTitleString; 71 public String mTruncTitleString; 72 public String mNoCountTitleString; 73 74 public String mEditUri = null; 75 public long mPicasaAlbumId = Shared.INVALID; 76 public boolean mIsLocal = true; 77 78 public DataSource mDataSource; 79 public boolean mSyncPending = false; 80 81 private ArrayList<MediaItem> mItems; 82 private LongSparseArray<MediaItem> mItemsLookup; 83 private LongSparseArray<MediaItem> mItemsLookupVideo; 84 public int mNumItemsLoaded = 0; 85 // mNumExpectedItems is preset to how many items are expected to be in the 86 // set as it is used to visually 87 // display the number of items in the set and we don't want this display to 88 // keep changing as items get loaded. 89 private int mNumExpectedItems = 0; 90 private boolean mNumExpectedItemsCountAccurate = false; 91 private int mCurrentLocation = 0; 92 93 public MediaSet() { 94 this(null); 95 } 96 97 public MediaSet(DataSource dataSource) { 98 mItems = new ArrayList<MediaItem>(16); 99 mItemsLookup = new LongSparseArray<MediaItem>(); 100 mItemsLookup.clear(); 101 mItemsLookupVideo = new LongSparseArray<MediaItem>(); 102 mItemsLookupVideo.clear(); 103 mDataSource = dataSource; 104 // TODO(Venkat): Can we move away from this dummy item setup? 105 MediaItem item = new MediaItem(); 106 item.mId = Shared.INVALID; 107 item.mParentMediaSet = this; 108 mItems.add(item); 109 mNumExpectedItems = 16; 110 } 111 112 /** 113 * @return underlying ArrayList of MediaItems. Use only for iteration (read 114 * operations) on the ArrayList. 115 */ 116 public ArrayList<MediaItem> getItems() { 117 return mItems; 118 } 119 120 public void setNumExpectedItems(int numExpectedItems) { 121 mItems.ensureCapacity(numExpectedItems); 122 mNumExpectedItems = numExpectedItems; 123 mNumExpectedItemsCountAccurate = true; 124 } 125 126 public int getNumExpectedItems() { 127 return mNumExpectedItems; 128 } 129 130 public boolean setContainsValidItems() { 131 if (mNumExpectedItems == 0) 132 return false; 133 return true; 134 } 135 136 public void updateNumExpectedItems() { 137 mNumExpectedItems = mNumItemsLoaded; 138 mNumExpectedItemsCountAccurate = true; 139 } 140 141 public int getNumItems() { 142 return mItems.size(); 143 } 144 145 public void clear() { 146 mItems.clear(); 147 MediaItem item = new MediaItem(); 148 item.mId = Shared.INVALID; 149 item.mParentMediaSet = this; 150 mItems.add(item); 151 mNumExpectedItems = 16; 152 refresh(); 153 mItemsLookup.clear(); 154 mItemsLookupVideo.clear(); 155 } 156 157 /** 158 * Generates the label for the MediaSet. 159 */ 160 public void generateTitle(final boolean truncateTitle) { 161 if (mName == null) { 162 mName = ""; 163 } 164 String size = (mNumExpectedItemsCountAccurate) ? " (" + mNumExpectedItems + ")" : ""; 165 mTitleString = mName + size; 166 if (truncateTitle) { 167 int length = mName.length(); 168 mTruncTitleString = (length > 16) ? mName.substring(0, 12) + "..." + mName.substring(length - 4, length) + size : mName 169 + size; 170 mNoCountTitleString = mName; 171 } else { 172 mTruncTitleString = mTitleString; 173 } 174 } 175 176 /** 177 * Adds a MediaItem to this set, and increments the load count. 178 * Additionally, it also recomputes the location bounds and time range of 179 * the media set. 180 */ 181 public void addItem(final MediaItem itemToAdd) { 182 // Important to not set the parentMediaSet in here as temporary 183 // MediaSet's are occasionally 184 // created and we do not want the MediaItem updated as a result of that. 185 if (itemToAdd == null) { 186 return; 187 } 188 final LongSparseArray<MediaItem> lookup = (itemToAdd.getMediaType() == MediaItem.MEDIA_TYPE_IMAGE) ? mItemsLookup 189 : mItemsLookupVideo; 190 MediaItem lookupItem = lookup.get(itemToAdd.mId); 191 if (lookupItem != null && !lookupItem.mFilePath.equals(itemToAdd.mFilePath)) { 192 lookupItem = null; 193 } 194 final MediaItem item = (lookupItem == null) ? itemToAdd : lookupItem; 195 item.mFlagForDelete = false; 196 if (mItems.size() == 0) { 197 mItems.add(item); 198 } else if (mItems.get(0).mId == -1L) { 199 mItems.set(0, item); 200 } else { 201 if (mItems.size() > mCurrentLocation) { 202 mItems.set(mCurrentLocation, item); 203 } else { 204 mItems.add(mCurrentLocation, item); 205 } 206 } 207 if (item.mId != Shared.INVALID) { 208 if (lookupItem == null) { 209 lookup.put(item.mId, item); 210 } 211 ++mNumItemsLoaded; 212 ++mCurrentLocation; 213 } 214 if (item.isDateTakenValid()) { 215 long dateTaken = item.mDateTakenInMs; 216 if (dateTaken < mMinTimestamp) { 217 mMinTimestamp = dateTaken; 218 } 219 if (dateTaken > mMaxTimestamp) { 220 mMaxTimestamp = dateTaken; 221 } 222 } else if (item.isDateAddedValid()) { 223 long dateAdded = item.mDateAddedInSec * 1000; 224 if (dateAdded < mMinAddedTimestamp) { 225 mMinAddedTimestamp = dateAdded; 226 } 227 if (dateAdded > mMaxAddedTimestamp) { 228 mMaxAddedTimestamp = dateAdded; 229 } 230 } 231 232 // Determining the latitude longitude bounds of the set and setting the 233 // location string. 234 if (!item.isLatLongValid()) { 235 return; 236 } 237 double itemLatitude = item.mLatitude; 238 double itemLongitude = item.mLongitude; 239 if (mMinLatLatitude > itemLatitude) { 240 mMinLatLatitude = itemLatitude; 241 mMinLatLongitude = itemLongitude; 242 mLatLongDetermined = true; 243 } 244 if (mMaxLatLatitude < itemLatitude) { 245 mMaxLatLatitude = itemLatitude; 246 mMaxLatLongitude = itemLongitude; 247 mLatLongDetermined = true; 248 } 249 if (mMinLonLongitude > itemLongitude) { 250 mMinLonLatitude = itemLatitude; 251 mMinLonLongitude = itemLongitude; 252 mLatLongDetermined = true; 253 } 254 if (mMaxLonLongitude < itemLongitude) { 255 mMaxLonLatitude = itemLatitude; 256 mMaxLonLongitude = itemLongitude; 257 mLatLongDetermined = true; 258 } 259 } 260 261 /** 262 * Removes a MediaItem if present in the MediaSet. 263 * 264 * @return true if the item was removed, false if removal failed or item was 265 * not present in the set. 266 */ 267 public boolean removeItem(final MediaItem itemToRemove) { 268 synchronized (mItems) { 269 if (mItems.remove(itemToRemove)) { 270 --mNumExpectedItems; 271 --mNumItemsLoaded; 272 --mCurrentLocation; 273 final LongSparseArray<MediaItem> lookup = (itemToRemove.getMediaType() == MediaItem.MEDIA_TYPE_IMAGE) ? mItemsLookup 274 : mItemsLookupVideo; 275 lookup.remove(itemToRemove.mId); 276 return true; 277 } 278 return false; 279 } 280 } 281 282 public void removeDuplicate(final MediaItem itemToRemove) { 283 synchronized (mItems) { 284 int numItems = mItems.size(); 285 boolean foundItem = false; 286 for (int i = 0; i < numItems; ++i) { 287 final MediaItem item = mItems.get(i); 288 if (item == itemToRemove) { 289 if (foundItem == false) { 290 foundItem = true; 291 } else { 292 mItems.remove(i); 293 --mNumExpectedItems; 294 --mNumItemsLoaded; 295 --mCurrentLocation; 296 break; 297 } 298 } 299 } 300 } 301 } 302 303 /** 304 * @return true if this MediaSet contains the argument MediaItem. 305 */ 306 public boolean lookupContainsItem(final MediaItem item) { 307 final LongSparseArray<MediaItem> lookupTable = (item.getMediaType() == MediaItem.MEDIA_TYPE_IMAGE) ? mItemsLookup 308 : mItemsLookupVideo; 309 MediaItem lookUp = lookupTable.get(item.mId); 310 if (lookUp != null && lookUp.mFilePath.equals(item.mFilePath)) { 311 return true; 312 } else { 313 return false; 314 } 315 } 316 317 /** 318 * @return true if the title string is truncated. 319 */ 320 public boolean isTruncated() { 321 return (mTitleString != null && !mTitleString.equals(mTruncTitleString)); 322 } 323 324 /** 325 * @return true if timestamps are available for this set. 326 */ 327 public boolean areTimestampsAvailable() { 328 return (mMinTimestamp < Long.MAX_VALUE && mMaxTimestamp > 0); 329 } 330 331 /** 332 * @return true if the added timestamps are available for this set. 333 */ 334 public boolean areAddedTimestampsAvailable() { 335 return (mMinAddedTimestamp < Long.MAX_VALUE && mMaxAddedTimestamp > 0); 336 } 337 338 /** 339 * @return true if this set of items corresponds to Picassa items. 340 */ 341 public boolean isPicassaSet() { 342 // 2 cases:- 343 // 1. This set is just a Picassa Album, and all its items are therefore 344 // from Picassa. 345 // 2. This set is a random collection of items and each item is a 346 // Picassa item. 347 if (isPicassaAlbum()) { 348 return true; 349 } 350 int numItems = mItems.size(); 351 for (int i = 0; i < numItems; i++) { 352 if (!mItems.get(i).isPicassaItem()) { 353 return false; 354 } 355 } 356 return true; 357 } 358 359 /** 360 * @return true if this set is a Picassa album. 361 */ 362 public boolean isPicassaAlbum() { 363 return (mPicasaAlbumId != Shared.INVALID); 364 } 365 366 public void refresh() { 367 mNumItemsLoaded = 0; 368 mCurrentLocation = 0; 369 final ArrayList<MediaItem> items = mItems; 370 final int numItems = items.size(); 371 for (int i = 0; i < numItems; ++i) { 372 MediaItem item = items.get(i); 373 item.mFlagForDelete = true; 374 } 375 } 376 377 public void checkForDeletedItems() { 378 final ArrayList<MediaItem> items = mItems; 379 final ArrayList<MediaItem> itemsToDelete = new ArrayList<MediaItem>(); 380 synchronized (items) { 381 final int numItems = items.size(); 382 for (int i = 0; i < numItems; ++i) { 383 MediaItem item = items.get(i); 384 if (item.mFlagForDelete) { 385 itemsToDelete.add(item); 386 } 387 } 388 } 389 final int numItemsToDelete = itemsToDelete.size(); 390 for (int i = 0; i < numItemsToDelete; ++i) { 391 removeItem(itemsToDelete.get(i)); 392 } 393 } 394 } 395