summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d
diff options
context:
space:
mode:
authorBobby Georgescu <georgescu@google.com>2014-05-14 10:19:19 -0700
committerBobby Georgescu <georgescu@google.com>2014-05-14 18:01:29 +0000
commitf640d379259bb114a50e3200f49961b89d60f2c2 (patch)
tree9a76ad6315f4f77063dda28b7441e2904ca3cd04 /src/com/android/gallery3d
parent29e13812d006579106c147f87c859aec23dfbe11 (diff)
downloadandroid_packages_apps_Gallery2-f640d379259bb114a50e3200f49961b89d60f2c2.tar.gz
android_packages_apps_Gallery2-f640d379259bb114a50e3200f49961b89d60f2c2.tar.bz2
android_packages_apps_Gallery2-f640d379259bb114a50e3200f49961b89d60f2c2.zip
Update ingest importer code
Change-Id: I0f3b0809deead2f49501a5309f0ddab9c911274f
Diffstat (limited to 'src/com/android/gallery3d')
-rw-r--r--src/com/android/gallery3d/ingest/ImportTask.java95
-rw-r--r--src/com/android/gallery3d/ingest/IngestActivity.java964
-rw-r--r--src/com/android/gallery3d/ingest/IngestService.java497
-rw-r--r--src/com/android/gallery3d/ingest/MtpDeviceIndex.java602
-rw-r--r--src/com/android/gallery3d/ingest/SimpleDate.java114
-rw-r--r--src/com/android/gallery3d/ingest/adapter/CheckBroker.java59
-rw-r--r--src/com/android/gallery3d/ingest/adapter/MtpAdapter.java338
-rw-r--r--src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java134
-rw-r--r--src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java18
-rw-r--r--src/com/android/gallery3d/ingest/data/DateBucket.java63
-rw-r--r--src/com/android/gallery3d/ingest/data/ImportTask.java121
-rw-r--r--src/com/android/gallery3d/ingest/data/IngestObjectInfo.java114
-rw-r--r--src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java141
-rw-r--r--src/com/android/gallery3d/ingest/data/MtpClient.java266
-rw-r--r--src/com/android/gallery3d/ingest/data/MtpDeviceIndex.java433
-rw-r--r--src/com/android/gallery3d/ingest/data/MtpDeviceIndexRunnable.java186
-rw-r--r--src/com/android/gallery3d/ingest/data/SimpleDate.java127
-rw-r--r--src/com/android/gallery3d/ingest/ui/DateTileView.java136
-rw-r--r--src/com/android/gallery3d/ingest/ui/IngestGridView.java50
-rw-r--r--src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java162
-rw-r--r--src/com/android/gallery3d/ingest/ui/MtpImageView.java441
-rw-r--r--src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java153
22 files changed, 2922 insertions, 2292 deletions
diff --git a/src/com/android/gallery3d/ingest/ImportTask.java b/src/com/android/gallery3d/ingest/ImportTask.java
deleted file mode 100644
index 7d2d641a5..000000000
--- a/src/com/android/gallery3d/ingest/ImportTask.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2013 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.android.gallery3d.ingest;
-
-import android.content.Context;
-import android.mtp.MtpDevice;
-import android.mtp.MtpObjectInfo;
-import android.os.Environment;
-import android.os.PowerManager;
-
-import com.android.gallery3d.util.GalleryUtils;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-public class ImportTask implements Runnable {
-
- public interface Listener {
- void onImportProgress(int visitedCount, int totalCount, String pathIfSuccessful);
-
- void onImportFinish(Collection<MtpObjectInfo> objectsNotImported, int visitedCount);
- }
-
- static private final String WAKELOCK_LABEL = "MTP Import Task";
-
- private Listener mListener;
- private String mDestAlbumName;
- private Collection<MtpObjectInfo> mObjectsToImport;
- private MtpDevice mDevice;
- private PowerManager.WakeLock mWakeLock;
-
- public ImportTask(MtpDevice device, Collection<MtpObjectInfo> objectsToImport,
- String destAlbumName, Context context) {
- mDestAlbumName = destAlbumName;
- mObjectsToImport = objectsToImport;
- mDevice = device;
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, WAKELOCK_LABEL);
- }
-
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- @Override
- public void run() {
- mWakeLock.acquire();
- try {
- List<MtpObjectInfo> objectsNotImported = new LinkedList<MtpObjectInfo>();
- int visited = 0;
- int total = mObjectsToImport.size();
- mListener.onImportProgress(visited, total, null);
- File dest = new File(Environment.getExternalStorageDirectory(), mDestAlbumName);
- dest.mkdirs();
- for (MtpObjectInfo object : mObjectsToImport) {
- visited++;
- String importedPath = null;
- if (GalleryUtils.hasSpaceForSize(object.getCompressedSize())) {
- importedPath = new File(dest, object.getName()).getAbsolutePath();
- if (!mDevice.importFile(object.getObjectHandle(), importedPath)) {
- importedPath = null;
- }
- }
- if (importedPath == null) {
- objectsNotImported.add(object);
- }
- if (mListener != null) {
- mListener.onImportProgress(visited, total, importedPath);
- }
- }
- if (mListener != null) {
- mListener.onImportFinish(objectsNotImported, visited);
- }
- } finally {
- mListener = null;
- mWakeLock.release();
- }
- }
-}
diff --git a/src/com/android/gallery3d/ingest/IngestActivity.java b/src/com/android/gallery3d/ingest/IngestActivity.java
index 687e9fd44..46173d686 100644
--- a/src/com/android/gallery3d/ingest/IngestActivity.java
+++ b/src/com/android/gallery3d/ingest/IngestActivity.java
@@ -16,6 +16,19 @@
package com.android.gallery3d.ingest;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.adapter.CheckBroker;
+import com.android.gallery3d.ingest.adapter.MtpAdapter;
+import com.android.gallery3d.ingest.adapter.MtpPagerAdapter;
+import com.android.gallery3d.ingest.data.ImportTask;
+import com.android.gallery3d.ingest.data.IngestObjectInfo;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex;
+import com.android.gallery3d.ingest.ui.DateTileView;
+import com.android.gallery3d.ingest.ui.IngestGridView;
+import com.android.gallery3d.ingest.ui.IngestGridView.OnClearChoicesListener;
+
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName;
@@ -24,7 +37,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.database.DataSetObserver;
-import android.mtp.MtpObjectInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -41,530 +54,559 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.adapter.CheckBroker;
-import com.android.gallery3d.ingest.adapter.MtpAdapter;
-import com.android.gallery3d.ingest.adapter.MtpPagerAdapter;
-import com.android.gallery3d.ingest.data.MtpBitmapFetch;
-import com.android.gallery3d.ingest.ui.DateTileView;
-import com.android.gallery3d.ingest.ui.IngestGridView;
-import com.android.gallery3d.ingest.ui.IngestGridView.OnClearChoicesListener;
-
import java.lang.ref.WeakReference;
import java.util.Collection;
+/**
+ * MTP importer, main activity.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class IngestActivity extends Activity implements
- MtpDeviceIndex.ProgressListener, ImportTask.Listener {
-
- private IngestService mHelperService;
- private boolean mActive = false;
- private IngestGridView mGridView;
- private MtpAdapter mAdapter;
- private Handler mHandler;
- private ProgressDialog mProgressDialog;
- private ActionMode mActiveActionMode;
-
- private View mWarningView;
- private TextView mWarningText;
- private int mLastCheckedPosition = 0;
-
- private ViewPager mFullscreenPager;
- private MtpPagerAdapter mPagerAdapter;
- private boolean mFullscreenPagerVisible = false;
-
- private MenuItem mMenuSwitcherItem;
- private MenuItem mActionMenuSwitcherItem;
-
- // The MTP framework components don't give us fine-grained file copy
- // progress updates, so for large photos and videos, we will be stuck
- // with a dialog not updating for a long time. To give the user feedback,
- // we switch to the animated indeterminate progress bar after the timeout
- // specified by INDETERMINATE_SWITCH_TIMEOUT_MS. On the next update from
- // the framework, we switch back to the normal progress bar.
- private static final int INDETERMINATE_SWITCH_TIMEOUT_MS = 3000;
-
+ MtpDeviceIndex.ProgressListener, ImportTask.Listener {
+
+ private IngestService mHelperService;
+ private boolean mActive = false;
+ private IngestGridView mGridView;
+ private MtpAdapter mAdapter;
+ private Handler mHandler;
+ private ProgressDialog mProgressDialog;
+ private ActionMode mActiveActionMode;
+
+ private View mWarningView;
+ private TextView mWarningText;
+ private int mLastCheckedPosition = 0;
+
+ private ViewPager mFullscreenPager;
+ private MtpPagerAdapter mPagerAdapter;
+ private boolean mFullscreenPagerVisible = false;
+
+ private MenuItem mMenuSwitcherItem;
+ private MenuItem mActionMenuSwitcherItem;
+
+ // The MTP framework components don't give us fine-grained file copy
+ // progress updates, so for large photos and videos, we will be stuck
+ // with a dialog not updating for a long time. To give the user feedback,
+ // we switch to the animated indeterminate progress bar after the timeout
+ // specified by INDETERMINATE_SWITCH_TIMEOUT_MS. On the next update from
+ // the framework, we switch back to the normal progress bar.
+ private static final int INDETERMINATE_SWITCH_TIMEOUT_MS = 3000;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ doBindHelperService();
+
+ setContentView(R.layout.ingest_activity_item_list);
+ mGridView = (IngestGridView) findViewById(R.id.ingest_gridview);
+ mAdapter = new MtpAdapter(this);
+ mAdapter.registerDataSetObserver(mMasterObserver);
+ mGridView.setAdapter(mAdapter);
+ mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener);
+ mGridView.setOnItemClickListener(mOnItemClickListener);
+ mGridView.setOnClearChoicesListener(mPositionMappingCheckBroker);
+
+ mFullscreenPager = (ViewPager) findViewById(R.id.ingest_view_pager);
+
+ mHandler = new ItemListHandler(this);
+
+ MtpBitmapFetch.configureForContext(this);
+ }
+
+ private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- doBindHelperService();
-
- setContentView(R.layout.ingest_activity_item_list);
- mGridView = (IngestGridView) findViewById(R.id.ingest_gridview);
- mAdapter = new MtpAdapter(this);
- mAdapter.registerDataSetObserver(mMasterObserver);
- mGridView.setAdapter(mAdapter);
- mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener);
- mGridView.setOnItemClickListener(mOnItemClickListener);
- mGridView.setOnClearChoicesListener(mPositionMappingCheckBroker);
-
- mFullscreenPager = (ViewPager) findViewById(R.id.ingest_view_pager);
-
- mHandler = new ItemListHandler(this);
-
- MtpBitmapFetch.configureForContext(this);
+ public void onItemClick(AdapterView<?> adapterView, View itemView, int position,
+ long arg3) {
+ mLastCheckedPosition = position;
+ mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
}
+ };
- private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> adapterView, View itemView, int position, long arg3) {
- mLastCheckedPosition = position;
- mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
- }
- };
-
- private MultiChoiceModeListener mMultiChoiceModeListener = new MultiChoiceModeListener() {
- private boolean mIgnoreItemCheckedStateChanges = false;
+ private MultiChoiceModeListener mMultiChoiceModeListener = new MultiChoiceModeListener() {
+ private boolean mIgnoreItemCheckedStateChanges = false;
- private void updateSelectedTitle(ActionMode mode) {
- int count = mGridView.getCheckedItemCount();
- mode.setTitle(getResources().getQuantityString(
- R.plurals.number_of_items_selected, count, count));
- }
-
- @Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
- boolean checked) {
- if (mIgnoreItemCheckedStateChanges) return;
- if (mAdapter.itemAtPositionIsBucket(position)) {
- SparseBooleanArray checkedItems = mGridView.getCheckedItemPositions();
- mIgnoreItemCheckedStateChanges = true;
- mGridView.setItemChecked(position, false);
-
- // Takes advantage of the fact that SectionIndexer imposes the
- // need to clamp to the valid range
- int nextSectionStart = mAdapter.getPositionForSection(
- mAdapter.getSectionForPosition(position) + 1);
- if (nextSectionStart == position)
- nextSectionStart = mAdapter.getCount();
-
- boolean rangeValue = false; // Value we want to set all of the bucket items to
-
- // Determine if all the items in the bucket are currently checked, so that we
- // can uncheck them, otherwise we will check all items in the bucket.
- for (int i = position + 1; i < nextSectionStart; i++) {
- if (checkedItems.get(i) == false) {
- rangeValue = true;
- break;
- }
- }
-
- // Set all items in the bucket to the desired state
- for (int i = position + 1; i < nextSectionStart; i++) {
- if (checkedItems.get(i) != rangeValue)
- mGridView.setItemChecked(i, rangeValue);
- }
-
- mPositionMappingCheckBroker.onBulkCheckedChange();
- mIgnoreItemCheckedStateChanges = false;
- } else {
- mPositionMappingCheckBroker.onCheckedChange(position, checked);
- }
- mLastCheckedPosition = position;
- updateSelectedTitle(mode);
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
- updateSelectedTitle(mode);
- mActiveActionMode = mode;
- mActionMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
- setSwitcherMenuState(mActionMenuSwitcherItem, mFullscreenPagerVisible);
- return true;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mActiveActionMode = null;
- mActionMenuSwitcherItem = null;
- mHandler.sendEmptyMessage(ItemListHandler.MSG_BULK_CHECKED_CHANGE);
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- updateSelectedTitle(mode);
- return false;
- }
- };
-
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.import_items:
- if (mActiveActionMode != null) {
- mHelperService.importSelectedItems(
- mGridView.getCheckedItemPositions(),
- mAdapter);
- mActiveActionMode.finish();
- }
- return true;
- case R.id.ingest_switch_view:
- setFullscreenPagerVisibility(!mFullscreenPagerVisible);
- return true;
- default:
- return false;
- }
+ private void updateSelectedTitle(ActionMode mode) {
+ int count = mGridView.getCheckedItemCount();
+ mode.setTitle(getResources().getQuantityString(
+ R.plurals.ingest_number_of_items_selected, count, count));
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
- mMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
- menu.findItem(R.id.import_items).setVisible(false);
- setSwitcherMenuState(mMenuSwitcherItem, mFullscreenPagerVisible);
- return true;
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+ boolean checked) {
+ if (mIgnoreItemCheckedStateChanges) {
+ return;
+ }
+ if (mAdapter.itemAtPositionIsBucket(position)) {
+ SparseBooleanArray checkedItems = mGridView.getCheckedItemPositions();
+ mIgnoreItemCheckedStateChanges = true;
+ mGridView.setItemChecked(position, false);
+
+ // Takes advantage of the fact that SectionIndexer imposes the
+ // need to clamp to the valid range
+ int nextSectionStart = mAdapter.getPositionForSection(
+ mAdapter.getSectionForPosition(position) + 1);
+ if (nextSectionStart == position) {
+ nextSectionStart = mAdapter.getCount();
+ }
+
+ boolean rangeValue = false; // Value we want to set all of the bucket items to
+
+ // Determine if all the items in the bucket are currently checked, so that we
+ // can uncheck them, otherwise we will check all items in the bucket.
+ for (int i = position + 1; i < nextSectionStart; i++) {
+ if (!checkedItems.get(i)) {
+ rangeValue = true;
+ break;
+ }
+ }
+
+ // Set all items in the bucket to the desired state
+ for (int i = position + 1; i < nextSectionStart; i++) {
+ if (checkedItems.get(i) != rangeValue) {
+ mGridView.setItemChecked(i, rangeValue);
+ }
+ }
+
+ mPositionMappingCheckBroker.onBulkCheckedChange();
+ mIgnoreItemCheckedStateChanges = false;
+ } else {
+ mPositionMappingCheckBroker.onCheckedChange(position, checked);
+ }
+ mLastCheckedPosition = position;
+ updateSelectedTitle(mode);
}
@Override
- protected void onDestroy() {
- super.onDestroy();
- doUnbindHelperService();
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return onOptionsItemSelected(item);
}
@Override
- protected void onResume() {
- DateTileView.refreshLocale();
- mActive = true;
- if (mHelperService != null) mHelperService.setClientActivity(this);
- updateWarningView();
- super.onResume();
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
+ updateSelectedTitle(mode);
+ mActiveActionMode = mode;
+ mActionMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
+ setSwitcherMenuState(mActionMenuSwitcherItem, mFullscreenPagerVisible);
+ return true;
}
@Override
- protected void onPause() {
- if (mHelperService != null) mHelperService.setClientActivity(null);
- mActive = false;
- cleanupProgressDialog();
- super.onPause();
+ public void onDestroyActionMode(ActionMode mode) {
+ mActiveActionMode = null;
+ mActionMenuSwitcherItem = null;
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_BULK_CHECKED_CHANGE);
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- MtpBitmapFetch.configureForContext(this);
- }
-
- private void showWarningView(int textResId) {
- if (mWarningView == null) {
- mWarningView = findViewById(R.id.ingest_warning_view);
- mWarningText =
- (TextView)mWarningView.findViewById(R.id.ingest_warning_view_text);
- }
- mWarningText.setText(textResId);
- mWarningView.setVisibility(View.VISIBLE);
- setFullscreenPagerVisibility(false);
- mGridView.setVisibility(View.GONE);
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateSelectedTitle(mode);
+ return false;
}
-
- private void hideWarningView() {
- if (mWarningView != null) {
- mWarningView.setVisibility(View.GONE);
- setFullscreenPagerVisibility(false);
- }
+ };
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ if (id == R.id.ingest_import_items) {
+ if (mActiveActionMode != null) {
+ mHelperService.importSelectedItems(
+ mGridView.getCheckedItemPositions(),
+ mAdapter);
+ mActiveActionMode.finish();
+ }
+ return true;
+ } else if (id == R.id.ingest_switch_view) {
+ setFullscreenPagerVisibility(!mFullscreenPagerVisible);
+ return true;
+ } else {
+ return false;
}
-
- private PositionMappingCheckBroker mPositionMappingCheckBroker = new PositionMappingCheckBroker();
-
- private class PositionMappingCheckBroker extends CheckBroker
- implements OnClearChoicesListener {
- private int mLastMappingPager = -1;
- private int mLastMappingGrid = -1;
-
- private int mapPagerToGridPosition(int position) {
- if (position != mLastMappingPager) {
- mLastMappingPager = position;
- mLastMappingGrid = mAdapter.translatePositionWithoutLabels(position);
- }
- return mLastMappingGrid;
- }
-
- private int mapGridToPagerPosition(int position) {
- if (position != mLastMappingGrid) {
- mLastMappingGrid = position;
- mLastMappingPager = mPagerAdapter.translatePositionWithLabels(position);
- }
- return mLastMappingPager;
- }
-
- @Override
- public void setItemChecked(int position, boolean checked) {
- mGridView.setItemChecked(mapPagerToGridPosition(position), checked);
- }
-
- @Override
- public void onCheckedChange(int position, boolean checked) {
- if (mPagerAdapter != null) {
- super.onCheckedChange(mapGridToPagerPosition(position), checked);
- }
- }
-
- @Override
- public boolean isItemChecked(int position) {
- return mGridView.getCheckedItemPositions().get(mapPagerToGridPosition(position));
- }
-
- @Override
- public void onClearChoices() {
- onBulkCheckedChange();
- }
- };
-
- private DataSetObserver mMasterObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
- }
- };
-
- private int pickFullscreenStartingPosition() {
- int firstVisiblePosition = mGridView.getFirstVisiblePosition();
- if (mLastCheckedPosition <= firstVisiblePosition
- || mLastCheckedPosition > mGridView.getLastVisiblePosition()) {
- return firstVisiblePosition;
- } else {
- return mLastCheckedPosition;
- }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
+ mMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
+ menu.findItem(R.id.ingest_import_items).setVisible(false);
+ setSwitcherMenuState(mMenuSwitcherItem, mFullscreenPagerVisible);
+ return true;
+ }
+
+ @Override
+ protected void onDestroy() {
+ doUnbindHelperService();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onResume() {
+ DateTileView.refreshLocale();
+ mActive = true;
+ if (mHelperService != null) {
+ mHelperService.setClientActivity(this);
}
-
- private void setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode) {
- if (menuItem == null) return;
- if (!inFullscreenMode) {
- menuItem.setIcon(android.R.drawable.ic_menu_zoom);
- menuItem.setTitle(R.string.switch_photo_fullscreen);
- } else {
- menuItem.setIcon(android.R.drawable.ic_dialog_dialer);
- menuItem.setTitle(R.string.switch_photo_grid);
- }
+ updateWarningView();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ if (mHelperService != null) {
+ mHelperService.setClientActivity(null);
}
-
- private void setFullscreenPagerVisibility(boolean visible) {
- mFullscreenPagerVisible = visible;
- if (visible) {
- if (mPagerAdapter == null) {
- mPagerAdapter = new MtpPagerAdapter(this, mPositionMappingCheckBroker);
- mPagerAdapter.setMtpDeviceIndex(mAdapter.getMtpDeviceIndex());
- }
- mFullscreenPager.setAdapter(mPagerAdapter);
- mFullscreenPager.setCurrentItem(mPagerAdapter.translatePositionWithLabels(
- pickFullscreenStartingPosition()), false);
- } else if (mPagerAdapter != null) {
- mGridView.setSelection(mAdapter.translatePositionWithoutLabels(
- mFullscreenPager.getCurrentItem()));
- mFullscreenPager.setAdapter(null);
- }
- mGridView.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
- mFullscreenPager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- if (mActionMenuSwitcherItem != null) {
- setSwitcherMenuState(mActionMenuSwitcherItem, visible);
- }
- setSwitcherMenuState(mMenuSwitcherItem, visible);
+ mActive = false;
+ cleanupProgressDialog();
+ super.onPause();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ MtpBitmapFetch.configureForContext(this);
+ }
+
+ private void showWarningView(int textResId) {
+ if (mWarningView == null) {
+ mWarningView = findViewById(R.id.ingest_warning_view);
+ mWarningText =
+ (TextView) mWarningView.findViewById(R.id.ingest_warning_view_text);
}
-
- private void updateWarningView() {
- if (!mAdapter.deviceConnected()) {
- showWarningView(R.string.ingest_no_device);
- } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
- showWarningView(R.string.ingest_empty_device);
- } else {
- hideWarningView();
- }
+ mWarningText.setText(textResId);
+ mWarningView.setVisibility(View.VISIBLE);
+ setFullscreenPagerVisibility(false);
+ mGridView.setVisibility(View.GONE);
+ setSwitcherMenuVisibility(false);
+ }
+
+ private void hideWarningView() {
+ if (mWarningView != null) {
+ mWarningView.setVisibility(View.GONE);
+ setFullscreenPagerVisibility(false);
}
-
- private void UiThreadNotifyIndexChanged() {
- mAdapter.notifyDataSetChanged();
- if (mActiveActionMode != null) {
- mActiveActionMode.finish();
- mActiveActionMode = null;
- }
- updateWarningView();
+ setSwitcherMenuVisibility(true);
+ }
+
+ private PositionMappingCheckBroker mPositionMappingCheckBroker =
+ new PositionMappingCheckBroker();
+
+ private class PositionMappingCheckBroker extends CheckBroker
+ implements OnClearChoicesListener {
+ private int mLastMappingPager = -1;
+ private int mLastMappingGrid = -1;
+
+ private int mapPagerToGridPosition(int position) {
+ if (position != mLastMappingPager) {
+ mLastMappingPager = position;
+ mLastMappingGrid = mAdapter.translatePositionWithoutLabels(position);
+ }
+ return mLastMappingGrid;
}
- protected void notifyIndexChanged() {
- mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
+ private int mapGridToPagerPosition(int position) {
+ if (position != mLastMappingGrid) {
+ mLastMappingGrid = position;
+ mLastMappingPager = mPagerAdapter.translatePositionWithLabels(position);
+ }
+ return mLastMappingPager;
}
- private static class ProgressState {
- String message;
- String title;
- int current;
- int max;
-
- public void reset() {
- title = null;
- message = null;
- current = 0;
- max = 0;
- }
+ @Override
+ public void setItemChecked(int position, boolean checked) {
+ mGridView.setItemChecked(mapPagerToGridPosition(position), checked);
}
- private ProgressState mProgressState = new ProgressState();
-
@Override
- public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
- // Not guaranteed to be called on the UI thread
- mProgressState.reset();
- mProgressState.max = 0;
- mProgressState.message = getResources().getQuantityString(
- R.plurals.ingest_number_of_items_scanned, numVisited, numVisited);
- mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+ public void onCheckedChange(int position, boolean checked) {
+ if (mPagerAdapter != null) {
+ super.onCheckedChange(mapGridToPagerPosition(position), checked);
+ }
}
@Override
- public void onSorting() {
- // Not guaranteed to be called on the UI thread
- mProgressState.reset();
- mProgressState.max = 0;
- mProgressState.message = getResources().getString(R.string.ingest_sorting);
- mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+ public boolean isItemChecked(int position) {
+ return mGridView.getCheckedItemPositions().get(mapPagerToGridPosition(position));
}
@Override
- public void onIndexFinish() {
- // Not guaranteed to be called on the UI thread
- mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
- mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
+ public void onClearChoices() {
+ onBulkCheckedChange();
}
+ }
+ private DataSetObserver mMasterObserver = new DataSetObserver() {
@Override
- public void onImportProgress(final int visitedCount, final int totalCount,
- String pathIfSuccessful) {
- // Not guaranteed to be called on the UI thread
- mProgressState.reset();
- mProgressState.max = totalCount;
- mProgressState.current = visitedCount;
- mProgressState.title = getResources().getString(R.string.ingest_importing);
- mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
- mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
- mHandler.sendEmptyMessageDelayed(ItemListHandler.MSG_PROGRESS_INDETERMINATE,
- INDETERMINATE_SWITCH_TIMEOUT_MS);
+ public void onChanged() {
+ if (mPagerAdapter != null) {
+ mPagerAdapter.notifyDataSetChanged();
+ }
}
@Override
- public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported,
- int numVisited) {
- // Not guaranteed to be called on the UI thread
- mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
- mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
- // TODO: maybe show an extra dialog listing the ones that failed
- // importing, if any?
- }
-
- private ProgressDialog getProgressDialog() {
- if (mProgressDialog == null || !mProgressDialog.isShowing()) {
- mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setCancelable(false);
- }
- return mProgressDialog;
+ public void onInvalidated() {
+ if (mPagerAdapter != null) {
+ mPagerAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ private int pickFullscreenStartingPosition() {
+ int firstVisiblePosition = mGridView.getFirstVisiblePosition();
+ if (mLastCheckedPosition <= firstVisiblePosition
+ || mLastCheckedPosition > mGridView.getLastVisiblePosition()) {
+ return firstVisiblePosition;
+ } else {
+ return mLastCheckedPosition;
}
+ }
- private void updateProgressDialog() {
- ProgressDialog dialog = getProgressDialog();
- boolean indeterminate = (mProgressState.max == 0);
- dialog.setIndeterminate(indeterminate);
- dialog.setProgressStyle(indeterminate ? ProgressDialog.STYLE_SPINNER
- : ProgressDialog.STYLE_HORIZONTAL);
- if (mProgressState.title != null) {
- dialog.setTitle(mProgressState.title);
- }
- if (mProgressState.message != null) {
- dialog.setMessage(mProgressState.message);
- }
- if (!indeterminate) {
- dialog.setProgress(mProgressState.current);
- dialog.setMax(mProgressState.max);
- }
- if (!dialog.isShowing()) {
- dialog.show();
- }
+ private void setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode) {
+ if (menuItem == null) {
+ return;
+ }
+ if (!inFullscreenMode) {
+ menuItem.setIcon(android.R.drawable.ic_menu_zoom);
+ menuItem.setTitle(R.string.ingest_switch_photo_fullscreen);
+ } else {
+ menuItem.setIcon(android.R.drawable.ic_dialog_dialer);
+ menuItem.setTitle(R.string.ingest_switch_photo_grid);
+ }
+ }
+
+ private void setFullscreenPagerVisibility(boolean visible) {
+ mFullscreenPagerVisible = visible;
+ if (visible) {
+ if (mPagerAdapter == null) {
+ mPagerAdapter = new MtpPagerAdapter(this, mPositionMappingCheckBroker);
+ mPagerAdapter.setMtpDeviceIndex(mAdapter.getMtpDeviceIndex());
+ }
+ mFullscreenPager.setAdapter(mPagerAdapter);
+ mFullscreenPager.setCurrentItem(mPagerAdapter.translatePositionWithLabels(
+ pickFullscreenStartingPosition()), false);
+ } else if (mPagerAdapter != null) {
+ mGridView.setSelection(mAdapter.translatePositionWithoutLabels(
+ mFullscreenPager.getCurrentItem()));
+ mFullscreenPager.setAdapter(null);
+ }
+ mGridView.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
+ mFullscreenPager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ if (mActionMenuSwitcherItem != null) {
+ setSwitcherMenuState(mActionMenuSwitcherItem, visible);
}
+ setSwitcherMenuState(mMenuSwitcherItem, visible);
+ }
- private void makeProgressDialogIndeterminate() {
- ProgressDialog dialog = getProgressDialog();
- dialog.setIndeterminate(true);
+ private void setSwitcherMenuVisibility(boolean visible) {
+ if (mActionMenuSwitcherItem != null) {
+ mActionMenuSwitcherItem.setVisible(visible);
}
+ if (mMenuSwitcherItem != null) {
+ mMenuSwitcherItem.setVisible(visible);
+ }
+ }
+
+ private void updateWarningView() {
+ if (!mAdapter.deviceConnected()) {
+ showWarningView(R.string.ingest_no_device);
+ } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
+ showWarningView(R.string.ingest_empty_device);
+ } else {
+ hideWarningView();
+ }
+ }
- private void cleanupProgressDialog() {
- if (mProgressDialog != null) {
- mProgressDialog.hide();
- mProgressDialog = null;
- }
+ private void uiThreadNotifyIndexChanged() {
+ mAdapter.notifyDataSetChanged();
+ if (mActiveActionMode != null) {
+ mActiveActionMode.finish();
+ mActiveActionMode = null;
+ }
+ updateWarningView();
+ }
+
+ protected void notifyIndexChanged() {
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
+ }
+
+ private static class ProgressState {
+ String message;
+ String title;
+ int current;
+ int max;
+
+ public void reset() {
+ title = null;
+ message = null;
+ current = 0;
+ max = 0;
+ }
+ }
+
+ private ProgressState mProgressState = new ProgressState();
+
+ @Override
+ public void onObjectIndexed(IngestObjectInfo object, int numVisited) {
+ // Not guaranteed to be called on the UI thread
+ mProgressState.reset();
+ mProgressState.max = 0;
+ mProgressState.message = getResources().getQuantityString(
+ R.plurals.ingest_number_of_items_scanned, numVisited, numVisited);
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+ }
+
+ @Override
+ public void onSortingStarted() {
+ // Not guaranteed to be called on the UI thread
+ mProgressState.reset();
+ mProgressState.max = 0;
+ mProgressState.message = getResources().getString(R.string.ingest_sorting);
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+ }
+
+ @Override
+ public void onIndexingFinished() {
+ // Not guaranteed to be called on the UI thread
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
+ }
+
+ @Override
+ public void onImportProgress(final int visitedCount, final int totalCount,
+ String pathIfSuccessful) {
+ // Not guaranteed to be called on the UI thread
+ mProgressState.reset();
+ mProgressState.max = totalCount;
+ mProgressState.current = visitedCount;
+ mProgressState.title = getResources().getString(R.string.ingest_importing);
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+ mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
+ mHandler.sendEmptyMessageDelayed(ItemListHandler.MSG_PROGRESS_INDETERMINATE,
+ INDETERMINATE_SWITCH_TIMEOUT_MS);
+ }
+
+ @Override
+ public void onImportFinish(Collection<IngestObjectInfo> objectsNotImported,
+ int numVisited) {
+ // Not guaranteed to be called on the UI thread
+ mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
+ mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
+ // TODO(georgescu): maybe show an extra dialog listing the ones that failed
+ // importing, if any?
+ }
+
+ private ProgressDialog getProgressDialog() {
+ if (mProgressDialog == null || !mProgressDialog.isShowing()) {
+ mProgressDialog = new ProgressDialog(this);
+ mProgressDialog.setCancelable(false);
+ }
+ return mProgressDialog;
+ }
+
+ private void updateProgressDialog() {
+ ProgressDialog dialog = getProgressDialog();
+ boolean indeterminate = (mProgressState.max == 0);
+ dialog.setIndeterminate(indeterminate);
+ dialog.setProgressStyle(indeterminate ? ProgressDialog.STYLE_SPINNER
+ : ProgressDialog.STYLE_HORIZONTAL);
+ if (mProgressState.title != null) {
+ dialog.setTitle(mProgressState.title);
+ }
+ if (mProgressState.message != null) {
+ dialog.setMessage(mProgressState.message);
+ }
+ if (!indeterminate) {
+ dialog.setProgress(mProgressState.current);
+ dialog.setMax(mProgressState.max);
+ }
+ if (!dialog.isShowing()) {
+ dialog.show();
}
+ }
- // This is static and uses a WeakReference in order to avoid leaking the Activity
- private static class ItemListHandler extends Handler {
- public static final int MSG_PROGRESS_UPDATE = 0;
- public static final int MSG_PROGRESS_HIDE = 1;
- public static final int MSG_NOTIFY_CHANGED = 2;
- public static final int MSG_BULK_CHECKED_CHANGE = 3;
- public static final int MSG_PROGRESS_INDETERMINATE = 4;
+ private void makeProgressDialogIndeterminate() {
+ ProgressDialog dialog = getProgressDialog();
+ dialog.setIndeterminate(true);
+ }
- WeakReference<IngestActivity> mParentReference;
+ private void cleanupProgressDialog() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
- public ItemListHandler(IngestActivity parent) {
- super();
- mParentReference = new WeakReference<IngestActivity>(parent);
- }
+ // This is static and uses a WeakReference in order to avoid leaking the Activity
+ private static class ItemListHandler extends Handler {
+ public static final int MSG_PROGRESS_UPDATE = 0;
+ public static final int MSG_PROGRESS_HIDE = 1;
+ public static final int MSG_NOTIFY_CHANGED = 2;
+ public static final int MSG_BULK_CHECKED_CHANGE = 3;
+ public static final int MSG_PROGRESS_INDETERMINATE = 4;
- public void handleMessage(Message message) {
- IngestActivity parent = mParentReference.get();
- if (parent == null || !parent.mActive)
- return;
- switch (message.what) {
- case MSG_PROGRESS_HIDE:
- parent.cleanupProgressDialog();
- break;
- case MSG_PROGRESS_UPDATE:
- parent.updateProgressDialog();
- break;
- case MSG_NOTIFY_CHANGED:
- parent.UiThreadNotifyIndexChanged();
- break;
- case MSG_BULK_CHECKED_CHANGE:
- parent.mPositionMappingCheckBroker.onBulkCheckedChange();
- break;
- case MSG_PROGRESS_INDETERMINATE:
- parent.makeProgressDialogIndeterminate();
- break;
- default:
- break;
- }
- }
+ WeakReference<IngestActivity> mParentReference;
+
+ public ItemListHandler(IngestActivity parent) {
+ super();
+ mParentReference = new WeakReference<IngestActivity>(parent);
}
- private ServiceConnection mHelperServiceConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mHelperService = ((IngestService.LocalBinder) service).getService();
- mHelperService.setClientActivity(IngestActivity.this);
- MtpDeviceIndex index = mHelperService.getIndex();
- mAdapter.setMtpDeviceIndex(index);
- if (mPagerAdapter != null) mPagerAdapter.setMtpDeviceIndex(index);
- }
+ @Override
+ public void handleMessage(Message message) {
+ IngestActivity parent = mParentReference.get();
+ if (parent == null || !parent.mActive) {
+ return;
+ }
+ switch (message.what) {
+ case MSG_PROGRESS_HIDE:
+ parent.cleanupProgressDialog();
+ break;
+ case MSG_PROGRESS_UPDATE:
+ parent.updateProgressDialog();
+ break;
+ case MSG_NOTIFY_CHANGED:
+ parent.uiThreadNotifyIndexChanged();
+ break;
+ case MSG_BULK_CHECKED_CHANGE:
+ parent.mPositionMappingCheckBroker.onBulkCheckedChange();
+ break;
+ case MSG_PROGRESS_INDETERMINATE:
+ parent.makeProgressDialogIndeterminate();
+ break;
+ default:
+ break;
+ }
+ }
+ }
- public void onServiceDisconnected(ComponentName className) {
- mHelperService = null;
- }
- };
+ private ServiceConnection mHelperServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mHelperService = ((IngestService.LocalBinder) service).getService();
+ mHelperService.setClientActivity(IngestActivity.this);
+ MtpDeviceIndex index = mHelperService.getIndex();
+ mAdapter.setMtpDeviceIndex(index);
+ if (mPagerAdapter != null) {
+ mPagerAdapter.setMtpDeviceIndex(index);
+ }
+ }
- private void doBindHelperService() {
- bindService(new Intent(getApplicationContext(), IngestService.class),
- mHelperServiceConnection, Context.BIND_AUTO_CREATE);
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mHelperService = null;
}
+ };
- private void doUnbindHelperService() {
- if (mHelperService != null) {
- mHelperService.setClientActivity(null);
- unbindService(mHelperServiceConnection);
- }
+ private void doBindHelperService() {
+ bindService(new Intent(getApplicationContext(), IngestService.class),
+ mHelperServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void doUnbindHelperService() {
+ if (mHelperService != null) {
+ mHelperService.setClientActivity(null);
+ unbindService(mHelperServiceConnection);
}
+ }
}
diff --git a/src/com/android/gallery3d/ingest/IngestService.java b/src/com/android/gallery3d/ingest/IngestService.java
index 9d406b13e..98aa7f5c6 100644
--- a/src/com/android/gallery3d/ingest/IngestService.java
+++ b/src/com/android/gallery3d/ingest/IngestService.java
@@ -16,6 +16,13 @@
package com.android.gallery3d.ingest;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.ImportTask;
+import com.android.gallery3d.ingest.data.IngestObjectInfo;
+import com.android.gallery3d.ingest.data.MtpClient;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex;
+
+import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -25,299 +32,303 @@ import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.mtp.MtpDevice;
import android.mtp.MtpDeviceInfo;
-import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.SparseBooleanArray;
import android.widget.Adapter;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.NotificationIds;
-import com.android.gallery3d.data.MtpClient;
-import com.android.gallery3d.util.BucketNames;
-import com.android.gallery3d.util.UsageStatistics;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+/**
+ * Service for MTP importing tasks.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class IngestService extends Service implements ImportTask.Listener,
- MtpDeviceIndex.ProgressListener, MtpClient.Listener {
+ MtpDeviceIndex.ProgressListener, MtpClient.Listener {
- public class LocalBinder extends Binder {
- IngestService getService() {
- return IngestService.this;
- }
+ /**
+ * Convenience class to allow easy access to the service instance.
+ */
+ public class LocalBinder extends Binder {
+ IngestService getService() {
+ return IngestService.this;
}
+ }
- private static final int PROGRESS_UPDATE_INTERVAL_MS = 180;
+ private static final int PROGRESS_UPDATE_INTERVAL_MS = 180;
- private static MtpClient sClient;
+ private MtpClient mClient;
+ private final IBinder mBinder = new LocalBinder();
+ private ScannerClient mScannerClient;
+ private MtpDevice mDevice;
+ private String mDevicePrettyName;
+ private MtpDeviceIndex mIndex;
+ private IngestActivity mClientActivity;
+ private boolean mRedeliverImportFinish = false;
+ private int mRedeliverImportFinishCount = 0;
+ private Collection<IngestObjectInfo> mRedeliverObjectsNotImported;
+ private boolean mRedeliverNotifyIndexChanged = false;
+ private boolean mRedeliverIndexFinish = false;
+ private NotificationManager mNotificationManager;
+ private NotificationCompat.Builder mNotificationBuilder;
+ private long mLastProgressIndexTime = 0;
+ private boolean mNeedRelaunchNotification = false;
- private final IBinder mBinder = new LocalBinder();
- private ScannerClient mScannerClient;
- private MtpDevice mDevice;
- private String mDevicePrettyName;
- private MtpDeviceIndex mIndex;
- private IngestActivity mClientActivity;
- private boolean mRedeliverImportFinish = false;
- private int mRedeliverImportFinishCount = 0;
- private Collection<MtpObjectInfo> mRedeliverObjectsNotImported;
- private boolean mRedeliverNotifyIndexChanged = false;
- private boolean mRedeliverIndexFinish = false;
- private NotificationManager mNotificationManager;
- private NotificationCompat.Builder mNotificationBuilder;
- private long mLastProgressIndexTime = 0;
- private boolean mNeedRelaunchNotification = false;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mScannerClient = new ScannerClient(this);
- mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- mNotificationBuilder = new NotificationCompat.Builder(this);
- mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_sync) // TODO drawable
- .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, IngestActivity.class), 0));
- mIndex = MtpDeviceIndex.getInstance();
- mIndex.setProgressListener(this);
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mScannerClient = new ScannerClient(this);
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mNotificationBuilder = new NotificationCompat.Builder(this);
+ // TODO(georgescu): Use a better drawable for the notificaton?
+ mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_sync)
+ .setContentIntent(PendingIntent.getActivity(this, 0,
+ new Intent(this, IngestActivity.class), 0));
+ mIndex = MtpDeviceIndex.getInstance();
+ mIndex.setProgressListener(this);
- if (sClient == null) {
- sClient = new MtpClient(getApplicationContext());
- }
- List<MtpDevice> devices = sClient.getDeviceList();
- if (devices.size() > 0) {
- setDevice(devices.get(0));
- }
- sClient.addListener(this);
+ mClient = new MtpClient(getApplicationContext());
+ List<MtpDevice> devices = mClient.getDeviceList();
+ if (!devices.isEmpty()) {
+ setDevice(devices.get(0));
}
+ mClient.addListener(this);
+ }
- @Override
- public void onDestroy() {
- sClient.removeListener(this);
- mIndex.unsetProgressListener(this);
- super.onDestroy();
- }
+ @Override
+ public void onDestroy() {
+ mClient.close();
+ mIndex.unsetProgressListener(this);
+ super.onDestroy();
+ }
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
- private void setDevice(MtpDevice device) {
- if (mDevice == device) return;
- mRedeliverImportFinish = false;
- mRedeliverObjectsNotImported = null;
- mRedeliverNotifyIndexChanged = false;
- mRedeliverIndexFinish = false;
- mDevice = device;
- mIndex.setDevice(mDevice);
- if (mDevice != null) {
- MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo();
- if (deviceInfo == null) {
- setDevice(null);
- return;
- } else {
- mDevicePrettyName = deviceInfo.getModel();
- mNotificationBuilder.setContentTitle(mDevicePrettyName);
- new Thread(mIndex.getIndexRunnable()).start();
- }
- } else {
- mDevicePrettyName = null;
- }
- if (mClientActivity != null) {
- mClientActivity.notifyIndexChanged();
- } else {
- mRedeliverNotifyIndexChanged = true;
- }
+ private void setDevice(MtpDevice device) {
+ if (mDevice == device) {
+ return;
}
-
- protected MtpDeviceIndex getIndex() {
- return mIndex;
+ mRedeliverImportFinish = false;
+ mRedeliverObjectsNotImported = null;
+ mRedeliverNotifyIndexChanged = false;
+ mRedeliverIndexFinish = false;
+ mDevice = device;
+ mIndex.setDevice(mDevice);
+ if (mDevice != null) {
+ MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo();
+ if (deviceInfo == null) {
+ setDevice(null);
+ return;
+ } else {
+ mDevicePrettyName = deviceInfo.getModel();
+ mNotificationBuilder.setContentTitle(mDevicePrettyName);
+ new Thread(mIndex.getIndexRunnable()).start();
+ }
+ } else {
+ mDevicePrettyName = null;
}
+ if (mClientActivity != null) {
+ mClientActivity.notifyIndexChanged();
+ } else {
+ mRedeliverNotifyIndexChanged = true;
+ }
+ }
- protected void setClientActivity(IngestActivity activity) {
- if (mClientActivity == activity) return;
- mClientActivity = activity;
- if (mClientActivity == null) {
- if (mNeedRelaunchNotification) {
- mNotificationBuilder.setProgress(0, 0, false)
- .setContentText(getResources().getText(R.string.ingest_scanning_done));
- mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
- mNotificationBuilder.build());
- }
- return;
- }
- mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_IMPORTING);
- mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING);
- if (mRedeliverImportFinish) {
- mClientActivity.onImportFinish(mRedeliverObjectsNotImported,
- mRedeliverImportFinishCount);
- mRedeliverImportFinish = false;
- mRedeliverObjectsNotImported = null;
- }
- if (mRedeliverNotifyIndexChanged) {
- mClientActivity.notifyIndexChanged();
- mRedeliverNotifyIndexChanged = false;
- }
- if (mRedeliverIndexFinish) {
- mClientActivity.onIndexFinish();
- mRedeliverIndexFinish = false;
- }
- if (mDevice != null) {
- mNeedRelaunchNotification = true;
- }
+ protected MtpDeviceIndex getIndex() {
+ return mIndex;
+ }
+
+ protected void setClientActivity(IngestActivity activity) {
+ if (mClientActivity == activity) {
+ return;
+ }
+ mClientActivity = activity;
+ if (mClientActivity == null) {
+ if (mNeedRelaunchNotification) {
+ mNotificationBuilder.setProgress(0, 0, false)
+ .setContentText(getResources().getText(R.string.ingest_scanning_done));
+ mNotificationManager.notify(R.id.ingest_notification_scanning,
+ mNotificationBuilder.build());
+ }
+ return;
+ }
+ mNotificationManager.cancel(R.id.ingest_notification_importing);
+ mNotificationManager.cancel(R.id.ingest_notification_scanning);
+ if (mRedeliverImportFinish) {
+ mClientActivity.onImportFinish(mRedeliverObjectsNotImported,
+ mRedeliverImportFinishCount);
+ mRedeliverImportFinish = false;
+ mRedeliverObjectsNotImported = null;
+ }
+ if (mRedeliverNotifyIndexChanged) {
+ mClientActivity.notifyIndexChanged();
+ mRedeliverNotifyIndexChanged = false;
}
+ if (mRedeliverIndexFinish) {
+ mClientActivity.onIndexingFinished();
+ mRedeliverIndexFinish = false;
+ }
+ if (mDevice != null) {
+ mNeedRelaunchNotification = true;
+ }
+ }
- protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) {
- List<MtpObjectInfo> importHandles = new ArrayList<MtpObjectInfo>();
- for (int i = 0; i < selected.size(); i++) {
- if (selected.valueAt(i)) {
- Object item = adapter.getItem(selected.keyAt(i));
- if (item instanceof MtpObjectInfo) {
- importHandles.add(((MtpObjectInfo) item));
- }
- }
+ protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) {
+ List<IngestObjectInfo> importHandles = new ArrayList<IngestObjectInfo>();
+ for (int i = 0; i < selected.size(); i++) {
+ if (selected.valueAt(i)) {
+ Object item = adapter.getItem(selected.keyAt(i));
+ if (item instanceof IngestObjectInfo) {
+ importHandles.add(((IngestObjectInfo) item));
}
- ImportTask task = new ImportTask(mDevice, importHandles, BucketNames.IMPORTED, this);
- task.setListener(this);
- mNotificationBuilder.setProgress(0, 0, true)
- .setContentText(getResources().getText(R.string.ingest_importing));
- startForeground(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
- mNotificationBuilder.build());
- new Thread(task).start();
+ }
}
+ ImportTask task = new ImportTask(mDevice, importHandles, mDevicePrettyName, this);
+ task.setListener(this);
+ mNotificationBuilder.setProgress(0, 0, true)
+ .setContentText(getResources().getText(R.string.ingest_importing));
+ startForeground(R.id.ingest_notification_importing,
+ mNotificationBuilder.build());
+ new Thread(task).start();
+ }
- @Override
- public void deviceAdded(MtpDevice device) {
- if (mDevice == null) {
- setDevice(device);
- UsageStatistics.onEvent(UsageStatistics.COMPONENT_IMPORTER,
- "DeviceConnected", null);
- }
+ @Override
+ public void deviceAdded(MtpDevice device) {
+ if (mDevice == null) {
+ setDevice(device);
}
+ }
+
+ @Override
+ public void deviceRemoved(MtpDevice device) {
+ if (device == mDevice) {
+ mNotificationManager.cancel(R.id.ingest_notification_scanning);
+ mNotificationManager.cancel(R.id.ingest_notification_importing);
+ setDevice(null);
+ mNeedRelaunchNotification = false;
- @Override
- public void deviceRemoved(MtpDevice device) {
- if (device == mDevice) {
- setDevice(null);
- mNeedRelaunchNotification = false;
- mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING);
- }
}
+ }
- @Override
- public void onImportProgress(int visitedCount, int totalCount,
- String pathIfSuccessful) {
- if (pathIfSuccessful != null) {
- mScannerClient.scanPath(pathIfSuccessful);
- }
- mNeedRelaunchNotification = false;
- if (mClientActivity != null) {
- mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful);
- }
- mNotificationBuilder.setProgress(totalCount, visitedCount, false)
- .setContentText(getResources().getText(R.string.ingest_importing));
- mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
- mNotificationBuilder.build());
+ @Override
+ public void onImportProgress(int visitedCount, int totalCount,
+ String pathIfSuccessful) {
+ if (pathIfSuccessful != null) {
+ mScannerClient.scanPath(pathIfSuccessful);
}
+ mNeedRelaunchNotification = false;
+ if (mClientActivity != null) {
+ mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful);
+ }
+ mNotificationBuilder.setProgress(totalCount, visitedCount, false)
+ .setContentText(getResources().getText(R.string.ingest_importing));
+ mNotificationManager.notify(R.id.ingest_notification_importing,
+ mNotificationBuilder.build());
+ }
- @Override
- public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported,
- int visitedCount) {
- stopForeground(true);
- mNeedRelaunchNotification = true;
- if (mClientActivity != null) {
- mClientActivity.onImportFinish(objectsNotImported, visitedCount);
- } else {
- mRedeliverImportFinish = true;
- mRedeliverObjectsNotImported = objectsNotImported;
- mRedeliverImportFinishCount = visitedCount;
- mNotificationBuilder.setProgress(0, 0, false)
- .setContentText(getResources().getText(R.string.import_complete));
- mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
- mNotificationBuilder.build());
- }
- UsageStatistics.onEvent(UsageStatistics.COMPONENT_IMPORTER,
- "ImportFinished", null, visitedCount);
+ @Override
+ public void onImportFinish(Collection<IngestObjectInfo> objectsNotImported,
+ int visitedCount) {
+ stopForeground(true);
+ mNeedRelaunchNotification = true;
+ if (mClientActivity != null) {
+ mClientActivity.onImportFinish(objectsNotImported, visitedCount);
+ } else {
+ mRedeliverImportFinish = true;
+ mRedeliverObjectsNotImported = objectsNotImported;
+ mRedeliverImportFinishCount = visitedCount;
+ mNotificationBuilder.setProgress(0, 0, false)
+ .setContentText(getResources().getText(R.string.ingest_import_complete));
+ mNotificationManager.notify(R.id.ingest_notification_importing,
+ mNotificationBuilder.build());
}
+ }
- @Override
- public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
- mNeedRelaunchNotification = false;
- if (mClientActivity != null) {
- mClientActivity.onObjectIndexed(object, numVisited);
- } else {
- // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds
- long currentTime = SystemClock.uptimeMillis();
- if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) {
- mLastProgressIndexTime = currentTime;
- mNotificationBuilder.setProgress(0, numVisited, true)
- .setContentText(getResources().getText(R.string.ingest_scanning));
- mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
- mNotificationBuilder.build());
- }
- }
+ @Override
+ public void onObjectIndexed(IngestObjectInfo object, int numVisited) {
+ mNeedRelaunchNotification = false;
+ if (mClientActivity != null) {
+ mClientActivity.onObjectIndexed(object, numVisited);
+ } else {
+ // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds
+ long currentTime = SystemClock.uptimeMillis();
+ if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) {
+ mLastProgressIndexTime = currentTime;
+ mNotificationBuilder.setProgress(0, numVisited, true)
+ .setContentText(getResources().getText(R.string.ingest_scanning));
+ mNotificationManager.notify(R.id.ingest_notification_scanning,
+ mNotificationBuilder.build());
+ }
}
+ }
- @Override
- public void onSorting() {
- if (mClientActivity != null) mClientActivity.onSorting();
+ @Override
+ public void onSortingStarted() {
+ if (mClientActivity != null) {
+ mClientActivity.onSortingStarted();
}
+ }
- @Override
- public void onIndexFinish() {
- mNeedRelaunchNotification = true;
- if (mClientActivity != null) {
- mClientActivity.onIndexFinish();
- } else {
- mNotificationBuilder.setProgress(0, 0, false)
- .setContentText(getResources().getText(R.string.ingest_scanning_done));
- mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
- mNotificationBuilder.build());
- mRedeliverIndexFinish = true;
- }
+ @Override
+ public void onIndexingFinished() {
+ mNeedRelaunchNotification = true;
+ if (mClientActivity != null) {
+ mClientActivity.onIndexingFinished();
+ } else {
+ mNotificationBuilder.setProgress(0, 0, false)
+ .setContentText(getResources().getText(R.string.ingest_scanning_done));
+ mNotificationManager.notify(R.id.ingest_notification_scanning,
+ mNotificationBuilder.build());
+ mRedeliverIndexFinish = true;
}
+ }
- // Copied from old Gallery3d code
- private static final class ScannerClient implements MediaScannerConnectionClient {
- ArrayList<String> mPaths = new ArrayList<String>();
- MediaScannerConnection mScannerConnection;
- boolean mConnected;
- Object mLock = new Object();
+ // Copied from old Gallery3d code
+ private static final class ScannerClient implements MediaScannerConnectionClient {
+ ArrayList<String> mPaths = new ArrayList<String>();
+ MediaScannerConnection mScannerConnection;
+ boolean mConnected;
+ Object mLock = new Object();
- public ScannerClient(Context context) {
- mScannerConnection = new MediaScannerConnection(context, this);
- }
+ public ScannerClient(Context context) {
+ mScannerConnection = new MediaScannerConnection(context, this);
+ }
- public void scanPath(String path) {
- synchronized (mLock) {
- if (mConnected) {
- mScannerConnection.scanFile(path, null);
- } else {
- mPaths.add(path);
- mScannerConnection.connect();
- }
- }
+ public void scanPath(String path) {
+ synchronized (mLock) {
+ if (mConnected) {
+ mScannerConnection.scanFile(path, null);
+ } else {
+ mPaths.add(path);
+ mScannerConnection.connect();
}
+ }
+ }
- @Override
- public void onMediaScannerConnected() {
- synchronized (mLock) {
- mConnected = true;
- if (!mPaths.isEmpty()) {
- for (String path : mPaths) {
- mScannerConnection.scanFile(path, null);
- }
- mPaths.clear();
- }
- }
+ @Override
+ public void onMediaScannerConnected() {
+ synchronized (mLock) {
+ mConnected = true;
+ if (!mPaths.isEmpty()) {
+ for (String path : mPaths) {
+ mScannerConnection.scanFile(path, null);
+ }
+ mPaths.clear();
}
+ }
+ }
- @Override
- public void onScanCompleted(String path, Uri uri) {
- }
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
}
+ }
}
diff --git a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
deleted file mode 100644
index fed851e0e..000000000
--- a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * Copyright (C) 2013 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.android.gallery3d.ingest;
-
-import android.mtp.MtpConstants;
-import android.mtp.MtpDevice;
-import android.mtp.MtpObjectInfo;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
-/**
- * MTP objects in the index are organized into "buckets," or groupings.
- * At present, these buckets are based on the date an item was created.
- *
- * When the index is created, the buckets are sorted in their natural
- * order, and the items within the buckets sorted by the date they are taken.
- *
- * The index enables the access of items and bucket labels as one unified list.
- * For example, let's say we have the following data in the index:
- * [Bucket A]: [photo 1], [photo 2]
- * [Bucket B]: [photo 3]
- *
- * Then the items can be thought of as being organized as a 5 element list:
- * [Bucket A], [photo 1], [photo 2], [Bucket B], [photo 3]
- *
- * The data can also be accessed in descending order, in which case the list
- * would be a bit different from simply reversing the ascending list, since the
- * bucket labels need to always be at the beginning:
- * [Bucket B], [photo 3], [Bucket A], [photo 2], [photo 1]
- *
- * The index enables all the following operations in constant time, both for
- * ascending and descending views of the data:
- * - get/getAscending/getDescending: get an item at a specified list position
- * - size: get the total number of items (bucket labels and MTP objects)
- * - getFirstPositionForBucketNumber
- * - getBucketNumberForPosition
- * - isFirstInBucket
- *
- * See the comments in buildLookupIndex for implementation notes.
- */
-public class MtpDeviceIndex {
-
- public static final int FORMAT_MOV = 0x300D; // For some reason this is not in MtpConstants
-
- public static final Set<Integer> SUPPORTED_IMAGE_FORMATS;
- public static final Set<Integer> SUPPORTED_VIDEO_FORMATS;
-
- static {
- SUPPORTED_IMAGE_FORMATS = new HashSet<Integer>();
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_JFIF);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_EXIF_JPEG);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_PNG);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_GIF);
- SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_BMP);
-
- SUPPORTED_VIDEO_FORMATS = new HashSet<Integer>();
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_3GP_CONTAINER);
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_AVI);
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MP4_CONTAINER);
- SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MPEG);
- // TODO: add FORMAT_MOV once Media Scanner supports .mov files
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDevice == null) ? 0 : mDevice.getDeviceId());
- result = prime * result + mGeneration;
- return result;
- }
-
- public interface ProgressListener {
- public void onObjectIndexed(MtpObjectInfo object, int numVisited);
-
- public void onSorting();
-
- public void onIndexFinish();
- }
-
- public enum SortOrder {
- Ascending, Descending
- }
-
- private MtpDevice mDevice;
- private int[] mUnifiedLookupIndex;
- private MtpObjectInfo[] mMtpObjects;
- private DateBucket[] mBuckets;
- private int mGeneration = 0;
-
- public enum Progress {
- Uninitialized, Initialized, Pending, Started, Sorting, Finished
- }
-
- private Progress mProgress = Progress.Uninitialized;
- private ProgressListener mProgressListener;
-
- private static final MtpDeviceIndex sInstance = new MtpDeviceIndex();
- private static final MtpObjectTimestampComparator sMtpObjectComparator =
- new MtpObjectTimestampComparator();
-
- public static MtpDeviceIndex getInstance() {
- return sInstance;
- }
-
- private MtpDeviceIndex() {
- }
-
- synchronized public MtpDevice getDevice() {
- return mDevice;
- }
-
- /**
- * Sets the MtpDevice that should be indexed and initializes state, but does
- * not kick off the actual indexing task, which is instead done by using
- * {@link #getIndexRunnable()}
- *
- * @param device The MtpDevice that should be indexed
- */
- synchronized public void setDevice(MtpDevice device) {
- if (device == mDevice) return;
- mDevice = device;
- resetState();
- }
-
- /**
- * Provides a Runnable for the indexing task assuming the state has already
- * been correctly initialized (by calling {@link #setDevice(MtpDevice)}) and
- * has not already been run.
- *
- * @return Runnable for the main indexing task
- */
- synchronized public Runnable getIndexRunnable() {
- if (mProgress != Progress.Initialized) return null;
- mProgress = Progress.Pending;
- return new IndexRunnable(mDevice);
- }
-
- synchronized public boolean indexReady() {
- return mProgress == Progress.Finished;
- }
-
- synchronized public Progress getProgress() {
- return mProgress;
- }
-
- /**
- * @param listener Listener to change to
- * @return Progress at the time the listener was added (useful for
- * configuring initial UI state)
- */
- synchronized public Progress setProgressListener(ProgressListener listener) {
- mProgressListener = listener;
- return mProgress;
- }
-
- /**
- * Make the listener null if it matches the argument
- *
- * @param listener Listener to unset, if currently registered
- */
- synchronized public void unsetProgressListener(ProgressListener listener) {
- if (mProgressListener == listener)
- mProgressListener = null;
- }
-
- /**
- * @return The total number of elements in the index (labels and items)
- */
- public int size() {
- return mProgress == Progress.Finished ? mUnifiedLookupIndex.length : 0;
- }
-
- /**
- * @param position Index of item to fetch, where 0 is the first item in the
- * specified order
- * @param order
- * @return the bucket label or MtpObjectInfo at the specified position and
- * order
- */
- public Object get(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return null;
- if(order == SortOrder.Ascending) {
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
- if (bucket.unifiedStartIndex == position) {
- return bucket.bucket;
- } else {
- return mMtpObjects[bucket.itemsStartIndex + position - 1
- - bucket.unifiedStartIndex];
- }
- } else {
- int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
- if (bucket.unifiedEndIndex == zeroIndex) {
- return bucket.bucket;
- } else {
- return mMtpObjects[bucket.itemsStartIndex + zeroIndex
- - bucket.unifiedStartIndex];
- }
- }
- }
-
- /**
- * @param position Index of item to fetch from a view of the data that doesn't
- * include labels and is in the specified order
- * @return position-th item in specified order, when not including labels
- */
- public MtpObjectInfo getWithoutLabels(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return null;
- if (order == SortOrder.Ascending) {
- return mMtpObjects[position];
- } else {
- return mMtpObjects[mMtpObjects.length - 1 - position];
- }
- }
-
- /**
- * Although this is O(log(number of buckets)), and thus should not be used
- * in hotspots, even if the attached device has items for every day for
- * a five-year timeframe, it would still only take 11 iterations at most,
- * so shouldn't be a huge issue.
- * @param position Index of item to map from a view of the data that doesn't
- * include labels and is in the specified order
- * @param order
- * @return position in a view of the data that does include labels
- */
- public int getPositionFromPositionWithoutLabels(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return -1;
- if (order == SortOrder.Descending) {
- position = mMtpObjects.length - 1 - position;
- }
- int bucketNumber = 0;
- int iMin = 0;
- int iMax = mBuckets.length - 1;
- while (iMax >= iMin) {
- int iMid = (iMax + iMin) / 2;
- if (mBuckets[iMid].itemsStartIndex + mBuckets[iMid].numItems <= position) {
- iMin = iMid + 1;
- } else if (mBuckets[iMid].itemsStartIndex > position) {
- iMax = iMid - 1;
- } else {
- bucketNumber = iMid;
- break;
- }
- }
- if (mBuckets.length == 0 || mUnifiedLookupIndex.length == 0) {
- return -1;
- }
- int mappedPos = mBuckets[bucketNumber].unifiedStartIndex
- + position - mBuckets[bucketNumber].itemsStartIndex;
- if (order == SortOrder.Descending) {
- mappedPos = mUnifiedLookupIndex.length - 1 - mappedPos;
- }
- return mappedPos;
- }
-
- public int getPositionWithoutLabelsFromPosition(int position, SortOrder order) {
- if (mProgress != Progress.Finished) return -1;
- if(order == SortOrder.Ascending) {
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
- if (bucket.unifiedStartIndex == position) position++;
- return bucket.itemsStartIndex + position - 1 - bucket.unifiedStartIndex;
- } else {
- int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
- if (mBuckets.length == 0 || mUnifiedLookupIndex.length == 0) {
- return -1;
- }
- DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
- if (bucket.unifiedEndIndex == zeroIndex) zeroIndex--;
- return mMtpObjects.length - 1 - bucket.itemsStartIndex
- - zeroIndex + bucket.unifiedStartIndex;
- }
- }
-
- /**
- * @return The number of MTP items in the index (without labels)
- */
- public int sizeWithoutLabels() {
- return mProgress == Progress.Finished ? mMtpObjects.length : 0;
- }
-
- public int getFirstPositionForBucketNumber(int bucketNumber, SortOrder order) {
- if (order == SortOrder.Ascending) {
- return mBuckets[bucketNumber].unifiedStartIndex;
- } else {
- return mUnifiedLookupIndex.length - mBuckets[mBuckets.length - 1 - bucketNumber].unifiedEndIndex - 1;
- }
- }
-
- public int getBucketNumberForPosition(int position, SortOrder order) {
- if (order == SortOrder.Ascending) {
- return mUnifiedLookupIndex[position];
- } else {
- return mBuckets.length - 1 - mUnifiedLookupIndex[mUnifiedLookupIndex.length - 1 - position];
- }
- }
-
- public boolean isFirstInBucket(int position, SortOrder order) {
- if (order == SortOrder.Ascending) {
- return mBuckets[mUnifiedLookupIndex[position]].unifiedStartIndex == position;
- } else {
- position = mUnifiedLookupIndex.length - 1 - position;
- return mBuckets[mUnifiedLookupIndex[position]].unifiedEndIndex == position;
- }
- }
-
- private Object[] mCachedReverseBuckets;
-
- public Object[] getBuckets(SortOrder order) {
- if (mBuckets == null) return null;
- if (order == SortOrder.Ascending) {
- return mBuckets;
- } else {
- if (mCachedReverseBuckets == null) {
- computeReversedBuckets();
- }
- return mCachedReverseBuckets;
- }
- }
-
- /*
- * See the comments for buildLookupIndex for notes on the specific fields of
- * this class.
- */
- private class DateBucket implements Comparable<DateBucket> {
- SimpleDate bucket;
- List<MtpObjectInfo> tempElementsList = new ArrayList<MtpObjectInfo>();
- int unifiedStartIndex;
- int unifiedEndIndex;
- int itemsStartIndex;
- int numItems;
-
- public DateBucket(SimpleDate bucket) {
- this.bucket = bucket;
- }
-
- public DateBucket(SimpleDate bucket, MtpObjectInfo firstElement) {
- this(bucket);
- tempElementsList.add(firstElement);
- }
-
- void sortElements(Comparator<MtpObjectInfo> comparator) {
- Collections.sort(tempElementsList, comparator);
- }
-
- @Override
- public String toString() {
- return bucket.toString();
- }
-
- @Override
- public int hashCode() {
- return bucket.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (!(obj instanceof DateBucket)) return false;
- DateBucket other = (DateBucket) obj;
- if (bucket == null) {
- if (other.bucket != null) return false;
- } else if (!bucket.equals(other.bucket)) {
- return false;
- }
- return true;
- }
-
- @Override
- public int compareTo(DateBucket another) {
- return this.bucket.compareTo(another.bucket);
- }
- }
-
- /**
- * Comparator to sort MtpObjectInfo objects by date created.
- */
- private static class MtpObjectTimestampComparator implements Comparator<MtpObjectInfo> {
- @Override
- public int compare(MtpObjectInfo o1, MtpObjectInfo o2) {
- long diff = o1.getDateCreated() - o2.getDateCreated();
- if (diff < 0) {
- return -1;
- } else if (diff == 0) {
- return 0;
- } else {
- return 1;
- }
- }
- }
-
- private void resetState() {
- mGeneration++;
- mUnifiedLookupIndex = null;
- mMtpObjects = null;
- mBuckets = null;
- mCachedReverseBuckets = null;
- mProgress = (mDevice == null) ? Progress.Uninitialized : Progress.Initialized;
- }
-
-
- private class IndexRunnable implements Runnable {
- private int[] mUnifiedLookupIndex;
- private MtpObjectInfo[] mMtpObjects;
- private DateBucket[] mBuckets;
- private Map<SimpleDate, DateBucket> mBucketsTemp;
- private MtpDevice mDevice;
- private int mNumObjects = 0;
-
- private class IndexingException extends Exception {};
-
- public IndexRunnable(MtpDevice device) {
- mDevice = device;
- }
-
- /*
- * Implementation note: this is the way the index supports a lot of its operations in
- * constant time and respecting the need to have bucket names always come before items
- * in that bucket when accessing the list sequentially, both in ascending and descending
- * orders.
- *
- * Let's say the data we have in the index is the following:
- * [Bucket A]: [photo 1], [photo 2]
- * [Bucket B]: [photo 3]
- *
- * In this case, the lookup index array would be
- * [0, 0, 0, 1, 1]
- *
- * Now, whether we access the list in ascending or descending order, we know which bucket
- * to look in (0 corresponds to A and 1 to B), and can return the bucket label as the first
- * item in a bucket as needed. The individual IndexBUckets have a startIndex and endIndex
- * that correspond to indices in this lookup index array, allowing us to calculate the
- * offset of the specific item we want from within a specific bucket.
- */
- private void buildLookupIndex() {
- int numBuckets = mBuckets.length;
- mUnifiedLookupIndex = new int[mNumObjects + numBuckets];
- int currentUnifiedIndexEntry = 0;
- int nextUnifiedEntry;
-
- mMtpObjects = new MtpObjectInfo[mNumObjects];
- int currentItemsEntry = 0;
- for (int i = 0; i < numBuckets; i++) {
- DateBucket bucket = mBuckets[i];
- nextUnifiedEntry = currentUnifiedIndexEntry + bucket.tempElementsList.size() + 1;
- Arrays.fill(mUnifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
- bucket.unifiedStartIndex = currentUnifiedIndexEntry;
- bucket.unifiedEndIndex = nextUnifiedEntry - 1;
- currentUnifiedIndexEntry = nextUnifiedEntry;
-
- bucket.itemsStartIndex = currentItemsEntry;
- bucket.numItems = bucket.tempElementsList.size();
- for (int j = 0; j < bucket.numItems; j++) {
- mMtpObjects[currentItemsEntry] = bucket.tempElementsList.get(j);
- currentItemsEntry++;
- }
- bucket.tempElementsList = null;
- }
- }
-
- private void copyResults() {
- MtpDeviceIndex.this.mUnifiedLookupIndex = mUnifiedLookupIndex;
- MtpDeviceIndex.this.mMtpObjects = mMtpObjects;
- MtpDeviceIndex.this.mBuckets = mBuckets;
- mUnifiedLookupIndex = null;
- mMtpObjects = null;
- mBuckets = null;
- }
-
- @Override
- public void run() {
- try {
- indexDevice();
- } catch (IndexingException e) {
- synchronized (MtpDeviceIndex.this) {
- resetState();
- if (mProgressListener != null) {
- mProgressListener.onIndexFinish();
- }
- }
- }
- }
-
- private void indexDevice() throws IndexingException {
- synchronized (MtpDeviceIndex.this) {
- mProgress = Progress.Started;
- }
- mBucketsTemp = new HashMap<SimpleDate, DateBucket>();
- for (int storageId : mDevice.getStorageIds()) {
- if (mDevice != getDevice()) throw new IndexingException();
- Stack<Integer> pendingDirectories = new Stack<Integer>();
- pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
- while (!pendingDirectories.isEmpty()) {
- if (mDevice != getDevice()) throw new IndexingException();
- int dirHandle = pendingDirectories.pop();
- for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
- MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle);
- if (objectInfo == null) throw new IndexingException();
- int format = objectInfo.getFormat();
- if (format == MtpConstants.FORMAT_ASSOCIATION) {
- pendingDirectories.add(objectHandle);
- } else if (SUPPORTED_IMAGE_FORMATS.contains(format)
- || SUPPORTED_VIDEO_FORMATS.contains(format)) {
- addObject(objectInfo);
- }
- }
- }
- }
- Collection<DateBucket> values = mBucketsTemp.values();
- mBucketsTemp = null;
- mBuckets = values.toArray(new DateBucket[values.size()]);
- values = null;
- synchronized (MtpDeviceIndex.this) {
- mProgress = Progress.Sorting;
- if (mProgressListener != null) {
- mProgressListener.onSorting();
- }
- }
- sortAll();
- buildLookupIndex();
- synchronized (MtpDeviceIndex.this) {
- if (mDevice != getDevice()) throw new IndexingException();
- copyResults();
-
- /*
- * In order for getBuckets to operate in constant time for descending
- * order, we must precompute a reversed array of the buckets, mainly
- * because the android.widget.SectionIndexer interface which adapters
- * that call getBuckets implement depends on section numbers to be
- * ascending relative to the scroll position, so we must have this for
- * descending order or the scrollbar goes crazy.
- */
- computeReversedBuckets();
-
- mProgress = Progress.Finished;
- if (mProgressListener != null) {
- mProgressListener.onIndexFinish();
- }
- }
- }
-
- private SimpleDate mDateInstance = new SimpleDate();
-
- private void addObject(MtpObjectInfo objectInfo) {
- mNumObjects++;
- mDateInstance.setTimestamp(objectInfo.getDateCreated());
- DateBucket bucket = mBucketsTemp.get(mDateInstance);
- if (bucket == null) {
- bucket = new DateBucket(mDateInstance, objectInfo);
- mBucketsTemp.put(mDateInstance, bucket);
- mDateInstance = new SimpleDate(); // only create new date
- // objects when they are used
- return;
- } else {
- bucket.tempElementsList.add(objectInfo);
- }
- if (mProgressListener != null) {
- mProgressListener.onObjectIndexed(objectInfo, mNumObjects);
- }
- }
-
- private void sortAll() {
- Arrays.sort(mBuckets);
- for (DateBucket bucket : mBuckets) {
- bucket.sortElements(sMtpObjectComparator);
- }
- }
-
- }
-
- private void computeReversedBuckets() {
- mCachedReverseBuckets = new Object[mBuckets.length];
- for (int i = 0; i < mCachedReverseBuckets.length; i++) {
- mCachedReverseBuckets[i] = mBuckets[mBuckets.length - 1 - i];
- }
- }
-}
diff --git a/src/com/android/gallery3d/ingest/SimpleDate.java b/src/com/android/gallery3d/ingest/SimpleDate.java
deleted file mode 100644
index 05db2cde2..000000000
--- a/src/com/android/gallery3d/ingest/SimpleDate.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2013 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.android.gallery3d.ingest;
-
-import java.text.DateFormat;
-import java.util.Calendar;
-
-/**
- * Represents a date (year, month, day)
- */
-public class SimpleDate implements Comparable<SimpleDate> {
- public int month; // MM
- public int day; // DD
- public int year; // YYYY
- private long timestamp;
- private String mCachedStringRepresentation;
-
- public SimpleDate() {
- }
-
- public SimpleDate(long timestamp) {
- setTimestamp(timestamp);
- }
-
- private static Calendar sCalendarInstance = Calendar.getInstance();
-
- public void setTimestamp(long timestamp) {
- synchronized (sCalendarInstance) {
- // TODO find a more efficient way to convert a timestamp to a date?
- sCalendarInstance.setTimeInMillis(timestamp);
- this.day = sCalendarInstance.get(Calendar.DATE);
- this.month = sCalendarInstance.get(Calendar.MONTH);
- this.year = sCalendarInstance.get(Calendar.YEAR);
- this.timestamp = timestamp;
- mCachedStringRepresentation = DateFormat.getDateInstance(DateFormat.SHORT).format(timestamp);
- }
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + day;
- result = prime * result + month;
- result = prime * result + year;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (!(obj instanceof SimpleDate))
- return false;
- SimpleDate other = (SimpleDate) obj;
- if (year != other.year)
- return false;
- if (month != other.month)
- return false;
- if (day != other.day)
- return false;
- return true;
- }
-
- @Override
- public int compareTo(SimpleDate other) {
- int yearDiff = this.year - other.getYear();
- if (yearDiff != 0)
- return yearDiff;
- else {
- int monthDiff = this.month - other.getMonth();
- if (monthDiff != 0)
- return monthDiff;
- else
- return this.day - other.getDay();
- }
- }
-
- public int getDay() {
- return day;
- }
-
- public int getMonth() {
- return month;
- }
-
- public int getYear() {
- return year;
- }
-
- @Override
- public String toString() {
- if (mCachedStringRepresentation == null) {
- mCachedStringRepresentation = DateFormat.getDateInstance(DateFormat.SHORT).format(timestamp);
- }
- return mCachedStringRepresentation;
- }
-}
diff --git a/src/com/android/gallery3d/ingest/adapter/CheckBroker.java b/src/com/android/gallery3d/ingest/adapter/CheckBroker.java
index 6783f23c5..dc8723fa6 100644
--- a/src/com/android/gallery3d/ingest/adapter/CheckBroker.java
+++ b/src/com/android/gallery3d/ingest/adapter/CheckBroker.java
@@ -16,41 +16,52 @@
package com.android.gallery3d.ingest.adapter;
+import android.annotation.TargetApi;
+import android.os.Build;
+
import java.util.ArrayList;
import java.util.Collection;
+/**
+ * Helper to keep checked state in sync.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public abstract class CheckBroker {
- private Collection<OnCheckedChangedListener> mListeners =
- new ArrayList<OnCheckedChangedListener>();
+ private Collection<OnCheckedChangedListener> mListeners =
+ new ArrayList<OnCheckedChangedListener>();
- public interface OnCheckedChangedListener {
- public void onCheckedChanged(int position, boolean isChecked);
- public void onBulkCheckedChanged();
- }
+ /**
+ * Listener for item checked state changes.
+ */
+ public interface OnCheckedChangedListener {
+ public void onCheckedChanged(int position, boolean isChecked);
- public abstract void setItemChecked(int position, boolean checked);
+ public void onBulkCheckedChanged();
+ }
- public void onCheckedChange(int position, boolean checked) {
- if (isItemChecked(position) != checked) {
- for (OnCheckedChangedListener l : mListeners) {
- l.onCheckedChanged(position, checked);
- }
- }
+ public abstract void setItemChecked(int position, boolean checked);
+
+ public void onCheckedChange(int position, boolean checked) {
+ if (isItemChecked(position) != checked) {
+ for (OnCheckedChangedListener l : mListeners) {
+ l.onCheckedChanged(position, checked);
+ }
}
+ }
- public void onBulkCheckedChange() {
- for (OnCheckedChangedListener l : mListeners) {
- l.onBulkCheckedChanged();
- }
+ public void onBulkCheckedChange() {
+ for (OnCheckedChangedListener l : mListeners) {
+ l.onBulkCheckedChanged();
}
+ }
- public abstract boolean isItemChecked(int position);
+ public abstract boolean isItemChecked(int position);
- public void registerOnCheckedChangeListener(OnCheckedChangedListener l) {
- mListeners.add(l);
- }
+ public void registerOnCheckedChangeListener(OnCheckedChangedListener l) {
+ mListeners.add(l);
+ }
- public void unregisterOnCheckedChangeListener(OnCheckedChangedListener l) {
- mListeners.remove(l);
- }
+ public void unregisterOnCheckedChangeListener(OnCheckedChangedListener l) {
+ mListeners.remove(l);
+ }
}
diff --git a/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java b/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java
index e8dd69f8c..c3ce59f4e 100644
--- a/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java
+++ b/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java
@@ -16,177 +16,187 @@
package com.android.gallery3d.ingest.adapter;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.IngestObjectInfo;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex.SortOrder;
+import com.android.gallery3d.ingest.data.SimpleDate;
+import com.android.gallery3d.ingest.ui.DateTileView;
+import com.android.gallery3d.ingest.ui.MtpThumbnailTileView;
+
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
-import android.mtp.MtpObjectInfo;
+import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.SectionIndexer;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.MtpDeviceIndex;
-import com.android.gallery3d.ingest.MtpDeviceIndex.SortOrder;
-import com.android.gallery3d.ingest.SimpleDate;
-import com.android.gallery3d.ingest.ui.DateTileView;
-import com.android.gallery3d.ingest.ui.MtpThumbnailTileView;
-
+/**
+ * Adapter for MTP thumbnail grid.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class MtpAdapter extends BaseAdapter implements SectionIndexer {
- public static final int ITEM_TYPE_MEDIA = 0;
- public static final int ITEM_TYPE_BUCKET = 1;
-
- private Context mContext;
- private MtpDeviceIndex mModel;
- private SortOrder mSortOrder = SortOrder.Descending;
- private LayoutInflater mInflater;
- private int mGeneration = 0;
-
- public MtpAdapter(Activity context) {
- super();
- mContext = context;
- mInflater = LayoutInflater.from(context);
- }
-
- public void setMtpDeviceIndex(MtpDeviceIndex index) {
- mModel = index;
- notifyDataSetChanged();
- }
-
- public MtpDeviceIndex getMtpDeviceIndex() {
- return mModel;
- }
-
- @Override
- public void notifyDataSetChanged() {
- mGeneration++;
- super.notifyDataSetChanged();
- }
-
- @Override
- public void notifyDataSetInvalidated() {
- mGeneration++;
- super.notifyDataSetInvalidated();
- }
-
- public boolean deviceConnected() {
- return (mModel != null) && (mModel.getDevice() != null);
- }
-
- public boolean indexReady() {
- return (mModel != null) && mModel.indexReady();
- }
-
- @Override
- public int getCount() {
- return mModel != null ? mModel.size() : 0;
- }
-
- @Override
- public Object getItem(int position) {
- return mModel.get(position, mSortOrder);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return true;
- }
-
- @Override
- public boolean isEnabled(int position) {
- return true;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public int getViewTypeCount() {
- return 2;
- }
-
- @Override
- public int getItemViewType(int position) {
- // If the position is the first in its section, then it corresponds to
- // a title tile, if not it's a media tile
- if (position == getPositionForSection(getSectionForPosition(position))) {
- return ITEM_TYPE_BUCKET;
- } else {
- return ITEM_TYPE_MEDIA;
- }
- }
-
- public boolean itemAtPositionIsBucket(int position) {
- return getItemViewType(position) == ITEM_TYPE_BUCKET;
- }
-
- public boolean itemAtPositionIsMedia(int position) {
- return getItemViewType(position) == ITEM_TYPE_MEDIA;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- int type = getItemViewType(position);
- if (type == ITEM_TYPE_MEDIA) {
- MtpThumbnailTileView imageView;
- if (convertView == null) {
- imageView = (MtpThumbnailTileView) mInflater.inflate(
- R.layout.ingest_thumbnail, parent, false);
- } else {
- imageView = (MtpThumbnailTileView) convertView;
- }
- imageView.setMtpDeviceAndObjectInfo(mModel.getDevice(), (MtpObjectInfo)getItem(position), mGeneration);
- return imageView;
- } else {
- DateTileView dateTile;
- if (convertView == null) {
- dateTile = (DateTileView) mInflater.inflate(
- R.layout.ingest_date_tile, parent, false);
- } else {
- dateTile = (DateTileView) convertView;
- }
- dateTile.setDate((SimpleDate)getItem(position));
- return dateTile;
- }
- }
-
- @Override
- public int getPositionForSection(int section) {
- if (getCount() == 0) {
- return 0;
- }
- int numSections = getSections().length;
- if (section >= numSections) {
- section = numSections - 1;
- }
- return mModel.getFirstPositionForBucketNumber(section, mSortOrder);
- }
-
- @Override
- public int getSectionForPosition(int position) {
- int count = getCount();
- if (count == 0) {
- return 0;
- }
- if (position >= count) {
- position = count - 1;
- }
- return mModel.getBucketNumberForPosition(position, mSortOrder);
- }
-
- @Override
- public Object[] getSections() {
- return getCount() > 0 ? mModel.getBuckets(mSortOrder) : null;
- }
-
- public SortOrder getSortOrder() {
- return mSortOrder;
- }
-
- public int translatePositionWithoutLabels(int position) {
- if (mModel == null) return -1;
- return mModel.getPositionFromPositionWithoutLabels(position, mSortOrder);
- }
+ public static final int ITEM_TYPE_MEDIA = 0;
+ public static final int ITEM_TYPE_BUCKET = 1;
+
+ @SuppressWarnings("unused")
+ private Context mContext;
+ private MtpDeviceIndex mModel;
+ private SortOrder mSortOrder = SortOrder.DESCENDING;
+ private LayoutInflater mInflater;
+ private int mGeneration = 0;
+
+ public MtpAdapter(Activity context) {
+ super();
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void setMtpDeviceIndex(MtpDeviceIndex index) {
+ mModel = index;
+ notifyDataSetChanged();
+ }
+
+ public MtpDeviceIndex getMtpDeviceIndex() {
+ return mModel;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ mGeneration++;
+ super.notifyDataSetChanged();
+ }
+
+ @Override
+ public void notifyDataSetInvalidated() {
+ mGeneration++;
+ super.notifyDataSetInvalidated();
+ }
+
+ public boolean deviceConnected() {
+ return (mModel != null) && mModel.isDeviceConnected();
+ }
+
+ public boolean indexReady() {
+ return (mModel != null) && mModel.isIndexReady();
+ }
+
+ @Override
+ public int getCount() {
+ return mModel != null ? mModel.size() : 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mModel.get(position, mSortOrder);
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ // If the position is the first in its section, then it corresponds to
+ // a title tile, if not it's a media tile
+ if (position == getPositionForSection(getSectionForPosition(position))) {
+ return ITEM_TYPE_BUCKET;
+ } else {
+ return ITEM_TYPE_MEDIA;
+ }
+ }
+
+ public boolean itemAtPositionIsBucket(int position) {
+ return getItemViewType(position) == ITEM_TYPE_BUCKET;
+ }
+
+ public boolean itemAtPositionIsMedia(int position) {
+ return getItemViewType(position) == ITEM_TYPE_MEDIA;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ int type = getItemViewType(position);
+ if (type == ITEM_TYPE_MEDIA) {
+ MtpThumbnailTileView imageView;
+ if (convertView == null) {
+ imageView = (MtpThumbnailTileView) mInflater.inflate(
+ R.layout.ingest_thumbnail, parent, false);
+ } else {
+ imageView = (MtpThumbnailTileView) convertView;
+ }
+ imageView.setMtpDeviceAndObjectInfo(mModel.getDevice(),
+ (IngestObjectInfo) getItem(position), mGeneration);
+ return imageView;
+ } else {
+ DateTileView dateTile;
+ if (convertView == null) {
+ dateTile = (DateTileView) mInflater.inflate(
+ R.layout.ingest_date_tile, parent, false);
+ } else {
+ dateTile = (DateTileView) convertView;
+ }
+ dateTile.setDate((SimpleDate) getItem(position));
+ return dateTile;
+ }
+ }
+
+ @Override
+ public int getPositionForSection(int section) {
+ if (getCount() == 0) {
+ return 0;
+ }
+ int numSections = getSections().length;
+ if (section >= numSections) {
+ section = numSections - 1;
+ }
+ return mModel.getFirstPositionForBucketNumber(section, mSortOrder);
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ int count = getCount();
+ if (count == 0) {
+ return 0;
+ }
+ if (position >= count) {
+ position = count - 1;
+ }
+ return mModel.getBucketNumberForPosition(position, mSortOrder);
+ }
+
+ @Override
+ public Object[] getSections() {
+ return getCount() > 0 ? mModel.getBuckets(mSortOrder) : null;
+ }
+
+ public SortOrder getSortOrder() {
+ return mSortOrder;
+ }
+
+ public int translatePositionWithoutLabels(int position) {
+ if (mModel == null) {
+ return -1;
+ }
+ return mModel.getPositionFromPositionWithoutLabels(position, mSortOrder);
+ }
}
diff --git a/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java b/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java
index 9e7abc01d..9fe650c17 100644
--- a/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java
+++ b/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java
@@ -16,87 +16,95 @@
package com.android.gallery3d.ingest.adapter;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.IngestObjectInfo;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex.SortOrder;
+import com.android.gallery3d.ingest.ui.MtpFullscreenView;
+
+import android.annotation.TargetApi;
import android.content.Context;
-import android.mtp.MtpObjectInfo;
+import android.os.Build;
import android.support.v4.view.PagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.MtpDeviceIndex;
-import com.android.gallery3d.ingest.MtpDeviceIndex.SortOrder;
-import com.android.gallery3d.ingest.ui.MtpFullscreenView;
-
+/**
+ * Adapter for full-screen MTP pager.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class MtpPagerAdapter extends PagerAdapter {
- private LayoutInflater mInflater;
- private int mGeneration = 0;
- private CheckBroker mBroker;
- private MtpDeviceIndex mModel;
- private SortOrder mSortOrder = SortOrder.Descending;
+ private LayoutInflater mInflater;
+ private int mGeneration = 0;
+ private CheckBroker mBroker;
+ private MtpDeviceIndex mModel;
+ private SortOrder mSortOrder = SortOrder.DESCENDING;
- private MtpFullscreenView mReusableView = null;
+ private MtpFullscreenView mReusableView = null;
- public MtpPagerAdapter(Context context, CheckBroker broker) {
- super();
- mInflater = LayoutInflater.from(context);
- mBroker = broker;
- }
+ public MtpPagerAdapter(Context context, CheckBroker broker) {
+ super();
+ mInflater = LayoutInflater.from(context);
+ mBroker = broker;
+ }
- public void setMtpDeviceIndex(MtpDeviceIndex index) {
- mModel = index;
- notifyDataSetChanged();
- }
+ public void setMtpDeviceIndex(MtpDeviceIndex index) {
+ mModel = index;
+ notifyDataSetChanged();
+ }
- @Override
- public int getCount() {
- return mModel != null ? mModel.sizeWithoutLabels() : 0;
- }
+ @Override
+ public int getCount() {
+ return mModel != null ? mModel.sizeWithoutLabels() : 0;
+ }
- @Override
- public void notifyDataSetChanged() {
- mGeneration++;
- super.notifyDataSetChanged();
- }
+ @Override
+ public void notifyDataSetChanged() {
+ mGeneration++;
+ super.notifyDataSetChanged();
+ }
- public int translatePositionWithLabels(int position) {
- if (mModel == null) return -1;
- return mModel.getPositionWithoutLabelsFromPosition(position, mSortOrder);
+ public int translatePositionWithLabels(int position) {
+ if (mModel == null) {
+ return -1;
}
+ return mModel.getPositionWithoutLabelsFromPosition(position, mSortOrder);
+ }
- @Override
- public void finishUpdate(ViewGroup container) {
- mReusableView = null;
- super.finishUpdate(container);
- }
+ @Override
+ public void finishUpdate(ViewGroup container) {
+ mReusableView = null;
+ super.finishUpdate(container);
+ }
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- MtpFullscreenView v = (MtpFullscreenView)object;
- container.removeView(v);
- mBroker.unregisterOnCheckedChangeListener(v);
- mReusableView = v;
- }
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ MtpFullscreenView v = (MtpFullscreenView) object;
+ container.removeView(v);
+ mBroker.unregisterOnCheckedChangeListener(v);
+ mReusableView = v;
+ }
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- MtpFullscreenView v;
- if (mReusableView != null) {
- v = mReusableView;
- mReusableView = null;
- } else {
- v = (MtpFullscreenView) mInflater.inflate(R.layout.ingest_fullsize, container, false);
- }
- MtpObjectInfo i = mModel.getWithoutLabels(position, mSortOrder);
- v.getImageView().setMtpDeviceAndObjectInfo(mModel.getDevice(), i, mGeneration);
- v.setPositionAndBroker(position, mBroker);
- container.addView(v);
- return v;
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ MtpFullscreenView v;
+ if (mReusableView != null) {
+ v = mReusableView;
+ mReusableView = null;
+ } else {
+ v = (MtpFullscreenView) mInflater.inflate(R.layout.ingest_fullsize, container, false);
}
+ IngestObjectInfo i = mModel.getWithoutLabels(position, mSortOrder);
+ v.getImageView().setMtpDeviceAndObjectInfo(mModel.getDevice(), i, mGeneration);
+ v.setPositionAndBroker(position, mBroker);
+ container.addView(v);
+ return v;
+ }
}
diff --git a/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java b/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java
index bbc90f670..c436fa7b4 100644
--- a/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java
+++ b/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java
@@ -16,14 +16,20 @@
package com.android.gallery3d.ingest.data;
+import android.annotation.TargetApi;
import android.graphics.Bitmap;
+import android.os.Build;
+/**
+ * Encapsulates a Bitmap and some additional metadata.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class BitmapWithMetadata {
- public Bitmap bitmap;
- public int rotationDegrees;
+ public Bitmap bitmap;
+ public int rotationDegrees;
- public BitmapWithMetadata(Bitmap bitmap, int rotationDegrees) {
- this.bitmap = bitmap;
- this.rotationDegrees = rotationDegrees;
- }
+ public BitmapWithMetadata(Bitmap bitmap, int rotationDegrees) {
+ this.bitmap = bitmap;
+ this.rotationDegrees = rotationDegrees;
+ }
}
diff --git a/src/com/android/gallery3d/ingest/data/DateBucket.java b/src/com/android/gallery3d/ingest/data/DateBucket.java
new file mode 100644
index 000000000..85eedb3bd
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/DateBucket.java
@@ -0,0 +1,63 @@
+package com.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+/**
+ * Date bucket for {@link MtpDeviceIndex}.
+ * See {@link MtpDeviceIndexRunnable} for implementation notes.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+class DateBucket implements Comparable<DateBucket> {
+ final SimpleDate date;
+ final int unifiedStartIndex;
+ final int unifiedEndIndex;
+ final int itemsStartIndex;
+ final int numItems;
+
+ public DateBucket(SimpleDate date, int unifiedStartIndex, int unifiedEndIndex,
+ int itemsStartIndex, int numItems) {
+ this.date = date;
+ this.unifiedStartIndex = unifiedStartIndex;
+ this.unifiedEndIndex = unifiedEndIndex;
+ this.itemsStartIndex = itemsStartIndex;
+ this.numItems = numItems;
+ }
+
+ @Override
+ public String toString() {
+ return date.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return date.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof DateBucket)) {
+ return false;
+ }
+ DateBucket other = (DateBucket) obj;
+ if (date == null) {
+ if (other.date != null) {
+ return false;
+ }
+ } else if (!date.equals(other.date)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(DateBucket another) {
+ return this.date.compareTo(another.date);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/ingest/data/ImportTask.java b/src/com/android/gallery3d/ingest/data/ImportTask.java
new file mode 100644
index 000000000..ee2a7d0e5
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/ImportTask.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 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.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.mtp.MtpDevice;
+import android.os.Build;
+import android.os.Environment;
+import android.os.PowerManager;
+import android.os.StatFs;
+import android.util.Log;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Task that handles the copying of items from an MTP device.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class ImportTask implements Runnable {
+
+ private static final String TAG = "ImportTask";
+
+ /**
+ * Import progress listener.
+ */
+ public interface Listener {
+ void onImportProgress(int visitedCount, int totalCount, String pathIfSuccessful);
+
+ void onImportFinish(Collection<IngestObjectInfo> objectsNotImported, int visitedCount);
+ }
+
+ private static final String WAKELOCK_LABEL = "Google Photos MTP Import Task";
+
+ private Listener mListener;
+ private String mDestAlbumName;
+ private Collection<IngestObjectInfo> mObjectsToImport;
+ private MtpDevice mDevice;
+ private PowerManager.WakeLock mWakeLock;
+
+ public ImportTask(MtpDevice device, Collection<IngestObjectInfo> objectsToImport,
+ String destAlbumName, Context context) {
+ mDestAlbumName = destAlbumName;
+ mObjectsToImport = objectsToImport;
+ mDevice = device;
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, WAKELOCK_LABEL);
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void run() {
+ mWakeLock.acquire();
+ try {
+ List<IngestObjectInfo> objectsNotImported = new LinkedList<IngestObjectInfo>();
+ int visited = 0;
+ int total = mObjectsToImport.size();
+ mListener.onImportProgress(visited, total, null);
+ File dest = new File(Environment.getExternalStorageDirectory(), mDestAlbumName);
+ dest.mkdirs();
+ for (IngestObjectInfo object : mObjectsToImport) {
+ visited++;
+ String importedPath = null;
+ if (hasSpaceForSize(object.getCompressedSize())) {
+ importedPath = new File(dest, object.getName(mDevice)).getAbsolutePath();
+ if (!mDevice.importFile(object.getObjectHandle(), importedPath)) {
+ importedPath = null;
+ }
+ }
+ if (importedPath == null) {
+ objectsNotImported.add(object);
+ }
+ if (mListener != null) {
+ mListener.onImportProgress(visited, total, importedPath);
+ }
+ }
+ if (mListener != null) {
+ mListener.onImportFinish(objectsNotImported, visited);
+ }
+ } finally {
+ mListener = null;
+ mWakeLock.release();
+ }
+ }
+
+ private static boolean hasSpaceForSize(long size) {
+ String state = Environment.getExternalStorageState();
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ return false;
+ }
+
+ String path = Environment.getExternalStorageDirectory().getPath();
+ try {
+ StatFs stat = new StatFs(path);
+ return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size;
+ } catch (Exception e) {
+ Log.i(TAG, "Fail to access external storage", e);
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/ingest/data/IngestObjectInfo.java b/src/com/android/gallery3d/ingest/data/IngestObjectInfo.java
new file mode 100644
index 000000000..25273838b
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/IngestObjectInfo.java
@@ -0,0 +1,114 @@
+package com.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.os.Build;
+
+/**
+ * Holds the info needed for the in-memory index of MTP objects.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class IngestObjectInfo implements Comparable<IngestObjectInfo> {
+
+ private int mHandle;
+ private long mDateCreated;
+ private int mFormat;
+ private int mCompressedSize;
+
+ public IngestObjectInfo(MtpObjectInfo mtpObjectInfo) {
+ mHandle = mtpObjectInfo.getObjectHandle();
+ mDateCreated = mtpObjectInfo.getDateCreated();
+ mFormat = mtpObjectInfo.getFormat();
+ mCompressedSize = mtpObjectInfo.getCompressedSize();
+ }
+
+ public IngestObjectInfo(int handle, long dateCreated, int format, int compressedSize) {
+ mHandle = handle;
+ mDateCreated = dateCreated;
+ mFormat = format;
+ mCompressedSize = compressedSize;
+ }
+
+ public int getCompressedSize() {
+ return mCompressedSize;
+ }
+
+ public int getFormat() {
+ return mFormat;
+ }
+
+ public long getDateCreated() {
+ return mDateCreated;
+ }
+
+ public int getObjectHandle() {
+ return mHandle;
+ }
+
+ public String getName(MtpDevice device) {
+ if (device != null) {
+ MtpObjectInfo info = device.getObjectInfo(mHandle);
+ if (info != null) {
+ return info.getName();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int compareTo(IngestObjectInfo another) {
+ long diff = getDateCreated() - another.getDateCreated();
+ if (diff < 0) {
+ return -1;
+ } else if (diff == 0) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "IngestObjectInfo [mHandle=" + mHandle + ", mDateCreated=" + mDateCreated
+ + ", mFormat=" + mFormat + ", mCompressedSize=" + mCompressedSize + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mCompressedSize;
+ result = prime * result + (int) (mDateCreated ^ (mDateCreated >>> 32));
+ result = prime * result + mFormat;
+ result = prime * result + mHandle;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof IngestObjectInfo)) {
+ return false;
+ }
+ IngestObjectInfo other = (IngestObjectInfo) obj;
+ if (mCompressedSize != other.mCompressedSize) {
+ return false;
+ }
+ if (mDateCreated != other.mDateCreated) {
+ return false;
+ }
+ if (mFormat != other.mFormat) {
+ return false;
+ }
+ if (mHandle != other.mHandle) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
index c6504a561..3295828f1 100644
--- a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
+++ b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
@@ -16,91 +16,98 @@
package com.android.gallery3d.ingest.data;
+import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.mtp.MtpDevice;
-import android.mtp.MtpObjectInfo;
+import android.os.Build;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.android.gallery3d.data.Exif;
import com.android.photos.data.GalleryBitmapPool;
+/**
+ * Helper class for fetching bitmaps from MTP devices.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class MtpBitmapFetch {
- private static int sMaxSize = 0;
+ private static int sMaxSize = 0;
- public static void recycleThumbnail(Bitmap b) {
- if (b != null) {
- GalleryBitmapPool.getInstance().put(b);
- }
+ public static void recycleThumbnail(Bitmap b) {
+ if (b != null) {
+ GalleryBitmapPool.getInstance().put(b);
}
+ }
- public static Bitmap getThumbnail(MtpDevice device, MtpObjectInfo info) {
- byte[] imageBytes = device.getThumbnail(info.getObjectHandle());
- if (imageBytes == null) {
- return null;
- }
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
- if (o.outWidth == 0 || o.outHeight == 0) {
- return null;
- }
- o.inBitmap = GalleryBitmapPool.getInstance().get(o.outWidth, o.outHeight);
- o.inMutable = true;
- o.inJustDecodeBounds = false;
- o.inSampleSize = 1;
- try {
- return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
- } catch (IllegalArgumentException e) {
- // BitmapFactory throws an exception rather than returning null
- // when image decoding fails and an existing bitmap was supplied
- // for recycling, even if the failure was not caused by the use
- // of that bitmap.
- return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
- }
+ public static Bitmap getThumbnail(MtpDevice device, IngestObjectInfo info) {
+ byte[] imageBytes = device.getThumbnail(info.getObjectHandle());
+ if (imageBytes == null) {
+ return null;
}
-
- public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info) {
- return getFullsize(device, info, sMaxSize);
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+ if (o.outWidth == 0 || o.outHeight == 0) {
+ return null;
}
+ o.inBitmap = GalleryBitmapPool.getInstance().get(o.outWidth, o.outHeight);
+ o.inMutable = true;
+ o.inJustDecodeBounds = false;
+ o.inSampleSize = 1;
+ try {
+ return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+ } catch (IllegalArgumentException e) {
+ // BitmapFactory throws an exception rather than returning null
+ // when image decoding fails and an existing bitmap was supplied
+ // for recycling, even if the failure was not caused by the use
+ // of that bitmap.
+ return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
+ }
+ }
- public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info, int maxSide) {
- byte[] imageBytes = device.getObject(info.getObjectHandle(), info.getCompressedSize());
- if (imageBytes == null) {
- return null;
- }
- Bitmap created;
- if (maxSide > 0) {
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
- int w = o.outWidth;
- int h = o.outHeight;
- int comp = Math.max(h, w);
- int sampleSize = 1;
- while ((comp >> 1) >= maxSide) {
- comp = comp >> 1;
- sampleSize++;
- }
- o.inSampleSize = sampleSize;
- o.inJustDecodeBounds = false;
- created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
- } else {
- created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
- }
- if (created == null) {
- return null;
- }
+ public static BitmapWithMetadata getFullsize(MtpDevice device, IngestObjectInfo info) {
+ return getFullsize(device, info, sMaxSize);
+ }
- return new BitmapWithMetadata(created, Exif.getOrientation(imageBytes));
+ public static BitmapWithMetadata getFullsize(MtpDevice device, IngestObjectInfo info,
+ int maxSide) {
+ byte[] imageBytes = device.getObject(info.getObjectHandle(), info.getCompressedSize());
+ if (imageBytes == null) {
+ return null;
}
-
- public static void configureForContext(Context context) {
- DisplayMetrics metrics = new DisplayMetrics();
- WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- wm.getDefaultDisplay().getMetrics(metrics);
- sMaxSize = Math.max(metrics.heightPixels, metrics.widthPixels);
+ Bitmap created;
+ if (maxSide > 0) {
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+ int w = o.outWidth;
+ int h = o.outHeight;
+ int comp = Math.max(h, w);
+ int sampleSize = 1;
+ while ((comp >> 1) >= maxSide) {
+ comp = comp >> 1;
+ sampleSize++;
+ }
+ o.inSampleSize = sampleSize;
+ o.inJustDecodeBounds = false;
+ created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+ } else {
+ created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
}
+ if (created == null) {
+ return null;
+ }
+
+ int orientation = Exif.getOrientation(imageBytes);
+ return new BitmapWithMetadata(created, orientation);
+ }
+
+ public static void configureForContext(Context context) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metrics);
+ sMaxSize = Math.max(metrics.heightPixels, metrics.widthPixels);
+ }
}
diff --git a/src/com/android/gallery3d/ingest/data/MtpClient.java b/src/com/android/gallery3d/ingest/data/MtpClient.java
new file mode 100644
index 000000000..cc6c9ce07
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/MtpClient.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 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.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.mtp.MtpDevice;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class helps an application manage a list of connected MTP or PTP devices.
+ * It listens for MTP devices being attached and removed from the USB host bus
+ * and notifies the application when the MTP device list changes.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class MtpClient {
+
+ private static final String TAG = "MtpClient";
+
+ private static final String ACTION_USB_PERMISSION =
+ "com.android.gallery3d.ingest.action.USB_PERMISSION";
+
+ private final Context mContext;
+ private final UsbManager mUsbManager;
+ private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
+ // mDevices contains all MtpDevices that have been seen by our client,
+ // so we can inform when the device has been detached.
+ // mDevices is also used for synchronization in this class.
+ private final HashMap<String, MtpDevice> mDevices = new HashMap<String, MtpDevice>();
+ // List of MTP devices we should not try to open for which we are currently
+ // asking for permission to open.
+ private final ArrayList<String> mRequestPermissionDevices = new ArrayList<String>();
+ // List of MTP devices we should not try to open.
+ // We add devices to this list if the user canceled a permission request or we were
+ // unable to open the device.
+ private final ArrayList<String> mIgnoredDevices = new ArrayList<String>();
+
+ private final PendingIntent mPermissionIntent;
+
+ private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ String deviceName = usbDevice.getDeviceName();
+
+ synchronized (mDevices) {
+ MtpDevice mtpDevice = mDevices.get(deviceName);
+
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
+ if (mtpDevice == null) {
+ mtpDevice = openDeviceLocked(usbDevice);
+ }
+ if (mtpDevice != null) {
+ for (Listener listener : mListeners) {
+ listener.deviceAdded(mtpDevice);
+ }
+ }
+ } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
+ if (mtpDevice != null) {
+ mDevices.remove(deviceName);
+ mRequestPermissionDevices.remove(deviceName);
+ mIgnoredDevices.remove(deviceName);
+ for (Listener listener : mListeners) {
+ listener.deviceRemoved(mtpDevice);
+ }
+ }
+ } else if (ACTION_USB_PERMISSION.equals(action)) {
+ mRequestPermissionDevices.remove(deviceName);
+ boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
+ false);
+ Log.d(TAG, "ACTION_USB_PERMISSION: " + permission);
+ if (permission) {
+ if (mtpDevice == null) {
+ mtpDevice = openDeviceLocked(usbDevice);
+ }
+ if (mtpDevice != null) {
+ for (Listener listener : mListeners) {
+ listener.deviceAdded(mtpDevice);
+ }
+ }
+ } else {
+ // so we don't ask for permission again
+ mIgnoredDevices.add(deviceName);
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * An interface for being notified when MTP or PTP devices are attached
+ * or removed. In the current implementation, only PTP devices are supported.
+ */
+ public interface Listener {
+ /**
+ * Called when a new device has been added
+ *
+ * @param device the new device that was added
+ */
+ public void deviceAdded(MtpDevice device);
+
+ /**
+ * Called when a new device has been removed
+ *
+ * @param device the device that was removed
+ */
+ public void deviceRemoved(MtpDevice device);
+ }
+
+ /**
+ * Tests to see if a {@link android.hardware.usb.UsbDevice}
+ * supports the PTP protocol (typically used by digital cameras)
+ *
+ * @param device the device to test
+ * @return true if the device is a PTP device.
+ */
+ public static boolean isCamera(UsbDevice device) {
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
+ intf.getInterfaceSubclass() == 1 &&
+ intf.getInterfaceProtocol() == 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * MtpClient constructor
+ *
+ * @param context the {@link android.content.Context} to use for the MtpClient
+ */
+ public MtpClient(Context context) {
+ mContext = context;
+ mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+ mPermissionIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(ACTION_USB_PERMISSION), 0);
+ IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ filter.addAction(ACTION_USB_PERMISSION);
+ context.registerReceiver(mUsbReceiver, filter);
+ }
+
+ /**
+ * Opens the {@link android.hardware.usb.UsbDevice} for an MTP or PTP
+ * device and return an {@link android.mtp.MtpDevice} for it.
+ *
+ * @param usbDevice the device to open
+ * @return an MtpDevice for the device.
+ */
+ private MtpDevice openDeviceLocked(UsbDevice usbDevice) {
+ String deviceName = usbDevice.getDeviceName();
+
+ // don't try to open devices that we have decided to ignore
+ // or are currently asking permission for
+ if (isCamera(usbDevice) && !mIgnoredDevices.contains(deviceName)
+ && !mRequestPermissionDevices.contains(deviceName)) {
+ if (!mUsbManager.hasPermission(usbDevice)) {
+ mUsbManager.requestPermission(usbDevice, mPermissionIntent);
+ mRequestPermissionDevices.add(deviceName);
+ } else {
+ UsbDeviceConnection connection = mUsbManager.openDevice(usbDevice);
+ if (connection != null) {
+ MtpDevice mtpDevice = new MtpDevice(usbDevice);
+ if (mtpDevice.open(connection)) {
+ mDevices.put(usbDevice.getDeviceName(), mtpDevice);
+ return mtpDevice;
+ } else {
+ // so we don't try to open it again
+ mIgnoredDevices.add(deviceName);
+ }
+ } else {
+ // so we don't try to open it again
+ mIgnoredDevices.add(deviceName);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Closes all resources related to the MtpClient object
+ */
+ public void close() {
+ mContext.unregisterReceiver(mUsbReceiver);
+ }
+
+ /**
+ * Registers a {@link com.android.gallery3d.data.MtpClient.Listener} interface to receive
+ * notifications when MTP or PTP devices are added or removed.
+ *
+ * @param listener the listener to register
+ */
+ public void addListener(Listener listener) {
+ synchronized (mDevices) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Unregisters a {@link com.android.gallery3d.data.MtpClient.Listener} interface.
+ *
+ * @param listener the listener to unregister
+ */
+ public void removeListener(Listener listener) {
+ synchronized (mDevices) {
+ mListeners.remove(listener);
+ }
+ }
+
+
+ /**
+ * Retrieves a list of all currently connected {@link android.mtp.MtpDevice}.
+ *
+ * @return the list of MtpDevices
+ */
+ public List<MtpDevice> getDeviceList() {
+ synchronized (mDevices) {
+ // Query the USB manager since devices might have attached
+ // before we added our listener.
+ for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
+ if (mDevices.get(usbDevice.getDeviceName()) == null) {
+ openDeviceLocked(usbDevice);
+ }
+ }
+
+ return new ArrayList<MtpDevice>(mDevices.values());
+ }
+ }
+
+
+}
diff --git a/src/com/android/gallery3d/ingest/data/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/data/MtpDeviceIndex.java
new file mode 100644
index 000000000..b21ad8355
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/MtpDeviceIndex.java
@@ -0,0 +1,433 @@
+package com.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.mtp.MtpConstants;
+import android.mtp.MtpDevice;
+import android.os.Build;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Index of MTP media objects organized into "buckets," or groupings, based on the date
+ * they were created.
+ *
+ * When the index is created, the buckets are sorted in their natural
+ * order, and the items within the buckets sorted by the date they are taken.
+ *
+ * The index enables the access of items and bucket labels as one unified list.
+ * For example, let's say we have the following data in the index:
+ * [Bucket A]: [photo 1], [photo 2]
+ * [Bucket B]: [photo 3]
+ *
+ * Then the items can be thought of as being organized as a 5 element list:
+ * [Bucket A], [photo 1], [photo 2], [Bucket B], [photo 3]
+ *
+ * The data can also be accessed in descending order, in which case the list
+ * would be a bit different from simply reversing the ascending list, since the
+ * bucket labels need to always be at the beginning:
+ * [Bucket B], [photo 3], [Bucket A], [photo 2], [photo 1]
+ *
+ * The index enables all the following operations in constant time, both for
+ * ascending and descending views of the data:
+ * - get/getAscending/getDescending: get an item at a specified list position
+ * - size: get the total number of items (bucket labels and MTP objects)
+ * - getFirstPositionForBucketNumber
+ * - getBucketNumberForPosition
+ * - isFirstInBucket
+ *
+ * See {@link MtpDeviceIndexRunnable} for implementation notes.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class MtpDeviceIndex {
+
+ /**
+ * Indexing progress listener.
+ */
+ public interface ProgressListener {
+ /**
+ * A media item on the device was indexed.
+ * @param object The media item that was just indexed
+ * @param numVisited Number of items visited so far
+ */
+ public void onObjectIndexed(IngestObjectInfo object, int numVisited);
+
+ /**
+ * The metadata loaded from the device is being sorted.
+ */
+ public void onSortingStarted();
+
+ /**
+ * The indexing is done and the index is ready to be used.
+ */
+ public void onIndexingFinished();
+ }
+
+ /**
+ * Media sort orders.
+ */
+ public enum SortOrder {
+ ASCENDING, DESCENDING
+ }
+
+ /** Quicktime MOV container (not already defined in {@link MtpConstants}) **/
+ public static final int FORMAT_MOV = 0x300D;
+
+ public static final Set<Integer> SUPPORTED_IMAGE_FORMATS;
+ public static final Set<Integer> SUPPORTED_VIDEO_FORMATS;
+
+ static {
+ Set<Integer> supportedImageFormats = new HashSet<Integer>();
+ supportedImageFormats.add(MtpConstants.FORMAT_JFIF);
+ supportedImageFormats.add(MtpConstants.FORMAT_EXIF_JPEG);
+ supportedImageFormats.add(MtpConstants.FORMAT_PNG);
+ supportedImageFormats.add(MtpConstants.FORMAT_GIF);
+ supportedImageFormats.add(MtpConstants.FORMAT_BMP);
+ SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableSet(supportedImageFormats);
+
+ Set<Integer> supportedVideoFormats = new HashSet<Integer>();
+ supportedVideoFormats.add(MtpConstants.FORMAT_3GP_CONTAINER);
+ supportedVideoFormats.add(MtpConstants.FORMAT_AVI);
+ supportedVideoFormats.add(MtpConstants.FORMAT_MP4_CONTAINER);
+ supportedVideoFormats.add(MtpConstants.FORMAT_MPEG);
+ // TODO(georgescu): add FORMAT_MOV once Android Media Scanner supports .mov files
+ SUPPORTED_VIDEO_FORMATS = Collections.unmodifiableSet(supportedVideoFormats);
+ }
+
+ private MtpDevice mDevice;
+ private long mGeneration;
+ private ProgressListener mProgressListener;
+ private volatile MtpDeviceIndexRunnable.Results mResults;
+ private final MtpDeviceIndexRunnable.Factory mIndexRunnableFactory;
+
+ private static final MtpDeviceIndex sInstance = new MtpDeviceIndex(
+ MtpDeviceIndexRunnable.getFactory());
+
+ public static MtpDeviceIndex getInstance() {
+ return sInstance;
+ }
+
+ protected MtpDeviceIndex(MtpDeviceIndexRunnable.Factory indexRunnableFactory) {
+ mIndexRunnableFactory = indexRunnableFactory;
+ }
+
+ public synchronized MtpDevice getDevice() {
+ return mDevice;
+ }
+
+ public synchronized boolean isDeviceConnected() {
+ return (mDevice != null);
+ }
+
+ /**
+ * @param format Media format from {@link MtpConstants}
+ * @return Whether the format is supported by this index.
+ */
+ public boolean isFormatSupported(int format) {
+ return SUPPORTED_IMAGE_FORMATS.contains(format)
+ || SUPPORTED_VIDEO_FORMATS.contains(format);
+ }
+
+ /**
+ * Sets the MtpDevice that should be indexed and initializes state, but does
+ * not kick off the actual indexing task, which is instead done by using
+ * {@link #getIndexRunnable()}
+ *
+ * @param device The MtpDevice that should be indexed
+ */
+ public synchronized void setDevice(MtpDevice device) {
+ if (device == mDevice) {
+ return;
+ }
+ mDevice = device;
+ resetState();
+ }
+
+ /**
+ * Provides a Runnable for the indexing task (assuming the state has already
+ * been correctly initialized by calling {@link #setDevice(MtpDevice)}).
+ *
+ * @return Runnable for the main indexing task
+ */
+ public synchronized Runnable getIndexRunnable() {
+ if (!isDeviceConnected() || mResults != null) {
+ return null;
+ }
+ return mIndexRunnableFactory.createMtpDeviceIndexRunnable(this);
+ }
+
+ /**
+ * @return Whether the index is ready to be used.
+ */
+ public synchronized boolean isIndexReady() {
+ return mResults != null;
+ }
+
+ /**
+ * @param listener
+ * @return Current progress (useful for configuring initial UI state)
+ */
+ public synchronized void setProgressListener(ProgressListener listener) {
+ mProgressListener = listener;
+ }
+
+ /**
+ * Make the listener null if it matches the argument
+ *
+ * @param listener Listener to unset, if currently registered
+ */
+ public synchronized void unsetProgressListener(ProgressListener listener) {
+ if (mProgressListener == listener) {
+ mProgressListener = null;
+ }
+ }
+
+ /**
+ * @return The total number of elements in the index (labels and items)
+ */
+ public int size() {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ return results != null ? results.unifiedLookupIndex.length : 0;
+ }
+
+ /**
+ * @param position Index of item to fetch, where 0 is the first item in the
+ * specified order
+ * @param order
+ * @return the bucket label or IngestObjectInfo at the specified position and
+ * order
+ */
+ public Object get(int position, SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (results == null) {
+ return null;
+ }
+ if (order == SortOrder.ASCENDING) {
+ DateBucket bucket = results.buckets[results.unifiedLookupIndex[position]];
+ if (bucket.unifiedStartIndex == position) {
+ return bucket.date;
+ } else {
+ return results.mtpObjects[bucket.itemsStartIndex + position - 1
+ - bucket.unifiedStartIndex];
+ }
+ } else {
+ int zeroIndex = results.unifiedLookupIndex.length - 1 - position;
+ DateBucket bucket = results.buckets[results.unifiedLookupIndex[zeroIndex]];
+ if (bucket.unifiedEndIndex == zeroIndex) {
+ return bucket.date;
+ } else {
+ return results.mtpObjects[bucket.itemsStartIndex + zeroIndex
+ - bucket.unifiedStartIndex];
+ }
+ }
+ }
+
+ /**
+ * @param position Index of item to fetch from a view of the data that does not
+ * include labels and is in the specified order
+ * @return position-th item in specified order, when not including labels
+ */
+ public IngestObjectInfo getWithoutLabels(int position, SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (results == null) {
+ return null;
+ }
+ if (order == SortOrder.ASCENDING) {
+ return results.mtpObjects[position];
+ } else {
+ return results.mtpObjects[results.mtpObjects.length - 1 - position];
+ }
+ }
+
+ /**
+ * @param position Index of item to map from a view of the data that does not
+ * include labels and is in the specified order
+ * @param order
+ * @return position in a view of the data that does include labels, or -1 if the index isn't
+ * ready
+ */
+ public int getPositionFromPositionWithoutLabels(int position, SortOrder order) {
+ /* Although this is O(log(number of buckets)), and thus should not be used
+ in hotspots, even if the attached device has items for every day for
+ a five-year timeframe, it would still only take 11 iterations at most,
+ so shouldn't be a huge issue. */
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (results == null) {
+ return -1;
+ }
+ if (order == SortOrder.DESCENDING) {
+ position = results.mtpObjects.length - 1 - position;
+ }
+ int bucketNumber = 0;
+ int iMin = 0;
+ int iMax = results.buckets.length - 1;
+ while (iMax >= iMin) {
+ int iMid = (iMax + iMin) / 2;
+ if (results.buckets[iMid].itemsStartIndex + results.buckets[iMid].numItems
+ <= position) {
+ iMin = iMid + 1;
+ } else if (results.buckets[iMid].itemsStartIndex > position) {
+ iMax = iMid - 1;
+ } else {
+ bucketNumber = iMid;
+ break;
+ }
+ }
+ int mappedPos = results.buckets[bucketNumber].unifiedStartIndex + position
+ - results.buckets[bucketNumber].itemsStartIndex + 1;
+ if (order == SortOrder.DESCENDING) {
+ mappedPos = results.unifiedLookupIndex.length - mappedPos;
+ }
+ return mappedPos;
+ }
+
+ /**
+ * @param position Index of item to map from a view of the data that
+ * includes labels and is in the specified order
+ * @param order
+ * @return position in a view of the data that does not include labels, or -1 if the index isn't
+ * ready
+ */
+ public int getPositionWithoutLabelsFromPosition(int position, SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (results == null) {
+ return -1;
+ }
+ if (order == SortOrder.ASCENDING) {
+ DateBucket bucket = results.buckets[results.unifiedLookupIndex[position]];
+ if (bucket.unifiedStartIndex == position) {
+ position++;
+ }
+ return bucket.itemsStartIndex + position - 1 - bucket.unifiedStartIndex;
+ } else {
+ int zeroIndex = results.unifiedLookupIndex.length - 1 - position;
+ DateBucket bucket = results.buckets[results.unifiedLookupIndex[zeroIndex]];
+ if (bucket.unifiedEndIndex == zeroIndex) {
+ zeroIndex--;
+ }
+ return results.mtpObjects.length - 1 - bucket.itemsStartIndex
+ - zeroIndex + bucket.unifiedStartIndex;
+ }
+ }
+
+ /**
+ * @return The number of media items in the index
+ */
+ public int sizeWithoutLabels() {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ return results != null ? results.mtpObjects.length : 0;
+ }
+
+ /**
+ * @param bucketNumber Index of bucket in the specified order
+ * @param order
+ * @return position of bucket's first item in a view of the data that includes labels
+ */
+ public int getFirstPositionForBucketNumber(int bucketNumber, SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (order == SortOrder.ASCENDING) {
+ return results.buckets[bucketNumber].unifiedStartIndex;
+ } else {
+ return results.unifiedLookupIndex.length
+ - results.buckets[results.buckets.length - 1 - bucketNumber].unifiedEndIndex
+ - 1;
+ }
+ }
+
+ /**
+ * @param position Index of item in the view of the data that includes labels and is in
+ * the specified order
+ * @param order
+ * @return Index of the bucket that contains the specified item
+ */
+ public int getBucketNumberForPosition(int position, SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (order == SortOrder.ASCENDING) {
+ return results.unifiedLookupIndex[position];
+ } else {
+ return results.buckets.length - 1
+ - results.unifiedLookupIndex[results.unifiedLookupIndex.length - 1
+ - position];
+ }
+ }
+
+ /**
+ * @param position Index of item in the view of the data that includes labels and is in
+ * the specified order
+ * @param order
+ * @return Whether the specified item is the first item in its bucket
+ */
+ public boolean isFirstInBucket(int position, SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (order == SortOrder.ASCENDING) {
+ return results.buckets[results.unifiedLookupIndex[position]].unifiedStartIndex
+ == position;
+ } else {
+ position = results.unifiedLookupIndex.length - 1 - position;
+ return results.buckets[results.unifiedLookupIndex[position]].unifiedEndIndex
+ == position;
+ }
+ }
+
+ /**
+ * @param order
+ * @return Array of buckets in the specified order
+ */
+ public DateBucket[] getBuckets(SortOrder order) {
+ MtpDeviceIndexRunnable.Results results = mResults;
+ if (results == null) {
+ return null;
+ }
+ return (order == SortOrder.ASCENDING) ? results.buckets : results.reversedBuckets;
+ }
+
+ protected void resetState() {
+ mGeneration++;
+ mResults = null;
+ }
+
+ /**
+ * @param device
+ * @param generation
+ * @return whether the index is at the given generation and the given device is connected
+ */
+ protected boolean isAtGeneration(MtpDevice device, long generation) {
+ return (mGeneration == generation) && (mDevice == device);
+ }
+
+ protected synchronized boolean setIndexingResults(MtpDevice device, long generation,
+ MtpDeviceIndexRunnable.Results results) {
+ if (!isAtGeneration(device, generation)) {
+ return false;
+ }
+ mResults = results;
+ onIndexFinish(true /*successful*/);
+ return true;
+ }
+
+ protected synchronized void onIndexFinish(boolean successful) {
+ if (!successful) {
+ resetState();
+ }
+ if (mProgressListener != null) {
+ mProgressListener.onIndexingFinished();
+ }
+ }
+
+ protected synchronized void onSorting() {
+ if (mProgressListener != null) {
+ mProgressListener.onSortingStarted();
+ }
+ }
+
+ protected synchronized void onObjectIndexed(IngestObjectInfo object, int numVisited) {
+ if (mProgressListener != null) {
+ mProgressListener.onObjectIndexed(object, numVisited);
+ }
+ }
+
+ protected long getGeneration() {
+ return mGeneration;
+ }
+}
diff --git a/src/com/android/gallery3d/ingest/data/MtpDeviceIndexRunnable.java b/src/com/android/gallery3d/ingest/data/MtpDeviceIndexRunnable.java
new file mode 100644
index 000000000..32275898e
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/MtpDeviceIndexRunnable.java
@@ -0,0 +1,186 @@
+package com.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.mtp.MtpConstants;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.Stack;
+import java.util.TreeMap;
+
+/**
+ * Runnable used by the {@link MtpDeviceIndex} to populate its index.
+ *
+ * Implementation note: this is the way the index supports a lot of its operations in
+ * constant time and respecting the need to have bucket names always come before items
+ * in that bucket when accessing the list sequentially, both in ascending and descending
+ * orders.
+ *
+ * Let's say the data we have in the index is the following:
+ * [Bucket A]: [photo 1], [photo 2]
+ * [Bucket B]: [photo 3]
+ *
+ * In this case, the lookup index array would be
+ * [0, 0, 0, 1, 1]
+ *
+ * Now, whether we access the list in ascending or descending order, we know which bucket
+ * to look in (0 corresponds to A and 1 to B), and can return the bucket label as the first
+ * item in a bucket as needed. The individual IndexBUckets have a startIndex and endIndex
+ * that correspond to indices in this lookup index array, allowing us to calculate the
+ * offset of the specific item we want from within a specific bucket.
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class MtpDeviceIndexRunnable implements Runnable {
+
+ /**
+ * MtpDeviceIndexRunnable factory.
+ */
+ public static class Factory {
+ public MtpDeviceIndexRunnable createMtpDeviceIndexRunnable(MtpDeviceIndex index) {
+ return new MtpDeviceIndexRunnable(index);
+ }
+ }
+
+ static class Results {
+ final int[] unifiedLookupIndex;
+ final IngestObjectInfo[] mtpObjects;
+ final DateBucket[] buckets;
+ final DateBucket[] reversedBuckets;
+
+ public Results(
+ int[] unifiedLookupIndex, IngestObjectInfo[] mtpObjects, DateBucket[] buckets) {
+ this.unifiedLookupIndex = unifiedLookupIndex;
+ this.mtpObjects = mtpObjects;
+ this.buckets = buckets;
+ this.reversedBuckets = new DateBucket[buckets.length];
+ for (int i = 0; i < buckets.length; i++) {
+ this.reversedBuckets[i] = buckets[buckets.length - 1 - i];
+ }
+ }
+ }
+
+ private final MtpDevice mDevice;
+ protected final MtpDeviceIndex mIndex;
+ private final long mIndexGeneration;
+
+ private static Factory sDefaultFactory = new Factory();
+
+ public static Factory getFactory() {
+ return sDefaultFactory;
+ }
+
+ /**
+ * Exception thrown when a problem occurred during indexing.
+ */
+ @SuppressWarnings("serial")
+ public class IndexingException extends RuntimeException {}
+
+ MtpDeviceIndexRunnable(MtpDeviceIndex index) {
+ mIndex = index;
+ mDevice = index.getDevice();
+ mIndexGeneration = index.getGeneration();
+ }
+
+ @Override
+ public void run() {
+ try {
+ indexDevice();
+ } catch (IndexingException e) {
+ mIndex.onIndexFinish(false /*successful*/);
+ }
+ }
+
+ private void indexDevice() throws IndexingException {
+ SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp =
+ new TreeMap<SimpleDate, List<IngestObjectInfo>>();
+ int numObjects = addAllObjects(bucketsTemp);
+ mIndex.onSorting();
+ int numBuckets = bucketsTemp.size();
+ DateBucket[] buckets = new DateBucket[numBuckets];
+ IngestObjectInfo[] mtpObjects = new IngestObjectInfo[numObjects];
+ int[] unifiedLookupIndex = new int[numObjects + numBuckets];
+ int currentUnifiedIndexEntry = 0;
+ int currentItemsEntry = 0;
+ int nextUnifiedEntry, unifiedStartIndex, numBucketObjects, unifiedEndIndex, itemsStartIndex;
+
+ int i = 0;
+ for (Map.Entry<SimpleDate, List<IngestObjectInfo>> bucketTemp : bucketsTemp.entrySet()) {
+ List<IngestObjectInfo> objects = bucketTemp.getValue();
+ Collections.sort(objects);
+ numBucketObjects = objects.size();
+
+ nextUnifiedEntry = currentUnifiedIndexEntry + numBucketObjects + 1;
+ Arrays.fill(unifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
+ unifiedStartIndex = currentUnifiedIndexEntry;
+ unifiedEndIndex = nextUnifiedEntry - 1;
+ currentUnifiedIndexEntry = nextUnifiedEntry;
+
+ itemsStartIndex = currentItemsEntry;
+ for (int j = 0; j < numBucketObjects; j++) {
+ mtpObjects[currentItemsEntry] = objects.get(j);
+ currentItemsEntry++;
+ }
+ buckets[i] = new DateBucket(bucketTemp.getKey(), unifiedStartIndex, unifiedEndIndex,
+ itemsStartIndex, numBucketObjects);
+ i++;
+ }
+ if (!mIndex.setIndexingResults(mDevice, mIndexGeneration,
+ new Results(unifiedLookupIndex, mtpObjects, buckets))) {
+ throw new IndexingException();
+ }
+ }
+
+ private SimpleDate mDateInstance = new SimpleDate();
+
+ protected void addObject(IngestObjectInfo objectInfo,
+ SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp, int numObjects) {
+ mDateInstance.setTimestamp(objectInfo.getDateCreated());
+ List<IngestObjectInfo> bucket = bucketsTemp.get(mDateInstance);
+ if (bucket == null) {
+ bucket = new ArrayList<IngestObjectInfo>();
+ bucketsTemp.put(mDateInstance, bucket);
+ mDateInstance = new SimpleDate(); // only create new date objects when they are used
+ }
+ bucket.add(objectInfo);
+ mIndex.onObjectIndexed(objectInfo, numObjects);
+ }
+
+ protected int addAllObjects(SortedMap<SimpleDate, List<IngestObjectInfo>> bucketsTemp)
+ throws IndexingException {
+ int numObjects = 0;
+ for (int storageId : mDevice.getStorageIds()) {
+ if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
+ throw new IndexingException();
+ }
+ Stack<Integer> pendingDirectories = new Stack<Integer>();
+ pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
+ while (!pendingDirectories.isEmpty()) {
+ if (!mIndex.isAtGeneration(mDevice, mIndexGeneration)) {
+ throw new IndexingException();
+ }
+ int dirHandle = pendingDirectories.pop();
+ for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
+ MtpObjectInfo mtpObjectInfo = mDevice.getObjectInfo(objectHandle);
+ if (mtpObjectInfo == null) {
+ throw new IndexingException();
+ }
+ int format = mtpObjectInfo.getFormat();
+ if (format == MtpConstants.FORMAT_ASSOCIATION) {
+ pendingDirectories.add(objectHandle);
+ } else if (mIndex.isFormatSupported(format)) {
+ numObjects++;
+ addObject(new IngestObjectInfo(mtpObjectInfo), bucketsTemp, numObjects);
+ }
+ }
+ }
+ }
+ return numObjects;
+ }
+}
diff --git a/src/com/android/gallery3d/ingest/data/SimpleDate.java b/src/com/android/gallery3d/ingest/data/SimpleDate.java
new file mode 100644
index 000000000..2476f80ed
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/SimpleDate.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2013 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.android.gallery3d.ingest.data;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+
+/**
+ * Represents a date (year, month, day)
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
+public class SimpleDate implements Comparable<SimpleDate> {
+ public int month; // MM
+ public int day; // DD
+ public int year; // YYYY
+ private long timestamp;
+ private String mCachedStringRepresentation;
+
+ public SimpleDate() {
+ }
+
+ public SimpleDate(long timestamp) {
+ setTimestamp(timestamp);
+ }
+
+ private static Calendar sCalendarInstance = Calendar.getInstance();
+
+ public void setTimestamp(long timestamp) {
+ synchronized (sCalendarInstance) {
+ // TODO(georgescu): find a more efficient way to convert a timestamp to a date?
+ sCalendarInstance.setTimeInMillis(timestamp);
+ this.day = sCalendarInstance.get(Calendar.DATE);
+ this.month = sCalendarInstance.get(Calendar.MONTH);
+ this.year = sCalendarInstance.get(Calendar.YEAR);
+ this.timestamp = timestamp;
+ mCachedStringRepresentation =
+ DateFormat.getDateInstance(DateFormat.SHORT).format(timestamp);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + day;
+ result = prime * result + month;
+ result = prime * result + year;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof SimpleDate)) {
+ return false;
+ }
+ SimpleDate other = (SimpleDate) obj;
+ if (year != other.year) {
+ return false;
+ }
+ if (month != other.month) {
+ return false;
+ }
+ if (day != other.day) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(SimpleDate other) {
+ int yearDiff = this.year - other.getYear();
+ if (yearDiff != 0) {
+ return yearDiff;
+ } else {
+ int monthDiff = this.month - other.getMonth();
+ if (monthDiff != 0) {
+ return monthDiff;
+ } else {
+ return this.day - other.getDay();
+ }
+ }
+ }
+
+ public int getDay() {
+ return day;
+ }
+
+ public int getMonth() {
+ return month;
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ @Override
+ public String toString() {
+ if (mCachedStringRepresentation == null) {
+ mCachedStringRepresentation =
+ DateFormat.getDateInstance(DateFormat.SHORT).format(timestamp);
+ }
+ return mCachedStringRepresentation;
+ }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/DateTileView.java b/src/com/android/gallery3d/ingest/ui/DateTileView.java
index 52fe9b85b..cd31e82a9 100644
--- a/src/com/android/gallery3d/ingest/ui/DateTileView.java
+++ b/src/com/android/gallery3d/ingest/ui/DateTileView.java
@@ -16,92 +16,96 @@
package com.android.gallery3d.ingest.ui;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.SimpleDate;
+
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.TextView;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.SimpleDate;
import java.text.DateFormatSymbols;
import java.util.Locale;
+/**
+ * Displays a date in a square tile.
+ */
public class DateTileView extends FrameLayout {
- private static String[] sMonthNames = DateFormatSymbols.getInstance().getShortMonths();
- private static Locale sLocale;
+ private static String[] sMonthNames = DateFormatSymbols.getInstance().getShortMonths();
+ private static Locale sLocale;
- static {
- refreshLocale();
- }
+ static {
+ refreshLocale();
+ }
- public static boolean refreshLocale() {
- Locale currentLocale = Locale.getDefault();
- if (!currentLocale.equals(sLocale)) {
- sLocale = currentLocale;
- sMonthNames = DateFormatSymbols.getInstance(sLocale).getShortMonths();
- return true;
- } else {
- return false;
- }
+ public static boolean refreshLocale() {
+ Locale currentLocale = Locale.getDefault();
+ if (!currentLocale.equals(sLocale)) {
+ sLocale = currentLocale;
+ sMonthNames = DateFormatSymbols.getInstance(sLocale).getShortMonths();
+ return true;
+ } else {
+ return false;
}
+ }
- private TextView mDateTextView;
- private TextView mMonthTextView;
- private TextView mYearTextView;
- private int mMonth = -1;
- private int mYear = -1;
- private int mDate = -1;
- private String[] mMonthNames = sMonthNames;
+ private TextView mDateTextView;
+ private TextView mMonthTextView;
+ private TextView mYearTextView;
+ private int mMonth = -1;
+ private int mYear = -1;
+ private int mDate = -1;
+ private String[] mMonthNames = sMonthNames;
- public DateTileView(Context context) {
- super(context);
- }
+ public DateTileView(Context context) {
+ super(context);
+ }
- public DateTileView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
+ public DateTileView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
- public DateTileView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
+ public DateTileView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Force this to be square
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- }
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Force this to be square
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mDateTextView = (TextView) findViewById(R.id.date_tile_day);
- mMonthTextView = (TextView) findViewById(R.id.date_tile_month);
- mYearTextView = (TextView) findViewById(R.id.date_tile_year);
- }
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDateTextView = (TextView) findViewById(R.id.date_tile_day);
+ mMonthTextView = (TextView) findViewById(R.id.date_tile_month);
+ mYearTextView = (TextView) findViewById(R.id.date_tile_year);
+ }
- public void setDate(SimpleDate date) {
- setDate(date.getDay(), date.getMonth(), date.getYear());
- }
+ public void setDate(SimpleDate date) {
+ setDate(date.getDay(), date.getMonth(), date.getYear());
+ }
- public void setDate(int date, int month, int year) {
- if (date != mDate) {
- mDate = date;
- mDateTextView.setText(mDate > 9 ? Integer.toString(mDate) : "0" + mDate);
- }
- if (mMonthNames != sMonthNames) {
- mMonthNames = sMonthNames;
- if (month == mMonth) {
- mMonthTextView.setText(mMonthNames[mMonth]);
- }
- }
- if (month != mMonth) {
- mMonth = month;
- mMonthTextView.setText(mMonthNames[mMonth]);
- }
- if (year != mYear) {
- mYear = year;
- mYearTextView.setText(Integer.toString(mYear));
- }
+ public void setDate(int date, int month, int year) {
+ if (date != mDate) {
+ mDate = date;
+ mDateTextView.setText(mDate > 9 ? Integer.toString(mDate) : "0" + mDate);
+ }
+ if (mMonthNames != sMonthNames) {
+ mMonthNames = sMonthNames;
+ if (month == mMonth) {
+ mMonthTextView.setText(mMonthNames[mMonth]);
+ }
+ }
+ if (month != mMonth) {
+ mMonth = month;
+ mMonthTextView.setText(mMonthNames[mMonth]);
+ }
+ if (year != mYear) {
+ mYear = year;
+ mYearTextView.setText(Integer.toString(mYear));
}
+ }
}
diff --git a/src/com/android/gallery3d/ingest/ui/IngestGridView.java b/src/com/android/gallery3d/ingest/ui/IngestGridView.java
index c821259fe..7bafa7c4e 100644
--- a/src/com/android/gallery3d/ingest/ui/IngestGridView.java
+++ b/src/com/android/gallery3d/ingest/ui/IngestGridView.java
@@ -21,38 +21,40 @@ import android.util.AttributeSet;
import android.widget.GridView;
/**
- * This just extends GridView with the ability to listen for calls
- * to clearChoices()
+ * Extends GridView with the ability to listen for calls to clearChoices()
*/
public class IngestGridView extends GridView {
- public interface OnClearChoicesListener {
- public void onClearChoices();
- }
+ /**
+ * Listener for all checked choices being cleared.
+ */
+ public interface OnClearChoicesListener {
+ public void onClearChoices();
+ }
- private OnClearChoicesListener mOnClearChoicesListener = null;
+ private OnClearChoicesListener mOnClearChoicesListener = null;
- public IngestGridView(Context context) {
- super(context);
- }
+ public IngestGridView(Context context) {
+ super(context);
+ }
- public IngestGridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
+ public IngestGridView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
- public IngestGridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
+ public IngestGridView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
- public void setOnClearChoicesListener(OnClearChoicesListener l) {
- mOnClearChoicesListener = l;
- }
+ public void setOnClearChoicesListener(OnClearChoicesListener l) {
+ mOnClearChoicesListener = l;
+ }
- @Override
- public void clearChoices() {
- super.clearChoices();
- if (mOnClearChoicesListener != null) {
- mOnClearChoicesListener.onClearChoices();
- }
+ @Override
+ public void clearChoices() {
+ super.clearChoices();
+ if (mOnClearChoicesListener != null) {
+ mOnClearChoicesListener.onClearChoices();
}
+ }
}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java b/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java
index 8d3884dc6..00785dde0 100644
--- a/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java
+++ b/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java
@@ -16,100 +16,106 @@
package com.android.gallery3d.ingest.ui;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.adapter.CheckBroker;
+
import android.content.Context;
import android.util.AttributeSet;
import android.widget.CheckBox;
import android.widget.Checkable;
import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RelativeLayout;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.adapter.CheckBroker;
-
+/**
+ * View for displaying an MTP-image and associated controls full-screen
+ */
public class MtpFullscreenView extends RelativeLayout implements Checkable,
CompoundButton.OnCheckedChangeListener, CheckBroker.OnCheckedChangedListener {
- private MtpImageView mImageView;
- private CheckBox mCheckbox;
- private int mPosition = -1;
- private CheckBroker mBroker;
-
- public MtpFullscreenView(Context context) {
- super(context);
- }
-
- public MtpFullscreenView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public MtpFullscreenView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mImageView = (MtpImageView) findViewById(R.id.ingest_fullsize_image);
- mCheckbox = (CheckBox) findViewById(R.id.ingest_fullsize_image_checkbox);
- mCheckbox.setOnCheckedChangeListener(this);
+ private MtpImageView mImageView;
+ private CheckBox mCheckbox;
+ private int mPosition = -1;
+ private CheckBroker mBroker;
+
+ public MtpFullscreenView(Context context) {
+ super(context);
+ }
+
+ public MtpFullscreenView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MtpFullscreenView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = (MtpImageView) findViewById(R.id.ingest_fullsize_image);
+ mCheckbox = (CheckBox) findViewById(R.id.ingest_fullsize_image_checkbox);
+ mCheckbox.setOnCheckedChangeListener(this);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mCheckbox.isChecked();
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ mCheckbox.setChecked(checked);
+ }
+
+ @Override
+ public void toggle() {
+ mCheckbox.toggle();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ setPositionAndBroker(-1, null);
+ super.onDetachedFromWindow();
+ }
+
+ public MtpImageView getImageView() {
+ return mImageView;
+ }
+
+ public int getPosition() {
+ return mPosition;
+ }
+
+ public void setPositionAndBroker(int position, CheckBroker b) {
+ if (mBroker != null) {
+ mBroker.unregisterOnCheckedChangeListener(this);
}
-
- @Override
- public boolean isChecked() {
- return mCheckbox.isChecked();
- }
-
- @Override
- public void setChecked(boolean checked) {
- mCheckbox.setChecked(checked);
- }
-
- @Override
- public void toggle() {
- mCheckbox.toggle();
- }
-
- @Override
- public void onDetachedFromWindow() {
- setPositionAndBroker(-1, null);
- super.onDetachedFromWindow();
- }
-
- public MtpImageView getImageView() {
- return mImageView;
- }
-
- public int getPosition() {
- return mPosition;
- }
-
- public void setPositionAndBroker(int position, CheckBroker b) {
- if (mBroker != null) {
- mBroker.unregisterOnCheckedChangeListener(this);
- }
- mPosition = position;
- mBroker = b;
- if (mBroker != null) {
- setChecked(mBroker.isItemChecked(position));
- mBroker.registerOnCheckedChangeListener(this);
- }
+ mPosition = position;
+ mBroker = b;
+ if (mBroker != null) {
+ setChecked(mBroker.isItemChecked(position));
+ mBroker.registerOnCheckedChangeListener(this);
}
+ }
- @Override
- public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
- if (mBroker != null) mBroker.setItemChecked(mPosition, isChecked);
+ @Override
+ public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
+ if (mBroker != null) {
+ mBroker.setItemChecked(mPosition, isChecked);
}
+ }
- @Override
- public void onCheckedChanged(int position, boolean isChecked) {
- if (position == mPosition) {
- setChecked(isChecked);
- }
+ @Override
+ public void onCheckedChanged(int position, boolean isChecked) {
+ if (position == mPosition) {
+ setChecked(isChecked);
}
+ }
- @Override
- public void onBulkCheckedChanged() {
- if(mBroker != null) setChecked(mBroker.isItemChecked(mPosition));
+ @Override
+ public void onBulkCheckedChanged() {
+ if (mBroker != null) {
+ setChecked(mBroker.isItemChecked(mPosition));
}
+ }
}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpImageView.java b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
index 80c105126..5362efd70 100644
--- a/src/com/android/gallery3d/ingest/ui/MtpImageView.java
+++ b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
@@ -16,12 +16,17 @@
package com.android.gallery3d.ingest.ui;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.BitmapWithMetadata;
+import com.android.gallery3d.ingest.data.IngestObjectInfo;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+import com.android.gallery3d.ingest.data.MtpDeviceIndex;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.mtp.MtpDevice;
-import android.mtp.MtpObjectInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -29,252 +34,264 @@ import android.os.Message;
import android.util.AttributeSet;
import android.widget.ImageView;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.MtpDeviceIndex;
-import com.android.gallery3d.ingest.data.BitmapWithMetadata;
-import com.android.gallery3d.ingest.data.MtpBitmapFetch;
-
import java.lang.ref.WeakReference;
+/**
+ * View for images from an MTP devices
+ */
public class MtpImageView extends ImageView {
- // We will use the thumbnail for images larger than this threshold
- private static final int MAX_FULLSIZE_PREVIEW_SIZE = 8388608; // 8 megabytes
+ // We will use the thumbnail for images larger than this threshold
+ private static final int MAX_FULLSIZE_PREVIEW_SIZE = 8388608; // 8 megabytes
- private int mObjectHandle;
- private int mGeneration;
+ private int mObjectHandle;
+ private int mGeneration;
- private WeakReference<MtpImageView> mWeakReference = new WeakReference<MtpImageView>(this);
- private Object mFetchLock = new Object();
- private boolean mFetchPending = false;
- private MtpObjectInfo mFetchObjectInfo;
- private MtpDevice mFetchDevice;
- private Object mFetchResult;
- private Drawable mOverlayIcon;
- private boolean mShowOverlayIcon;
+ private WeakReference<MtpImageView> mWeakReference = new WeakReference<MtpImageView>(this);
+ private Object mFetchLock = new Object();
+ private boolean mFetchPending = false;
+ private IngestObjectInfo mFetchObjectInfo;
+ private MtpDevice mFetchDevice;
+ private Object mFetchResult;
+ private Drawable mOverlayIcon;
+ private boolean mShowOverlayIcon;
- private static final FetchImageHandler sFetchHandler = FetchImageHandler.createOnNewThread();
- private static final ShowImageHandler sFetchCompleteHandler = new ShowImageHandler();
+ private static final FetchImageHandler sFetchHandler = FetchImageHandler.createOnNewThread();
+ private static final ShowImageHandler sFetchCompleteHandler = new ShowImageHandler();
- private void init() {
- showPlaceholder();
- }
+ private void init() {
+ showPlaceholder();
+ }
- public MtpImageView(Context context) {
- super(context);
- init();
- }
+ public MtpImageView(Context context) {
+ super(context);
+ init();
+ }
- public MtpImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
+ public MtpImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
- public MtpImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init();
- }
+ public MtpImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
- private void showPlaceholder() {
- setImageResource(android.R.color.transparent);
- }
+ private void showPlaceholder() {
+ setImageResource(android.R.color.transparent);
+ }
- public void setMtpDeviceAndObjectInfo(MtpDevice device, MtpObjectInfo object, int gen) {
- int handle = object.getObjectHandle();
- if (handle == mObjectHandle && gen == mGeneration) {
- return;
- }
- cancelLoadingAndClear();
- showPlaceholder();
- mGeneration = gen;
- mObjectHandle = handle;
- mShowOverlayIcon = MtpDeviceIndex.SUPPORTED_VIDEO_FORMATS.contains(object.getFormat());
- if (mShowOverlayIcon && mOverlayIcon == null) {
- mOverlayIcon = getResources().getDrawable(R.drawable.ic_control_play);
- updateOverlayIconBounds();
- }
- synchronized (mFetchLock) {
- mFetchObjectInfo = object;
- mFetchDevice = device;
- if (mFetchPending) return;
- mFetchPending = true;
- sFetchHandler.sendMessage(
- sFetchHandler.obtainMessage(0, mWeakReference));
- }
+ public void setMtpDeviceAndObjectInfo(MtpDevice device, IngestObjectInfo object, int gen) {
+ int handle = object.getObjectHandle();
+ if (handle == mObjectHandle && gen == mGeneration) {
+ return;
+ }
+ cancelLoadingAndClear();
+ showPlaceholder();
+ mGeneration = gen;
+ mObjectHandle = handle;
+ mShowOverlayIcon = MtpDeviceIndex.SUPPORTED_VIDEO_FORMATS.contains(object.getFormat());
+ if (mShowOverlayIcon && mOverlayIcon == null) {
+ mOverlayIcon = getResources().getDrawable(R.drawable.ic_control_play);
+ updateOverlayIconBounds();
}
+ synchronized (mFetchLock) {
+ mFetchObjectInfo = object;
+ mFetchDevice = device;
+ if (mFetchPending) {
+ return;
+ }
+ mFetchPending = true;
+ sFetchHandler.sendMessage(
+ sFetchHandler.obtainMessage(0, mWeakReference));
+ }
+ }
- protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) {
- if (info.getCompressedSize() <= MAX_FULLSIZE_PREVIEW_SIZE
- && MtpDeviceIndex.SUPPORTED_IMAGE_FORMATS.contains(info.getFormat())) {
- return MtpBitmapFetch.getFullsize(device, info);
- } else {
- return new BitmapWithMetadata(MtpBitmapFetch.getThumbnail(device, info), 0);
- }
+ protected Object fetchMtpImageDataFromDevice(MtpDevice device, IngestObjectInfo info) {
+ if (info.getCompressedSize() <= MAX_FULLSIZE_PREVIEW_SIZE
+ && MtpDeviceIndex.SUPPORTED_IMAGE_FORMATS.contains(info.getFormat())) {
+ return MtpBitmapFetch.getFullsize(device, info);
+ } else {
+ return new BitmapWithMetadata(MtpBitmapFetch.getThumbnail(device, info), 0);
}
+ }
- private float mLastBitmapWidth;
- private float mLastBitmapHeight;
- private int mLastRotationDegrees;
- private Matrix mDrawMatrix = new Matrix();
+ private float mLastBitmapWidth;
+ private float mLastBitmapHeight;
+ private int mLastRotationDegrees;
+ private Matrix mDrawMatrix = new Matrix();
- private void updateDrawMatrix() {
- mDrawMatrix.reset();
- float dwidth;
- float dheight;
- float vheight = getHeight();
- float vwidth = getWidth();
- float scale;
- boolean rotated90 = (mLastRotationDegrees % 180 != 0);
- if (rotated90) {
- dwidth = mLastBitmapHeight;
- dheight = mLastBitmapWidth;
- } else {
- dwidth = mLastBitmapWidth;
- dheight = mLastBitmapHeight;
- }
- if (dwidth <= vwidth && dheight <= vheight) {
- scale = 1.0f;
- } else {
- scale = Math.min(vwidth / dwidth, vheight / dheight);
- }
- mDrawMatrix.setScale(scale, scale);
- if (rotated90) {
- mDrawMatrix.postTranslate(-dheight * scale * 0.5f,
- -dwidth * scale * 0.5f);
- mDrawMatrix.postRotate(mLastRotationDegrees);
- mDrawMatrix.postTranslate(dwidth * scale * 0.5f,
- dheight * scale * 0.5f);
- }
- mDrawMatrix.postTranslate((vwidth - dwidth * scale) * 0.5f,
- (vheight - dheight * scale) * 0.5f);
- if (!rotated90 && mLastRotationDegrees > 0) {
- // rotated by a multiple of 180
- mDrawMatrix.postRotate(mLastRotationDegrees, vwidth / 2, vheight / 2);
- }
- setImageMatrix(mDrawMatrix);
+ private void updateDrawMatrix() {
+ mDrawMatrix.reset();
+ float dwidth;
+ float dheight;
+ float vheight = getHeight();
+ float vwidth = getWidth();
+ float scale;
+ boolean rotated90 = (mLastRotationDegrees % 180 != 0);
+ if (rotated90) {
+ dwidth = mLastBitmapHeight;
+ dheight = mLastBitmapWidth;
+ } else {
+ dwidth = mLastBitmapWidth;
+ dheight = mLastBitmapHeight;
+ }
+ if (dwidth <= vwidth && dheight <= vheight) {
+ scale = 1.0f;
+ } else {
+ scale = Math.min(vwidth / dwidth, vheight / dheight);
+ }
+ mDrawMatrix.setScale(scale, scale);
+ if (rotated90) {
+ mDrawMatrix.postTranslate(-dheight * scale * 0.5f,
+ -dwidth * scale * 0.5f);
+ mDrawMatrix.postRotate(mLastRotationDegrees);
+ mDrawMatrix.postTranslate(dwidth * scale * 0.5f,
+ dheight * scale * 0.5f);
}
+ mDrawMatrix.postTranslate((vwidth - dwidth * scale) * 0.5f,
+ (vheight - dheight * scale) * 0.5f);
+ if (!rotated90 && mLastRotationDegrees > 0) {
+ // rotated by a multiple of 180
+ mDrawMatrix.postRotate(mLastRotationDegrees, vwidth / 2, vheight / 2);
+ }
+ setImageMatrix(mDrawMatrix);
+ }
- private static final int OVERLAY_ICON_SIZE_DENOMINATOR = 4;
+ private static final int OVERLAY_ICON_SIZE_DENOMINATOR = 4;
- private void updateOverlayIconBounds() {
- int iheight = mOverlayIcon.getIntrinsicHeight();
- int iwidth = mOverlayIcon.getIntrinsicWidth();
- int vheight = getHeight();
- int vwidth = getWidth();
- float scale_height = ((float) vheight) / (iheight * OVERLAY_ICON_SIZE_DENOMINATOR);
- float scale_width = ((float) vwidth) / (iwidth * OVERLAY_ICON_SIZE_DENOMINATOR);
- if (scale_height >= 1f && scale_width >= 1f) {
- mOverlayIcon.setBounds((vwidth - iwidth) / 2,
- (vheight - iheight) / 2,
- (vwidth + iwidth) / 2,
- (vheight + iheight) / 2);
- } else {
- float scale = Math.min(scale_height, scale_width);
- mOverlayIcon.setBounds((int) (vwidth - scale * iwidth) / 2,
- (int) (vheight - scale * iheight) / 2,
- (int) (vwidth + scale * iwidth) / 2,
- (int) (vheight + scale * iheight) / 2);
- }
+ private void updateOverlayIconBounds() {
+ int iheight = mOverlayIcon.getIntrinsicHeight();
+ int iwidth = mOverlayIcon.getIntrinsicWidth();
+ int vheight = getHeight();
+ int vwidth = getWidth();
+ float scaleHeight = ((float) vheight) / (iheight * OVERLAY_ICON_SIZE_DENOMINATOR);
+ float scaleWidth = ((float) vwidth) / (iwidth * OVERLAY_ICON_SIZE_DENOMINATOR);
+ if (scaleHeight >= 1f && scaleWidth >= 1f) {
+ mOverlayIcon.setBounds((vwidth - iwidth) / 2,
+ (vheight - iheight) / 2,
+ (vwidth + iwidth) / 2,
+ (vheight + iheight) / 2);
+ } else {
+ float scale = Math.min(scaleHeight, scaleWidth);
+ mOverlayIcon.setBounds((int) (vwidth - scale * iwidth) / 2,
+ (int) (vheight - scale * iheight) / 2,
+ (int) (vwidth + scale * iwidth) / 2,
+ (int) (vheight + scale * iheight) / 2);
}
+ }
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed && getScaleType() == ScaleType.MATRIX) {
- updateDrawMatrix();
- }
- if (mShowOverlayIcon && changed && mOverlayIcon != null) {
- updateOverlayIconBounds();
- }
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed && getScaleType() == ScaleType.MATRIX) {
+ updateDrawMatrix();
}
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mShowOverlayIcon && mOverlayIcon != null) {
- mOverlayIcon.draw(canvas);
- }
+ if (mShowOverlayIcon && changed && mOverlayIcon != null) {
+ updateOverlayIconBounds();
}
+ }
- protected void onMtpImageDataFetchedFromDevice(Object result) {
- BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata)result;
- if (getScaleType() == ScaleType.MATRIX) {
- mLastBitmapHeight = bitmapWithMetadata.bitmap.getHeight();
- mLastBitmapWidth = bitmapWithMetadata.bitmap.getWidth();
- mLastRotationDegrees = bitmapWithMetadata.rotationDegrees;
- updateDrawMatrix();
- } else {
- setRotation(bitmapWithMetadata.rotationDegrees);
- }
- setAlpha(0f);
- setImageBitmap(bitmapWithMetadata.bitmap);
- animate().alpha(1f);
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mShowOverlayIcon && mOverlayIcon != null) {
+ mOverlayIcon.draw(canvas);
}
+ }
- protected void cancelLoadingAndClear() {
- synchronized (mFetchLock) {
- mFetchDevice = null;
- mFetchObjectInfo = null;
- mFetchResult = null;
- }
- animate().cancel();
- setImageResource(android.R.color.transparent);
+ protected void onMtpImageDataFetchedFromDevice(Object result) {
+ BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata) result;
+ if (getScaleType() == ScaleType.MATRIX) {
+ mLastBitmapHeight = bitmapWithMetadata.bitmap.getHeight();
+ mLastBitmapWidth = bitmapWithMetadata.bitmap.getWidth();
+ mLastRotationDegrees = bitmapWithMetadata.rotationDegrees;
+ updateDrawMatrix();
+ } else {
+ setRotation(bitmapWithMetadata.rotationDegrees);
}
+ setAlpha(0f);
+ setImageBitmap(bitmapWithMetadata.bitmap);
+ animate().alpha(1f);
+ }
- @Override
- public void onDetachedFromWindow() {
- cancelLoadingAndClear();
- super.onDetachedFromWindow();
+ protected void cancelLoadingAndClear() {
+ synchronized (mFetchLock) {
+ mFetchDevice = null;
+ mFetchObjectInfo = null;
+ mFetchResult = null;
}
+ animate().cancel();
+ setImageResource(android.R.color.transparent);
+ }
- private static class FetchImageHandler extends Handler {
- public FetchImageHandler(Looper l) {
- super(l);
- }
+ @Override
+ public void onDetachedFromWindow() {
+ cancelLoadingAndClear();
+ super.onDetachedFromWindow();
+ }
- public static FetchImageHandler createOnNewThread() {
- HandlerThread t = new HandlerThread("MtpImageView Fetch");
- t.start();
- return new FetchImageHandler(t.getLooper());
- }
+ private static class FetchImageHandler extends Handler {
+ public FetchImageHandler(Looper l) {
+ super(l);
+ }
- @Override
- public void handleMessage(Message msg) {
- @SuppressWarnings("unchecked")
- MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
- if (parent == null) return;
- MtpObjectInfo objectInfo;
- MtpDevice device;
- synchronized (parent.mFetchLock) {
- parent.mFetchPending = false;
- device = parent.mFetchDevice;
- objectInfo = parent.mFetchObjectInfo;
- }
- if (device == null) return;
- Object result = parent.fetchMtpImageDataFromDevice(device, objectInfo);
- if (result == null) return;
- synchronized (parent.mFetchLock) {
- if (parent.mFetchObjectInfo != objectInfo) return;
- parent.mFetchResult = result;
- parent.mFetchDevice = null;
- parent.mFetchObjectInfo = null;
- sFetchCompleteHandler.sendMessage(
- sFetchCompleteHandler.obtainMessage(0, parent.mWeakReference));
- }
- }
+ public static FetchImageHandler createOnNewThread() {
+ HandlerThread t = new HandlerThread("MtpImageView Fetch");
+ t.start();
+ return new FetchImageHandler(t.getLooper());
}
- private static class ShowImageHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- @SuppressWarnings("unchecked")
- MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
- if (parent == null) return;
- Object result;
- synchronized (parent.mFetchLock) {
- result = parent.mFetchResult;
- }
- if (result == null) return;
- parent.onMtpImageDataFetchedFromDevice(result);
+ @Override
+ public void handleMessage(Message msg) {
+ @SuppressWarnings("unchecked")
+ MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
+ if (parent == null) {
+ return;
+ }
+ IngestObjectInfo objectInfo;
+ MtpDevice device;
+ synchronized (parent.mFetchLock) {
+ parent.mFetchPending = false;
+ device = parent.mFetchDevice;
+ objectInfo = parent.mFetchObjectInfo;
+ }
+ if (device == null) {
+ return;
+ }
+ Object result = parent.fetchMtpImageDataFromDevice(device, objectInfo);
+ if (result == null) {
+ return;
+ }
+ synchronized (parent.mFetchLock) {
+ if (parent.mFetchObjectInfo != objectInfo) {
+ return;
}
+ parent.mFetchResult = result;
+ parent.mFetchDevice = null;
+ parent.mFetchObjectInfo = null;
+ sFetchCompleteHandler.sendMessage(
+ sFetchCompleteHandler.obtainMessage(0, parent.mWeakReference));
+ }
+ }
+ }
+
+ private static class ShowImageHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ @SuppressWarnings("unchecked")
+ MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
+ if (parent == null) {
+ return;
+ }
+ Object result;
+ synchronized (parent.mFetchLock) {
+ result = parent.mFetchResult;
+ }
+ if (result == null) {
+ return;
+ }
+ parent.onMtpImageDataFetchedFromDevice(result);
}
+ }
}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java b/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java
index 3307e78aa..844a75024 100644
--- a/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java
+++ b/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java
@@ -16,91 +16,98 @@
package com.android.gallery3d.ingest.ui;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.IngestObjectInfo;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.mtp.MtpDevice;
-import android.mtp.MtpObjectInfo;
import android.util.AttributeSet;
import android.widget.Checkable;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.data.MtpBitmapFetch;
-
+/**
+ * View for thumbnail images from an MTP device
+ */
public class MtpThumbnailTileView extends MtpImageView implements Checkable {
- private Paint mForegroundPaint;
- private boolean mIsChecked;
- private Bitmap mBitmap;
-
- private void init() {
- mForegroundPaint = new Paint();
- mForegroundPaint.setColor(getResources().getColor(R.color.ingest_highlight_semitransparent));
- }
-
- public MtpThumbnailTileView(Context context) {
- super(context);
- init();
- }
-
- public MtpThumbnailTileView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- public MtpThumbnailTileView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init();
+ private Paint mForegroundPaint;
+ private boolean mIsChecked;
+ private Bitmap mBitmap;
+
+ private void init() {
+ mForegroundPaint = new Paint();
+ mForegroundPaint.setColor(
+ getResources().getColor(R.color.ingest_highlight_semitransparent));
+ }
+
+ public MtpThumbnailTileView(Context context) {
+ super(context);
+ init();
+ }
+
+ public MtpThumbnailTileView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public MtpThumbnailTileView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Force this to be square
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+
+ @Override
+ protected Object fetchMtpImageDataFromDevice(MtpDevice device, IngestObjectInfo info) {
+ return MtpBitmapFetch.getThumbnail(device, info);
+ }
+
+ @Override
+ protected void onMtpImageDataFetchedFromDevice(Object result) {
+ mBitmap = (Bitmap) result;
+ setImageBitmap(mBitmap);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (isChecked()) {
+ canvas.drawRect(canvas.getClipBounds(), mForegroundPaint);
}
-
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Force this to be square
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- }
-
- @Override
- protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) {
- return MtpBitmapFetch.getThumbnail(device, info);
- }
-
- @Override
- protected void onMtpImageDataFetchedFromDevice(Object result) {
- mBitmap = (Bitmap)result;
- setImageBitmap(mBitmap);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (isChecked()) {
- canvas.drawRect(canvas.getClipBounds(), mForegroundPaint);
- }
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mIsChecked;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mIsChecked != checked) {
+ mIsChecked = checked;
+ invalidate();
}
-
- @Override
- public boolean isChecked() {
- return mIsChecked;
- }
-
- @Override
- public void setChecked(boolean checked) {
- mIsChecked = checked;
- }
-
- @Override
- public void toggle() {
- setChecked(!mIsChecked);
- }
-
- @Override
- protected void cancelLoadingAndClear() {
- super.cancelLoadingAndClear();
- if (mBitmap != null) {
- MtpBitmapFetch.recycleThumbnail(mBitmap);
- mBitmap = null;
- }
+ }
+
+ @Override
+ public void toggle() {
+ setChecked(!mIsChecked);
+ }
+
+ @Override
+ protected void cancelLoadingAndClear() {
+ super.cancelLoadingAndClear();
+ if (mBitmap != null) {
+ MtpBitmapFetch.recycleThumbnail(mBitmap);
+ mBitmap = null;
}
+ }
}