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 import java.util.HashMap; 21 import java.util.HashSet; 22 import java.util.Set; 23 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.database.ContentObserver; 27 import android.util.Log; 28 import android.view.Gravity; 29 import android.widget.Toast; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.Process; 33 34 import com.cooliris.app.App; 35 import com.cooliris.app.Res; 36 import com.cooliris.media.MediaClustering.Cluster; 37 38 public final class MediaFeed implements Runnable { 39 private final String TAG = "MediaFeed"; 40 public static final int OPERATION_DELETE = 0; 41 public static final int OPERATION_ROTATE = 1; 42 public static final int OPERATION_CROP = 2; 43 44 private static final int NUM_ITEMS_LOOKAHEAD = 60; 45 private static final int NUM_INTERRUPT_RETRIES = 30; 46 private static final int JOIN_TIMEOUT = 50; 47 48 private IndexRange mVisibleRange = new IndexRange(); 49 private IndexRange mBufferedRange = new IndexRange(); 50 private ArrayList<MediaSet> mMediaSets = new ArrayList<MediaSet>(); 51 private Listener mListener; 52 private DataSource mDataSource; 53 private boolean mListenerNeedsUpdate = false; 54 private boolean mMediaFeedNeedsToRun = false; 55 private MediaSet mSingleWrapper = new MediaSet(); 56 private boolean mInClusteringMode = false; 57 private HashMap<MediaSet, MediaClustering> mClusterSets = new HashMap<MediaSet, MediaClustering>(32); 58 private int mExpandedMediaSetIndex = Shared.INVALID; 59 private MediaFilter mMediaFilter; 60 private MediaSet mMediaFilteredSet; 61 private Context mContext; 62 private Thread mDataSourceThread = null; 63 private Thread mAlbumSourceThread = null; 64 private boolean mListenerNeedsLayout; 65 private boolean mWaitingForMediaScanner; 66 private boolean mSingleImageMode; 67 private boolean mLoading; 68 private HashMap<String, ContentObserver> mContentObservers = new HashMap<String, ContentObserver>(); 69 private ArrayList<String[]> mRequestedRefresh = new ArrayList<String[]>(); 70 private volatile boolean mIsShutdown = false; 71 72 public interface Listener { onFeedAboutToChange(MediaFeed feed)73 public abstract void onFeedAboutToChange(MediaFeed feed); 74 onFeedChanged(MediaFeed feed, boolean needsLayout)75 public abstract void onFeedChanged(MediaFeed feed, boolean needsLayout); 76 } 77 MediaFeed(Context context, DataSource dataSource, Listener listener)78 public MediaFeed(Context context, DataSource dataSource, Listener listener) { 79 mContext = context; 80 mListener = listener; 81 mDataSource = dataSource; 82 mSingleWrapper.setNumExpectedItems(1); 83 mLoading = true; 84 } 85 shutdown()86 public void shutdown() { 87 mIsShutdown = true; 88 if (mDataSourceThread != null) { 89 mDataSource.shutdown(); 90 repeatShuttingDownThread(mDataSourceThread); 91 mDataSourceThread = null; 92 } 93 if (mAlbumSourceThread != null) { 94 repeatShuttingDownThread(mAlbumSourceThread); 95 mAlbumSourceThread = null; 96 } 97 int numSets = mMediaSets.size(); 98 for (int i = 0; i < numSets; ++i) { 99 MediaSet set = mMediaSets.get(i); 100 set.clear(); 101 } 102 synchronized (mMediaSets) { 103 mMediaSets.clear(); 104 } 105 int numClusters = mClusterSets.size(); 106 for (int i = 0; i < numClusters; ++i) { 107 MediaClustering mc = mClusterSets.get(i); 108 if (mc != null) { 109 mc.clear(); 110 } 111 } 112 mClusterSets.clear(); 113 mListener = null; 114 mDataSource = null; 115 mSingleWrapper = null; 116 } 117 repeatShuttingDownThread(Thread targetThread)118 private void repeatShuttingDownThread(Thread targetThread) { 119 for (int i = 0; i < NUM_INTERRUPT_RETRIES && targetThread.isAlive(); ++i) { 120 targetThread.interrupt(); 121 try { 122 targetThread.join(JOIN_TIMEOUT); 123 } catch (InterruptedException e) { 124 Log.w(TAG, "Cannot stop the thread: " + targetThread.getName(), e); 125 Thread.currentThread().interrupt(); 126 return; 127 } 128 } 129 130 if (targetThread.isAlive()) { 131 Log.w(TAG, "Cannot stop the thread: " + targetThread.getName()); 132 } 133 } 134 setVisibleRange(int begin, int end)135 public void setVisibleRange(int begin, int end) { 136 if (begin != mVisibleRange.begin || end != mVisibleRange.end) { 137 mVisibleRange.begin = begin; 138 mVisibleRange.end = end; 139 int numItems = 96; 140 int numItemsBy2 = numItems / 2; 141 int numItemsBy4 = numItems / 4; 142 mBufferedRange.begin = (begin / numItemsBy2) * numItemsBy2 - numItemsBy4; 143 mBufferedRange.end = mBufferedRange.begin + numItems; 144 mMediaFeedNeedsToRun = true; 145 } 146 } 147 setFilter(MediaFilter filter)148 public void setFilter(MediaFilter filter) { 149 mMediaFilter = filter; 150 mMediaFilteredSet = null; 151 if (mListener != null) { 152 mListener.onFeedAboutToChange(this); 153 } 154 mMediaFeedNeedsToRun = true; 155 } 156 removeFilter()157 public void removeFilter() { 158 mMediaFilter = null; 159 mMediaFilteredSet = null; 160 if (mListener != null) { 161 mListener.onFeedAboutToChange(this); 162 updateListener(true); 163 } 164 mMediaFeedNeedsToRun = true; 165 } 166 getMediaSets()167 public ArrayList<MediaSet> getMediaSets() { 168 return mMediaSets; 169 } 170 getMediaSet(final long setId)171 public MediaSet getMediaSet(final long setId) { 172 if (setId != Shared.INVALID) { 173 try { 174 int mMediaSetsSize = mMediaSets.size(); 175 for (int i = 0; i < mMediaSetsSize; i++) { 176 final MediaSet set = mMediaSets.get(i); 177 if (set.mId == setId) { 178 set.mFlagForDelete = false; 179 return set; 180 } 181 } 182 } catch (Exception e) { 183 return null; 184 } 185 } 186 return null; 187 } 188 getFilteredSet()189 public MediaSet getFilteredSet() { 190 return mMediaFilteredSet; 191 } 192 addMediaSet(final long setId, DataSource dataSource)193 public MediaSet addMediaSet(final long setId, DataSource dataSource) { 194 int numSets = mMediaSets.size(); 195 for (int i = 0; i < numSets; i++) { 196 MediaSet set = mMediaSets.get(i); 197 if ((set.mId == setId) && (set.mDataSource == dataSource)) { 198 // The mediaset already exists, but might be out-dated. 199 // To avoid the same mediaset being added twice, we delete 200 // the old one first, then add the new one below. 201 mMediaSets.remove(i); 202 break; 203 } 204 } 205 206 MediaSet mediaSet = new MediaSet(dataSource); 207 mediaSet.mId = setId; 208 mMediaSets.add(mediaSet); 209 if (mDataSourceThread != null && !mDataSourceThread.isAlive()) { 210 mDataSourceThread.start(); 211 } 212 mMediaFeedNeedsToRun = true; 213 return mediaSet; 214 } 215 getDataSource()216 public DataSource getDataSource() { 217 return mDataSource; 218 } 219 getClustering()220 public MediaClustering getClustering() { 221 if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) { 222 return mClusterSets.get(mMediaSets.get(mExpandedMediaSetIndex)); 223 } 224 return null; 225 } 226 getClustersForSet(final MediaSet set)227 public ArrayList<Cluster> getClustersForSet(final MediaSet set) { 228 ArrayList<Cluster> clusters = null; 229 if (mClusterSets != null && mClusterSets.containsKey(set)) { 230 MediaClustering mediaClustering = mClusterSets.get(set); 231 if (mediaClustering != null) { 232 clusters = mediaClustering.getClusters(); 233 } 234 } 235 return clusters; 236 } 237 addItemToMediaSet(MediaItem item, MediaSet mediaSet)238 public void addItemToMediaSet(MediaItem item, MediaSet mediaSet) { 239 item.mParentMediaSet = mediaSet; 240 mediaSet.addItem(item); 241 synchronized (mClusterSets) { 242 if (item.mClusteringState == MediaItem.NOT_CLUSTERED) { 243 MediaClustering clustering = mClusterSets.get(mediaSet); 244 if (clustering == null) { 245 clustering = new MediaClustering(mediaSet.isPicassaAlbum()); 246 mClusterSets.put(mediaSet, clustering); 247 } 248 clustering.setTimeRange(mediaSet.mMaxTimestamp - mediaSet.mMinTimestamp, mediaSet.getNumExpectedItems()); 249 clustering.addItemForClustering(item); 250 item.mClusteringState = MediaItem.CLUSTERED; 251 } 252 } 253 mMediaFeedNeedsToRun = true; 254 } 255 performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data)256 public void performOperation(final int operation, final ArrayList<MediaBucket> mediaBuckets, final Object data) { 257 int numBuckets = mediaBuckets.size(); 258 final ArrayList<MediaBucket> copyMediaBuckets = new ArrayList<MediaBucket>(numBuckets); 259 for (int i = 0; i < numBuckets; ++i) { 260 copyMediaBuckets.add(mediaBuckets.get(i)); 261 } 262 if (operation == OPERATION_DELETE && mListener != null) { 263 mListener.onFeedAboutToChange(this); 264 } 265 Thread operationThread = new Thread(new Runnable() { 266 public void run() { 267 ArrayList<MediaBucket> mediaBuckets = copyMediaBuckets; 268 if (operation == OPERATION_DELETE) { 269 int numBuckets = mediaBuckets.size(); 270 for (int i = 0; i < numBuckets; ++i) { 271 MediaBucket bucket = mediaBuckets.get(i); 272 MediaSet set = bucket.mediaSet; 273 ArrayList<MediaItem> items = bucket.mediaItems; 274 if (set != null && items == null) { 275 // Remove the entire bucket. 276 removeMediaSet(set); 277 } else if (set != null && items != null) { 278 // We need to remove these items from the set. 279 int numItems = items.size(); 280 // We also need to delete the items from the 281 // cluster. 282 MediaClustering clustering = mClusterSets.get(set); 283 for (int j = 0; j < numItems; ++j) { 284 MediaItem item = items.get(j); 285 removeItemFromMediaSet(item, set); 286 if (clustering != null) { 287 clustering.removeItemFromClustering(item); 288 } 289 } 290 set.updateNumExpectedItems(); 291 set.generateTitle(true); 292 } 293 } 294 updateListener(true); 295 mMediaFeedNeedsToRun = true; 296 if (mDataSource != null) { 297 mDataSource.performOperation(OPERATION_DELETE, mediaBuckets, null); 298 } 299 } else { 300 mDataSource.performOperation(operation, mediaBuckets, data); 301 } 302 } 303 }); 304 operationThread.setName("Operation " + operation); 305 operationThread.start(); 306 } 307 removeMediaSet(MediaSet set)308 public void removeMediaSet(MediaSet set) { 309 synchronized (mMediaSets) { 310 mMediaSets.remove(set); 311 } 312 mMediaFeedNeedsToRun = true; 313 } 314 removeItemFromMediaSet(MediaItem item, MediaSet mediaSet)315 private void removeItemFromMediaSet(MediaItem item, MediaSet mediaSet) { 316 mediaSet.removeItem(item); 317 synchronized (mClusterSets) { 318 MediaClustering clustering = mClusterSets.get(mediaSet); 319 if (clustering != null) { 320 clustering.removeItemFromClustering(item); 321 } 322 } 323 mMediaFeedNeedsToRun = true; 324 } 325 updateListener(boolean needsLayout)326 public void updateListener(boolean needsLayout) { 327 mListenerNeedsUpdate = true; 328 mListenerNeedsLayout = needsLayout; 329 } 330 getNumSlots()331 public int getNumSlots() { 332 int currentMediaSetIndex = mExpandedMediaSetIndex; 333 ArrayList<MediaSet> mediaSets = mMediaSets; 334 int mediaSetsSize = mediaSets.size(); 335 336 if (mInClusteringMode == false) { 337 if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) { 338 return mediaSetsSize; 339 } else { 340 MediaSet setToUse = (mMediaFilteredSet == null) ? mediaSets.get(currentMediaSetIndex) : mMediaFilteredSet; 341 return setToUse.getNumExpectedItems(); 342 } 343 } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) { 344 MediaSet set = mediaSets.get(currentMediaSetIndex); 345 MediaClustering clustering = mClusterSets.get(set); 346 if (clustering != null) { 347 return clustering.getClustersForDisplay().size(); 348 } 349 } 350 return 0; 351 } 352 copySlotStateFrom(MediaFeed another)353 public void copySlotStateFrom(MediaFeed another) { 354 mExpandedMediaSetIndex = another.mExpandedMediaSetIndex; 355 mInClusteringMode = another.mInClusteringMode; 356 } 357 getBreaks()358 public ArrayList<Integer> getBreaks() { 359 if (true) 360 return null; 361 int currentMediaSetIndex = mExpandedMediaSetIndex; 362 ArrayList<MediaSet> mediaSets = mMediaSets; 363 int mediaSetsSize = mediaSets.size(); 364 if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) 365 return null; 366 MediaSet set = mediaSets.get(currentMediaSetIndex); 367 MediaClustering clustering = mClusterSets.get(set); 368 if (clustering != null) { 369 clustering.compute(null, true); 370 final ArrayList<Cluster> clusters = clustering.getClustersForDisplay(); 371 int numClusters = clusters.size(); 372 final ArrayList<Integer> retVal = new ArrayList<Integer>(numClusters); 373 int size = 0; 374 for (int i = 0; i < numClusters; ++i) { 375 size += clusters.get(i).getItems().size(); 376 retVal.add(size); 377 } 378 return retVal; 379 } else { 380 return null; 381 } 382 } 383 getSetForSlot(int slotIndex)384 public MediaSet getSetForSlot(int slotIndex) { 385 if (slotIndex < 0) { 386 return null; 387 } 388 389 ArrayList<MediaSet> mediaSets = mMediaSets; 390 int mediaSetsSize = mediaSets.size(); 391 int currentMediaSetIndex = mExpandedMediaSetIndex; 392 393 if (mInClusteringMode == false) { 394 if (currentMediaSetIndex == Shared.INVALID || currentMediaSetIndex >= mediaSetsSize) { 395 if (slotIndex >= mediaSetsSize) { 396 return null; 397 } 398 return mMediaSets.get(slotIndex); 399 } 400 if (mSingleWrapper.getNumItems() == 0) { 401 mSingleWrapper.addItem(null); 402 } 403 MediaSet setToUse = (mMediaFilteredSet == null) ? mMediaSets.get(currentMediaSetIndex) : mMediaFilteredSet; 404 ArrayList<MediaItem> items = setToUse.getItems(); 405 if (slotIndex >= setToUse.getNumItems()) { 406 return null; 407 } 408 mSingleWrapper.getItems().set(0, items.get(slotIndex)); 409 return mSingleWrapper; 410 } else if (currentMediaSetIndex != Shared.INVALID && currentMediaSetIndex < mediaSetsSize) { 411 MediaSet set = mediaSets.get(currentMediaSetIndex); 412 MediaClustering clustering = mClusterSets.get(set); 413 if (clustering != null) { 414 ArrayList<MediaClustering.Cluster> clusters = clustering.getClustersForDisplay(); 415 if (clusters.size() > slotIndex) { 416 MediaClustering.Cluster cluster = clusters.get(slotIndex); 417 cluster.generateCaption(mContext); 418 return cluster; 419 } 420 } 421 } 422 return null; 423 } 424 getWaitingForMediaScanner()425 public boolean getWaitingForMediaScanner() { 426 return mWaitingForMediaScanner; 427 } 428 isLoading()429 public boolean isLoading() { 430 return mLoading; 431 } 432 start()433 public void start() { 434 final MediaFeed feed = this; 435 onResume(); 436 mLoading = true; 437 mDataSourceThread = new Thread(this); 438 mDataSourceThread.setName("MediaFeed"); 439 mIsShutdown = false; 440 mAlbumSourceThread = new Thread(new Runnable() { 441 public void run() { 442 if (mContext == null) 443 return; 444 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 445 DataSource dataSource = mDataSource; 446 // We must wait while the SD card is mounted or the MediaScanner 447 // is running. 448 if (dataSource != null) { 449 loadMediaSets(); 450 } 451 mWaitingForMediaScanner = false; 452 while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) { 453 // MediaScanner is still running, wait 454 if (Thread.interrupted()) 455 return; 456 mWaitingForMediaScanner = true; 457 try { 458 if (mContext == null) 459 return; 460 showToast(mContext.getResources().getString(Res.string.initializing), Toast.LENGTH_LONG); 461 if (dataSource != null) { 462 loadMediaSets(); 463 } 464 Thread.sleep(10000); 465 } catch (InterruptedException e) { 466 return; 467 } 468 } 469 if (mWaitingForMediaScanner) { 470 showToast(mContext.getResources().getString(Res.string.loading_new), Toast.LENGTH_LONG); 471 mWaitingForMediaScanner = false; 472 loadMediaSets(); 473 } 474 mLoading = false; 475 } 476 }); 477 mAlbumSourceThread.setName("MediaSets"); 478 mAlbumSourceThread.start(); 479 } 480 loadMediaSets()481 private void loadMediaSets() { 482 if (mDataSource == null) 483 return; 484 final ArrayList<MediaSet> sets = mMediaSets; 485 synchronized (sets) { 486 final int numSets = sets.size(); 487 for (int i = 0; i < numSets; ++i) { 488 final MediaSet set = sets.get(i); 489 set.mFlagForDelete = true; 490 } 491 mDataSource.refresh(MediaFeed.this, mDataSource.getDatabaseUris()); 492 mDataSource.loadMediaSets(MediaFeed.this); 493 final ArrayList<MediaSet> setsToRemove = new ArrayList<MediaSet>(); 494 for (int i = 0; i < numSets; ++i) { 495 final MediaSet set = sets.get(i); 496 if (set.mFlagForDelete) { 497 setsToRemove.add(set); 498 } 499 } 500 int numSetsToRemove = setsToRemove.size(); 501 for (int i = 0; i < numSetsToRemove; ++i) { 502 sets.remove(setsToRemove.get(i)); 503 } 504 setsToRemove.clear(); 505 } 506 mMediaFeedNeedsToRun = true; 507 updateListener(false); 508 } 509 showToast(final String string, final int duration)510 private void showToast(final String string, final int duration) { 511 showToast(string, duration, false); 512 } 513 showToast(final String string, final int duration, final boolean centered)514 private void showToast(final String string, final int duration, final boolean centered) { 515 if (mContext != null && !App.get(mContext).isPaused()) { 516 App.get(mContext).getHandler().post(new Runnable() { 517 public void run() { 518 if (mContext != null) { 519 Toast toast = Toast.makeText(mContext, string, duration); 520 if (centered) { 521 toast.setGravity(Gravity.CENTER, 0, 0); 522 } 523 toast.show(); 524 } 525 } 526 }); 527 } 528 } 529 run()530 public void run() { 531 DataSource dataSource = mDataSource; 532 int sleepMs = 10; 533 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 534 if (dataSource != null) { 535 while (!Thread.interrupted() && !mIsShutdown) { 536 String[] databaseUris = null; 537 boolean performRefresh = false; 538 synchronized (mRequestedRefresh) { 539 if (mRequestedRefresh.size() > 0) { 540 // We prune this first. 541 int numRequests = mRequestedRefresh.size(); 542 for (int i = 0; i < numRequests; ++i) { 543 databaseUris = ArrayUtils.addAll(databaseUris, mRequestedRefresh.get(i)); 544 } 545 mRequestedRefresh.clear(); 546 performRefresh = true; 547 // We need to eliminate duplicate uris in this array 548 final HashMap<String, String> uris = new HashMap<String, String>(); 549 if (databaseUris != null) { 550 int numUris = databaseUris.length; 551 for (int i = 0; i < numUris; ++i) { 552 final String uri = databaseUris[i]; 553 if (uri != null) 554 uris.put(uri, uri); 555 } 556 } 557 databaseUris = new String[0]; 558 databaseUris = (String[]) uris.keySet().toArray(databaseUris); 559 } 560 } 561 boolean settingFeedAboutToChange = false; 562 if (performRefresh) { 563 if (dataSource != null) { 564 if (mListener != null) { 565 settingFeedAboutToChange = true; 566 mListener.onFeedAboutToChange(this); 567 } 568 dataSource.refresh(this, databaseUris); 569 mMediaFeedNeedsToRun = true; 570 } 571 } 572 if (mListenerNeedsUpdate && !mMediaFeedNeedsToRun) { 573 mListenerNeedsUpdate = false; 574 if (mListener != null) 575 synchronized (mMediaSets) { 576 mListener.onFeedChanged(this, mListenerNeedsLayout); 577 } 578 try { 579 Thread.sleep(sleepMs); 580 } catch (InterruptedException e) { 581 return; 582 } 583 } else { 584 try { 585 Thread.sleep(sleepMs); 586 } catch (InterruptedException e) { 587 return; 588 } 589 } 590 sleepMs = 300; 591 if (!mMediaFeedNeedsToRun) 592 continue; 593 App app = App.get(mContext); 594 if (app == null || app.isPaused()) 595 continue; 596 if (settingFeedAboutToChange) { 597 updateListener(true); 598 } 599 mMediaFeedNeedsToRun = false; 600 ArrayList<MediaSet> mediaSets = mMediaSets; 601 synchronized (mediaSets) { 602 int expandedSetIndex = mExpandedMediaSetIndex; 603 if (expandedSetIndex >= mMediaSets.size()) { 604 expandedSetIndex = Shared.INVALID; 605 } 606 if (expandedSetIndex == Shared.INVALID) { 607 // We purge the sets outside this visibleRange. 608 int numSets = mediaSets.size(); 609 IndexRange visibleRange = mVisibleRange; 610 IndexRange bufferedRange = mBufferedRange; 611 boolean scanMediaSets = true; 612 for (int i = 0; i < numSets; ++i) { 613 if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) { 614 MediaSet set = mediaSets.get(i); 615 int numItemsLoaded = set.mNumItemsLoaded; 616 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) { 617 synchronized (set) { 618 dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); 619 set.checkForDeletedItems(); 620 } 621 if (set.getNumExpectedItems() == 0) { 622 mediaSets.remove(set); 623 break; 624 } 625 if (mListener != null) { 626 mListenerNeedsUpdate = false; 627 mListener.onFeedChanged(this, mListenerNeedsLayout); 628 mListenerNeedsLayout = false; 629 } 630 sleepMs = 100; 631 scanMediaSets = false; 632 } 633 if (!set.setContainsValidItems()) { 634 mediaSets.remove(set); 635 if (mListener != null) { 636 mListenerNeedsUpdate = false; 637 mListener.onFeedChanged(this, mListenerNeedsLayout); 638 mListenerNeedsLayout = false; 639 } 640 break; 641 } 642 } 643 } 644 numSets = mediaSets.size(); 645 for (int i = 0; i < numSets; ++i) { 646 MediaSet set = mediaSets.get(i); 647 if (i >= bufferedRange.begin && i <= bufferedRange.end) { 648 if (scanMediaSets) { 649 int numItemsLoaded = set.mNumItemsLoaded; 650 if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) { 651 synchronized (set) { 652 dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); 653 set.checkForDeletedItems(); 654 } 655 if (set.getNumExpectedItems() == 0) { 656 mediaSets.remove(set); 657 break; 658 } 659 if (mListener != null) { 660 mListenerNeedsUpdate = false; 661 mListener.onFeedChanged(this, mListenerNeedsLayout); 662 mListenerNeedsLayout = false; 663 } 664 sleepMs = 100; 665 scanMediaSets = false; 666 } 667 } 668 } else if (!mListenerNeedsUpdate && (i < bufferedRange.begin || i > bufferedRange.end)) { 669 // Purge this set to its initial status. 670 MediaClustering clustering = mClusterSets.get(set); 671 if (clustering != null) { 672 clustering.clear(); 673 mClusterSets.remove(set); 674 } 675 if (set.getNumItems() != 0) 676 set.clear(); 677 } 678 } 679 } 680 if (expandedSetIndex != Shared.INVALID) { 681 int numSets = mMediaSets.size(); 682 for (int i = 0; i < numSets; ++i) { 683 // Purge other sets. 684 if (i != expandedSetIndex) { 685 MediaSet set = mediaSets.get(i); 686 MediaClustering clustering = mClusterSets.get(set); 687 if (clustering != null) { 688 clustering.clear(); 689 mClusterSets.remove(set); 690 } 691 if (set.mNumItemsLoaded != 0) 692 set.clear(); 693 } 694 } 695 // Make sure all the items are loaded for the album. 696 int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded; 697 int requestedItems = mVisibleRange.end; 698 // requestedItems count changes in clustering mode. 699 if (mInClusteringMode && mClusterSets != null) { 700 requestedItems = 0; 701 MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex)); 702 if (clustering != null) { 703 ArrayList<Cluster> clusters = clustering.getClustersForDisplay(); 704 int numClusters = clusters.size(); 705 for (int i = 0; i < numClusters; i++) { 706 requestedItems += clusters.get(i).getNumExpectedItems(); 707 } 708 } 709 } 710 MediaSet set = mediaSets.get(expandedSetIndex); 711 if (numItemsLoaded < set.getNumExpectedItems()) { 712 // We perform calculations for a window that gets 713 // anchored to a multiple of NUM_ITEMS_LOOKAHEAD. 714 // The start of the window is 0, x, 2x, 3x ... etc 715 // where x = NUM_ITEMS_LOOKAHEAD. 716 synchronized (set) { 717 dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD) 718 * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD); 719 set.checkForDeletedItems(); 720 } 721 if (set.getNumExpectedItems() == 0) { 722 mediaSets.remove(set); 723 mListenerNeedsUpdate = false; 724 mListener.onFeedChanged(this, mListenerNeedsLayout); 725 mListenerNeedsLayout = false; 726 } 727 if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) { 728 mListenerNeedsUpdate = false; 729 mListener.onFeedChanged(this, mListenerNeedsLayout); 730 mListenerNeedsLayout = false; 731 } 732 } 733 } 734 MediaFilter filter = mMediaFilter; 735 if (filter != null && mMediaFilteredSet == null) { 736 if (expandedSetIndex != Shared.INVALID) { 737 MediaSet set = mediaSets.get(expandedSetIndex); 738 ArrayList<MediaItem> items = set.getItems(); 739 int numItems = set.getNumItems(); 740 MediaSet filteredSet = new MediaSet(); 741 filteredSet.setNumExpectedItems(numItems); 742 mMediaFilteredSet = filteredSet; 743 for (int i = 0; i < numItems; ++i) { 744 MediaItem item = items.get(i); 745 if (filter.pass(item)) { 746 filteredSet.addItem(item); 747 } 748 } 749 filteredSet.updateNumExpectedItems(); 750 filteredSet.generateTitle(true); 751 } 752 updateListener(true); 753 } 754 } 755 } 756 } 757 } 758 expandMediaSet(int mediaSetIndex)759 public void expandMediaSet(int mediaSetIndex) { 760 // We need to check if this slot can be focused or not. 761 if (mListener != null) { 762 mListener.onFeedAboutToChange(this); 763 } 764 if (mExpandedMediaSetIndex > 0 && mediaSetIndex == Shared.INVALID) { 765 // We are collapsing a previously expanded media set 766 if (mediaSetIndex < mMediaSets.size() && mExpandedMediaSetIndex >= 0 && mExpandedMediaSetIndex < mMediaSets.size()) { 767 MediaSet set = mMediaSets.get(mExpandedMediaSetIndex); 768 if (set.getNumItems() == 0) { 769 set.clear(); 770 } 771 } 772 } 773 mExpandedMediaSetIndex = mediaSetIndex; 774 if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) { 775 // Notify Picasa that the user entered the album. 776 // MediaSet set = mMediaSets.get(mediaSetIndex); 777 // PicasaService.requestSync(mContext, 778 // PicasaService.TYPE_ALBUM_PHOTOS, set.mPicasaAlbumId); 779 } 780 updateListener(true); 781 mMediaFeedNeedsToRun = true; 782 } 783 canExpandSet(int slotIndex)784 public boolean canExpandSet(int slotIndex) { 785 int mediaSetIndex = slotIndex; 786 if (mediaSetIndex < mMediaSets.size() && mediaSetIndex >= 0) { 787 MediaSet set = mMediaSets.get(mediaSetIndex); 788 if (set.getNumItems() > 0) { 789 MediaItem item = set.getItems().get(0); 790 if (item.mId == Shared.INVALID) { 791 return false; 792 } 793 return true; 794 } 795 } 796 return false; 797 } 798 hasExpandedMediaSet()799 public boolean hasExpandedMediaSet() { 800 return (mExpandedMediaSetIndex != Shared.INVALID); 801 } 802 restorePreviousClusteringState()803 public boolean restorePreviousClusteringState() { 804 boolean retVal = disableClusteringIfNecessary(); 805 if (retVal) { 806 if (mListener != null) { 807 mListener.onFeedAboutToChange(this); 808 } 809 updateListener(true); 810 mMediaFeedNeedsToRun = true; 811 } 812 return retVal; 813 } 814 disableClusteringIfNecessary()815 private boolean disableClusteringIfNecessary() { 816 if (mInClusteringMode) { 817 // Disable clustering. 818 mInClusteringMode = false; 819 mMediaFeedNeedsToRun = true; 820 return true; 821 } 822 return false; 823 } 824 isClustered()825 public boolean isClustered() { 826 return mInClusteringMode; 827 } 828 getCurrentSet()829 public MediaSet getCurrentSet() { 830 if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) { 831 return mMediaSets.get(mExpandedMediaSetIndex); 832 } 833 return null; 834 } 835 performClustering()836 public void performClustering() { 837 if (mListener != null) { 838 mListener.onFeedAboutToChange(this); 839 } 840 MediaSet setToUse = null; 841 if (mExpandedMediaSetIndex != Shared.INVALID && mExpandedMediaSetIndex < mMediaSets.size()) { 842 setToUse = mMediaSets.get(mExpandedMediaSetIndex); 843 } 844 if (setToUse != null) { 845 MediaClustering clustering = null; 846 synchronized (mClusterSets) { 847 // Make sure the computation is completed to the end. 848 clustering = mClusterSets.get(setToUse); 849 if (clustering != null) { 850 clustering.compute(null, true); 851 } else { 852 return; 853 } 854 } 855 mInClusteringMode = true; 856 updateListener(true); 857 } 858 } 859 moveSetToFront(MediaSet mediaSet)860 public void moveSetToFront(MediaSet mediaSet) { 861 ArrayList<MediaSet> mediaSets = mMediaSets; 862 int numSets = mediaSets.size(); 863 if (numSets == 0) { 864 mediaSets.add(mediaSet); 865 return; 866 } 867 MediaSet setToFind = mediaSets.get(0); 868 if (setToFind == mediaSet) { 869 return; 870 } 871 mediaSets.set(0, mediaSet); 872 int indexToSwapTill = -1; 873 for (int i = 1; i < numSets; ++i) { 874 MediaSet set = mediaSets.get(i); 875 if (set == mediaSet) { 876 mediaSets.set(i, setToFind); 877 indexToSwapTill = i; 878 break; 879 } 880 } 881 if (indexToSwapTill != Shared.INVALID) { 882 for (int i = indexToSwapTill; i > 1; --i) { 883 MediaSet setEnd = mediaSets.get(i); 884 MediaSet setPrev = mediaSets.get(i - 1); 885 mediaSets.set(i, setPrev); 886 mediaSets.set(i - 1, setEnd); 887 } 888 } 889 mMediaFeedNeedsToRun = true; 890 } 891 replaceMediaSet(long setId, DataSource dataSource)892 public MediaSet replaceMediaSet(long setId, DataSource dataSource) { 893 Log.i(TAG, "Replacing media set " + setId); 894 final MediaSet set = getMediaSet(setId); 895 if (set != null) 896 set.refresh(); 897 return set; 898 } 899 setSingleImageMode(boolean singleImageMode)900 public void setSingleImageMode(boolean singleImageMode) { 901 mSingleImageMode = singleImageMode; 902 } 903 isSingleImageMode()904 public boolean isSingleImageMode() { 905 return mSingleImageMode; 906 } 907 getExpandedMediaSet()908 public MediaSet getExpandedMediaSet() { 909 if (mExpandedMediaSetIndex == Shared.INVALID) 910 return null; 911 if (mExpandedMediaSetIndex >= mMediaSets.size()) 912 return null; 913 return mMediaSets.get(mExpandedMediaSetIndex); 914 } 915 refresh()916 public void refresh() { 917 if (mDataSource != null) { 918 synchronized (mRequestedRefresh) { 919 mRequestedRefresh.add(mDataSource.getDatabaseUris()); 920 } 921 } 922 } 923 refresh(final String[] databaseUris)924 private void refresh(final String[] databaseUris) { 925 synchronized (mMediaSets) { 926 if (mDataSource != null) { 927 synchronized (mRequestedRefresh) { 928 mRequestedRefresh.add(databaseUris); 929 } 930 } 931 } 932 } 933 onPause()934 public void onPause() { 935 final HashMap<String, ContentObserver> observers = mContentObservers; 936 final int numObservers = observers.size(); 937 if (numObservers > 0) { 938 String[] uris = new String[numObservers]; 939 final Set<String> keySet = observers.keySet(); 940 if (keySet != null) { 941 uris = keySet.toArray(uris); 942 final int numUris = uris.length; 943 final ContentResolver cr = mContext.getContentResolver(); 944 for (int i = 0; i < numUris; ++i) { 945 final String uri = uris[i]; 946 if (uri != null) { 947 final ContentObserver observer = observers.get(uri); 948 cr.unregisterContentObserver(observer); 949 observers.remove(uri); 950 } 951 } 952 } 953 } 954 observers.clear(); 955 } 956 onResume()957 public void onResume() { 958 final Context context = mContext; 959 final DataSource dataSource = mDataSource; 960 if (context == null || dataSource == null) 961 return; 962 // We setup the listeners for this datasource 963 final String[] uris = dataSource.getDatabaseUris(); 964 final HashMap<String, ContentObserver> observers = mContentObservers; 965 if (context instanceof Gallery) { 966 final Gallery gallery = (Gallery) context; 967 final ContentResolver cr = context.getContentResolver(); 968 if (uris != null) { 969 final int numUris = uris.length; 970 for (int i = 0; i < numUris; ++i) { 971 final String uri = uris[i]; 972 final ContentObserver presentObserver = observers.get(uri); 973 if (presentObserver == null) { 974 final Handler handler = App.get(context).getHandler(); 975 final ContentObserver observer = new ContentObserver(handler) { 976 public void onChange(boolean selfChange) { 977 if (!mWaitingForMediaScanner) { 978 MediaFeed.this.refresh(new String[] { uri }); 979 } 980 } 981 }; 982 cr.registerContentObserver(Uri.parse(uri), true, observer); 983 observers.put(uri, observer); 984 } 985 } 986 } 987 } 988 refresh(); 989 } 990 } 991