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