private boolean needContentReload() {
   for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
     if (mData[i % DATA_CACHE_SIZE] == null) return true;
   }
   MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
   return current == null || current.getPath() != mItemPath;
 }
  private void updateImageCache() {
    HashSet<Long> toBeRemoved = new HashSet<Long>(mImageCache.keySet());
    for (int i = mActiveStart; i < mActiveEnd; ++i) {
      MediaItem item = mData[i % DATA_CACHE_SIZE];
      long version = item == null ? MediaObject.INVALID_DATA_VERSION : item.getDataVersion();
      if (version == MediaObject.INVALID_DATA_VERSION) continue;
      ImageEntry entry = mImageCache.get(version);
      toBeRemoved.remove(version);
      if (entry != null) {
        if (Math.abs(i - mCurrentIndex) > 1) {
          if (entry.fullImageTask != null) {
            entry.fullImageTask.cancel();
            entry.fullImageTask = null;
          }
          entry.fullImage = null;
          entry.requestedBits &= ~BIT_FULL_IMAGE;
        }
      } else {
        entry = new ImageEntry();
        entry.rotation = item.getFullImageRotation();
        mImageCache.put(version, entry);
      }
    }

    // Clear the data and requests for ImageEntries outside the new window.
    for (Long version : toBeRemoved) {
      ImageEntry entry = mImageCache.remove(version);
      if (entry.fullImageTask != null) entry.fullImageTask.cancel();
      if (entry.screenNailTask != null) entry.screenNailTask.cancel();
    }
  }
 @Override
 public void run() {
   while (mActive) {
     synchronized (this) {
       if (!mDirty && mActive) {
         updateLoading(false);
         Utils.waitWithoutInterrupt(this);
         continue;
       }
     }
     mDirty = false;
     UpdateInfo info = executeAndWait(new GetUpdateInfo());
     synchronized (DataManager.LOCK) {
       updateLoading(true);
       long version = mSource.reload();
       if (info.version != version) {
         info.reloadContent = true;
         info.size = mSource.getMediaItemCount();
       }
       if (!info.reloadContent) continue;
       info.items = mSource.getMediaItem(info.contentStart, info.contentEnd);
       MediaItem item = findCurrentMediaItem(info);
       if (item == null || item.getPath() != info.target) {
         info.indexHint = findIndexOfTarget(info);
       }
     }
     executeAndWait(new UpdateContent(info));
   }
 }
  // Returns the task if we started the task or the task is already started.
  private Future<?> startTaskIfNeeded(int index, int which) {
    if (index < mActiveStart || index >= mActiveEnd) return null;

    ImageEntry entry = mImageCache.get(getVersion(index));
    if (entry == null) return null;

    if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null) {
      return entry.screenNailTask;
    } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null) {
      return entry.fullImageTask;
    }

    MediaItem item = mData[index % DATA_CACHE_SIZE];
    Utils.assertTrue(item != null);

    if (which == BIT_SCREEN_NAIL && (entry.requestedBits & BIT_SCREEN_NAIL) == 0) {
      entry.requestedBits |= BIT_SCREEN_NAIL;
      entry.screenNailTask =
          mThreadPool.submit(
              new ScreenNailJob(item), new ScreenNailListener(item.getDataVersion()));
      // request screen nail
      return entry.screenNailTask;
    }
    if (which == BIT_FULL_IMAGE
        && (entry.requestedBits & BIT_FULL_IMAGE) == 0
        && (item.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0) {
      entry.requestedBits |= BIT_FULL_IMAGE;
      entry.fullImageTask =
          mThreadPool.submit(
              item.requestLargeImage(), new FullImageListener(item.getDataVersion()));
      // request full image
      return entry.fullImageTask;
    }
    return null;
  }
  private void updateImageRequests() {
    if (!mIsActive) return;

    int currentIndex = mCurrentIndex;
    MediaItem item = mData[currentIndex % DATA_CACHE_SIZE];
    if (item == null || item.getPath() != mItemPath) {
      // current item mismatch - don't request image
      return;
    }

    // 1. Find the most wanted request and start it (if not already started).
    Future<?> task = null;
    for (int i = 0; i < sImageFetchSeq.length; i++) {
      int offset = sImageFetchSeq[i].indexOffset;
      int bit = sImageFetchSeq[i].imageBit;
      task = startTaskIfNeeded(currentIndex + offset, bit);
      if (task != null) break;
    }

    // 2. Cancel everything else.
    for (ImageEntry entry : mImageCache.values()) {
      if (entry.screenNailTask != null && entry.screenNailTask != task) {
        entry.screenNailTask.cancel();
        entry.screenNailTask = null;
        entry.requestedBits &= ~BIT_SCREEN_NAIL;
      }
      if (entry.fullImageTask != null && entry.fullImageTask != task) {
        entry.fullImageTask.cancel();
        entry.fullImageTask = null;
        entry.requestedBits &= ~BIT_FULL_IMAGE;
      }
    }
  }
 private long getVersion(int index) {
   if (index < 0 || index >= mSize) return VERSION_OUT_OF_RANGE;
   if (index >= mContentStart && index < mContentEnd) {
     MediaItem item = mData[index % DATA_CACHE_SIZE];
     if (item != null) return item.getDataVersion();
   }
   return MediaObject.INVALID_DATA_VERSION;
 }
 public void onLongTap(int slotIndex) {
   if (mGetContent) return;
   MediaItem item = mAlbumDataAdapter.get(slotIndex);
   if (item == null) return;
   mSelectionManager.setAutoLeaveSelectionMode(true);
   mSelectionManager.toggle(item.getPath());
   mSlotView.invalidate();
 }
 @Override
 public int getItemIndex(Path path) {
   int start = mSlotView.getVisibleStart();
   int end = mSlotView.getVisibleEnd();
   for (int i = start; i < end; ++i) {
     MediaItem item = mAlbumDataAdapter.get(i);
     if (item != null && item.getPath() == path) return i;
   }
   return -1;
 }
 @Override
 public Bitmap run(JobContext jc) {
   Bitmap bitmap = mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
   if (jc.isCancelled()) return null;
   if (bitmap != null) {
     bitmap =
         BitmapUtils.rotateBitmap(
             bitmap, mItem.getRotation() - mItem.getFullImageRotation(), true);
   }
   return bitmap;
 }
  private boolean execute(DataManager manager, JobContext jc, int cmd, Path path) {
    boolean result = true;
    Log.v(TAG, "Execute cmd: " + cmd + " for " + path);
    long startTime = System.currentTimeMillis();

    switch (cmd) {
      case R.id.action_delete:
        manager.delete(path);
        break;
      case R.id.action_rotate_cw:
        manager.rotate(path, 90);
        break;
      case R.id.action_rotate_ccw:
        manager.rotate(path, -90);
        break;
      case R.id.action_toggle_full_caching:
        {
          MediaObject obj = manager.getMediaObject(path);
          int cacheFlag = obj.getCacheFlag();
          if (cacheFlag == MediaObject.CACHE_FLAG_FULL) {
            cacheFlag = MediaObject.CACHE_FLAG_SCREENNAIL;
          } else {
            cacheFlag = MediaObject.CACHE_FLAG_FULL;
          }
          obj.cache(cacheFlag);
          break;
        }
      case R.id.action_show_on_map:
        {
          MediaItem item = (MediaItem) manager.getMediaObject(path);
          double latlng[] = new double[2];
          item.getLatLong(latlng);
          if (GalleryUtils.isValidLocation(latlng[0], latlng[1])) {
            GalleryUtils.showOnMap((Context) mActivity, latlng[0], latlng[1]);
          }
          break;
        }
      case R.id.action_import:
        {
          MediaObject obj = manager.getMediaObject(path);
          result = obj.Import();
          break;
        }
      default:
        throw new AssertionError();
    }
    Log.v(
        TAG,
        "It takes " + (System.currentTimeMillis() - startTime) + " ms to execute cmd for " + path);
    return result;
  }
  public void setCurrentPhoto(Path path, int indexHint) {
    if (mItemPath == path) return;
    mItemPath = path;
    mCurrentIndex = indexHint;
    updateSlidingWindow();
    updateImageCache();
    fireModelInvalidated();

    // We need to reload content if the path doesn't match.
    MediaItem item = getCurrentMediaItem();
    if (item != null && item.getPath() != path) {
      if (mReloadTask != null) mReloadTask.notifyDirty();
    }
  }
  private void onSingleTapUp(int slotIndex) {
    if (!mIsActive) return;

    if (mSelectionManager.inSelectionMode()) {
      MediaItem item = mAlbumDataAdapter.get(slotIndex);
      if (item == null) return; // Item not ready yet, ignore the click
      mSelectionManager.toggle(item.getPath());
      mSlotView.invalidate();
    } else {
      // Render transition in pressed state
      mAlbumView.setPressedIndex(slotIndex);
      mAlbumView.setPressedUp();
      mHandler.sendMessageDelayed(
          mHandler.obtainMessage(MSG_PICK_PHOTO, slotIndex, 0), FadeTexture.DURATION);
    }
  }
  private void updateCurrentIndex(int index) {
    mCurrentIndex = index;
    updateSlidingWindow();

    MediaItem item = mData[index % DATA_CACHE_SIZE];
    mItemPath = item == null ? null : item.getPath();

    updateImageCache();
    updateImageRequests();
    updateTileProvider();
    mPhotoView.notifyOnNewImage();

    if (mDataListener != null) {
      mDataListener.onPhotoChanged(index, mItemPath);
    }
    fireModelInvalidated();
  }
    @Override
    public Void call() throws Exception {
      UpdateInfo info = mUpdateInfo;
      mSourceVersion = info.version;

      if (info.size != mSize) {
        mSize = info.size;
        if (mContentEnd > mSize) mContentEnd = mSize;
        if (mActiveEnd > mSize) mActiveEnd = mSize;
      }

      if (info.indexHint == MediaSet.INDEX_NOT_FOUND) {
        // The image has been deleted, clear mItemPath, the
        // mCurrentIndex will be updated in the updateCurrentItem().
        mItemPath = null;
        updateCurrentItem();
      } else {
        mCurrentIndex = info.indexHint;
      }

      updateSlidingWindow();

      if (info.items != null) {
        int start = Math.max(info.contentStart, mContentStart);
        int end = Math.min(info.contentStart + info.items.size(), mContentEnd);
        int dataIndex = start % DATA_CACHE_SIZE;
        for (int i = start; i < end; ++i) {
          mData[dataIndex] = info.items.get(i - info.contentStart);
          if (++dataIndex == DATA_CACHE_SIZE) dataIndex = 0;
        }
      }
      if (mItemPath == null) {
        MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE];
        mItemPath = current == null ? null : current.getPath();
      }
      updateImageCache();
      updateTileProvider();
      updateImageRequests();
      fireModelInvalidated();
      return null;
    }
 private void onGetContent(final MediaItem item) {
   DataManager dm = mActivity.getDataManager();
   Activity activity = mActivity;
   if (mData.getString(GalleryActivity.EXTRA_CROP) != null) {
     Uri uri = dm.getContentUri(item.getPath());
     Intent intent =
         new Intent(CropActivity.CROP_ACTION, uri)
             .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
             .putExtras(getData());
     if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
       intent.putExtra(CropExtras.KEY_RETURN_DATA, true);
     }
     activity.startActivity(intent);
     activity.finish();
   } else {
     Intent intent =
         new Intent(null, item.getContentUri()).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
     activity.setResult(Activity.RESULT_OK, intent);
     activity.finish();
   }
 }
  private void pickPhoto(int slotIndex, boolean startInFilmstrip) {
    if (!mIsActive) return;

    if (!startInFilmstrip) {
      // Launch photos in lights out mode
      mActivity.getGLRoot().setLightsOutMode(true);
    }

    MediaItem item = mAlbumDataAdapter.get(slotIndex);
    if (item == null) return; // Item not ready yet, ignore the click
    if (mGetContent) {
      onGetContent(item);
    } else if (mLaunchedFromPhotoPage) {
      TransitionStore transitions = mActivity.getTransitionStore();
      transitions.put(PhotoPage.KEY_ALBUMPAGE_TRANSITION, PhotoPage.MSG_ALBUMPAGE_PICKED);
      transitions.put(PhotoPage.KEY_INDEX_HINT, slotIndex);
      onBackPressed();
    } else {
      // Get into the PhotoPage.
      // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
      Bundle data = new Bundle();
      data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
      data.putParcelable(
          PhotoPage.KEY_OPEN_ANIMATION_RECT, mSlotView.getSlotRect(slotIndex, mRootPane));
      data.putString(PhotoPage.KEY_MEDIA_SET_PATH, mMediaSetPath.toString());
      data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, item.getPath().toString());
      data.putInt(PhotoPage.KEY_ALBUMPAGE_TRANSITION, PhotoPage.MSG_ALBUMPAGE_STARTED);
      data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, startInFilmstrip);
      data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll());
      if (startInFilmstrip) {
        mActivity.getStateManager().switchState(this, FilmstripPage.class, data);
      } else {
        mActivity.getStateManager().startStateForResult(SinglePhotoPage.class, REQUEST_PHOTO, data);
      }
    }
  }
 public static boolean isPanorama(MediaItem item) {
   if (item == null) return false;
   int w = item.getWidth();
   int h = item.getHeight();
   return (h > 0 && w / h >= 2);
 }