/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cooliris.media; import java.util.ArrayList; public class MediaSet { public static final int TYPE_SMART = 0; public static final int TYPE_FOLDER = 1; public static final int TYPE_USERDEFINED = 2; public long mId; public String mName; public boolean mFlagForDelete; public boolean mHasImages; public boolean mHasVideos; // The type of the media set. A smart media set is an automatically // generated media set. For example, the most recently // viewed items media set is a media set that gets populated by the contents // of a folder. A user defined media set // is a set that is made by the user. This would typically correspond to // media items belonging to an event. public int mType; // The min and max date taken and added at timestamps. public long mMinTimestamp = Long.MAX_VALUE; public long mMaxTimestamp = 0; public long mMinAddedTimestamp = Long.MAX_VALUE; public long mMaxAddedTimestamp = 0; // The latitude and longitude of the min latitude point. public double mMinLatLatitude = LocationMediaFilter.LAT_MAX; public double mMinLatLongitude; // The latitude and longitude of the max latitude point. public double mMaxLatLatitude = LocationMediaFilter.LAT_MIN; public double mMaxLatLongitude; // The latitude and longitude of the min longitude point. public double mMinLonLatitude; public double mMinLonLongitude = LocationMediaFilter.LON_MAX; // The latitude and longitude of the max longitude point. public double mMaxLonLatitude; public double mMaxLonLongitude = LocationMediaFilter.LON_MIN; // Reverse geocoding the latitude, longitude and getting an address or // location. public String mReverseGeocodedLocation; // Set to true if at least one item in the set has a valid latitude and // longitude. public boolean mLatLongDetermined = false; public boolean mReverseGeocodedLocationComputed = false; public boolean mReverseGeocodedLocationRequestMade = false; public String mTitleString; public String mTruncTitleString; public String mNoCountTitleString; public String mEditUri = null; public long mPicasaAlbumId = Shared.INVALID; public boolean mIsLocal = true; public DataSource mDataSource; public boolean mSyncPending = false; private ArrayList<MediaItem> mItems; private LongSparseArray<MediaItem> mItemsLookup; private LongSparseArray<MediaItem> mItemsLookupVideo; public int mNumItemsLoaded = 0; // mNumExpectedItems is preset to how many items are expected to be in the // set as it is used to visually // display the number of items in the set and we don't want this display to // keep changing as items get loaded. private int mNumExpectedItems = 0; private boolean mNumExpectedItemsCountAccurate = false; private int mCurrentLocation = 0; public MediaSet() { this(null); } public MediaSet(DataSource dataSource) { mItems = new ArrayList<MediaItem>(16); mItemsLookup = new LongSparseArray<MediaItem>(); mItemsLookup.clear(); mItemsLookupVideo = new LongSparseArray<MediaItem>(); mItemsLookupVideo.clear(); mDataSource = dataSource; // TODO(Venkat): Can we move away from this dummy item setup? MediaItem item = new MediaItem(); item.mId = Shared.INVALID; item.mParentMediaSet = this; mItems.add(item); mNumExpectedItems = 16; } /** * @return underlying ArrayList of MediaItems. Use only for iteration (read * operations) on the ArrayList. */ public ArrayList<MediaItem> getItems() { return mItems; } public void setNumExpectedItems(int numExpectedItems) { mItems.ensureCapacity(numExpectedItems); mNumExpectedItems = numExpectedItems; mNumExpectedItemsCountAccurate = true; } public int getNumExpectedItems() { return mNumExpectedItems; } public boolean setContainsValidItems() { if (mNumExpectedItems == 0) return false; return true; } public void updateNumExpectedItems() { mNumExpectedItems = mNumItemsLoaded; mNumExpectedItemsCountAccurate = true; } public int getNumItems() { return mItems.size(); } public void clear() { mItems.clear(); MediaItem item = new MediaItem(); item.mId = Shared.INVALID; item.mParentMediaSet = this; mItems.add(item); mNumExpectedItems = 16; refresh(); mItemsLookup.clear(); mItemsLookupVideo.clear(); } /** * Generates the label for the MediaSet. */ public void generateTitle(final boolean truncateTitle) { if (mName == null) { mName = ""; } String size = (mNumExpectedItemsCountAccurate) ? " (" + mNumExpectedItems + ")" : ""; mTitleString = mName + size; if (truncateTitle) { int length = mName.length(); mTruncTitleString = (length > 16) ? mName.substring(0, 12) + "..." + mName.substring(length - 4, length) + size : mName + size; mNoCountTitleString = mName; } else { mTruncTitleString = mTitleString; } } /** * Adds a MediaItem to this set, and increments the load count. * Additionally, it also recomputes the location bounds and time range of * the media set. */ public void addItem(final MediaItem itemToAdd) { // Important to not set the parentMediaSet in here as temporary // MediaSet's are occasionally // created and we do not want the MediaItem updated as a result of that. if (itemToAdd == null) { return; } final LongSparseArray<MediaItem> lookup = (itemToAdd.getMediaType() == MediaItem.MEDIA_TYPE_IMAGE) ? mItemsLookup : mItemsLookupVideo; MediaItem lookupItem = lookup.get(itemToAdd.mId); if (lookupItem != null && !lookupItem.mFilePath.equals(itemToAdd.mFilePath)) { lookupItem = null; } final MediaItem item = (lookupItem == null) ? itemToAdd : lookupItem; item.mFlagForDelete = false; if (mItems.size() == 0) { mItems.add(item); } else if (mItems.get(0).mId == -1L) { mItems.set(0, item); } else { if (mItems.size() > mCurrentLocation) { mItems.set(mCurrentLocation, item); } else { mItems.add(mCurrentLocation, item); } } if (item.mId != Shared.INVALID) { if (lookupItem == null) { lookup.put(item.mId, item); } ++mNumItemsLoaded; ++mCurrentLocation; } if (item.isDateTakenValid()) { long dateTaken = item.mDateTakenInMs; if (dateTaken < mMinTimestamp) { mMinTimestamp = dateTaken; } if (dateTaken > mMaxTimestamp) { mMaxTimestamp = dateTaken; } } else if (item.isDateAddedValid()) { long dateAdded = item.mDateAddedInSec * 1000; if (dateAdded < mMinAddedTimestamp) { mMinAddedTimestamp = dateAdded; } if (dateAdded > mMaxAddedTimestamp) { mMaxAddedTimestamp = dateAdded; } } // Determining the latitude longitude bounds of the set and setting the // location string. if (!item.isLatLongValid()) { return; } double itemLatitude = item.mLatitude; double itemLongitude = item.mLongitude; if (mMinLatLatitude > itemLatitude) { mMinLatLatitude = itemLatitude; mMinLatLongitude = itemLongitude; mLatLongDetermined = true; } if (mMaxLatLatitude < itemLatitude) { mMaxLatLatitude = itemLatitude; mMaxLatLongitude = itemLongitude; mLatLongDetermined = true; } if (mMinLonLongitude > itemLongitude) { mMinLonLatitude = itemLatitude; mMinLonLongitude = itemLongitude; mLatLongDetermined = true; } if (mMaxLonLongitude < itemLongitude) { mMaxLonLatitude = itemLatitude; mMaxLonLongitude = itemLongitude; mLatLongDetermined = true; } } /** * Removes a MediaItem if present in the MediaSet. * * @return true if the item was removed, false if removal failed or item was * not present in the set. */ public boolean removeItem(final MediaItem itemToRemove) { synchronized (mItems) { if (mItems.remove(itemToRemove)) { --mNumExpectedItems; --mNumItemsLoaded; --mCurrentLocation; final LongSparseArray<MediaItem> lookup = (itemToRemove.getMediaType() == MediaItem.MEDIA_TYPE_IMAGE) ? mItemsLookup : mItemsLookupVideo; lookup.remove(itemToRemove.mId); return true; } return false; } } public void removeDuplicate(final MediaItem itemToRemove) { synchronized (mItems) { int numItems = mItems.size(); boolean foundItem = false; for (int i = 0; i < numItems; ++i) { final MediaItem item = mItems.get(i); if (item == itemToRemove) { if (foundItem == false) { foundItem = true; } else { mItems.remove(i); --mNumExpectedItems; --mNumItemsLoaded; --mCurrentLocation; break; } } } } } /** * @return true if this MediaSet contains the argument MediaItem. */ public boolean lookupContainsItem(final MediaItem item) { final LongSparseArray<MediaItem> lookupTable = (item.getMediaType() == MediaItem.MEDIA_TYPE_IMAGE) ? mItemsLookup : mItemsLookupVideo; MediaItem lookUp = lookupTable.get(item.mId); if (lookUp != null && lookUp.mFilePath.equals(item.mFilePath)) { return true; } else { return false; } } /** * @return true if the title string is truncated. */ public boolean isTruncated() { return (mTitleString != null && !mTitleString.equals(mTruncTitleString)); } /** * @return true if timestamps are available for this set. */ public boolean areTimestampsAvailable() { return (mMinTimestamp < Long.MAX_VALUE && mMaxTimestamp > 0); } /** * @return true if the added timestamps are available for this set. */ public boolean areAddedTimestampsAvailable() { return (mMinAddedTimestamp < Long.MAX_VALUE && mMaxAddedTimestamp > 0); } /** * @return true if this set of items corresponds to Picassa items. */ public boolean isPicassaSet() { // 2 cases:- // 1. This set is just a Picassa Album, and all its items are therefore // from Picassa. // 2. This set is a random collection of items and each item is a // Picassa item. if (isPicassaAlbum()) { return true; } int numItems = mItems.size(); for (int i = 0; i < numItems; i++) { if (!mItems.get(i).isPicassaItem()) { return false; } } return true; } /** * @return true if this set is a Picassa album. */ public boolean isPicassaAlbum() { return (mPicasaAlbumId != Shared.INVALID); } public void refresh() { mNumItemsLoaded = 0; mCurrentLocation = 0; final ArrayList<MediaItem> items = mItems; final int numItems = items.size(); for (int i = 0; i < numItems; ++i) { MediaItem item = items.get(i); item.mFlagForDelete = true; } } public void checkForDeletedItems() { final ArrayList<MediaItem> items = mItems; final ArrayList<MediaItem> itemsToDelete = new ArrayList<MediaItem>(); synchronized (items) { final int numItems = items.size(); for (int i = 0; i < numItems; ++i) { MediaItem item = items.get(i); if (item.mFlagForDelete) { itemsToDelete.add(item); } } } final int numItemsToDelete = itemsToDelete.size(); for (int i = 0; i < numItemsToDelete; ++i) { removeItem(itemsToDelete.get(i)); } } }