OSDN Git Service

Importer: Full-screen viewing, UI refinement, refactoring
authorBobby Georgescu <georgescu@google.com>
Fri, 1 Feb 2013 20:57:14 +0000 (12:57 -0800)
committerBobby Georgescu <georgescu@google.com>
Wed, 13 Feb 2013 01:35:44 +0000 (17:35 -0800)
Bug: 7990333
Bug: 8151814
Bug: 8037411

This CL adds or changes the following things:
 - Full-screen image viewing and UI for switching modes
 - Moved general functionality from MtpThumbnailTileView
   to MtpImageView to allow for reuse in full-screen image
   use-case
 - MtpBitmapCache moved from ui to data package
 - Orientation now respected when set in image metadata
 - Miscellaneous UI consistency issues fixed
 - Miscellaneous instability issues fixed

Change-Id: I5f188b763617b693e32fedc03273d711d604922a

18 files changed:
AndroidManifest.xml
res/layout/ingest_activity_item_list.xml
res/layout/ingest_fullsize.xml [new file with mode: 0644]
res/menu/ingest_menu_item_list_selection.xml
res/values/strings.xml
src/com/android/gallery3d/ingest/IngestActivity.java
src/com/android/gallery3d/ingest/IngestService.java
src/com/android/gallery3d/ingest/MtpDeviceIndex.java
src/com/android/gallery3d/ingest/adapter/CheckBroker.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/adapter/MtpAdapter.java
src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/ui/IngestGridView.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/ui/MtpBitmapCache.java [deleted file]
src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/ui/MtpImageView.java [new file with mode: 0644]
src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java

index 4cb3089..78a6db1 100644 (file)
          media from attached MTP devices, like cameras and camera phones -->
         <activity android:launchMode="singleInstance"
             android:taskAffinity="" android:name="com.android.gallery3d.ingest.IngestActivity"
+            android:configChanges="orientation|screenSize"
             android:label="@string/app_name">
             <intent-filter>
                 <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
index 23a95f6..f0e91e8 100644 (file)
      limitations under the License.
 -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <GridView
+    <com.android.gallery3d.ingest.ui.IngestGridView
         android:id="@+id/ingest_gridview"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:columnWidth="200px"
+        android:columnWidth="120dip"
         android:numColumns="auto_fit"
         android:fastScrollEnabled="true"
         android:background="@android:color/background_dark"
         android:choiceMode="multipleChoiceModal"
         android:stretchMode="columnWidth"  />
 
+    <android.support.v4.view.ViewPager
+        android:id="@+id/ingest_view_pager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/background_dark"
+        android:visibility="invisible" />
+
     <LinearLayout
-        android:id="@+id/ingest_warning_overlay"
+        android:id="@+id/ingest_warning_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_margin="20dip"
         android:gravity="center"
         android:orientation="horizontal"
-        android:visibility="gone" >
+        android:visibility="invisible" >
 
         <ImageView
-            android:id="@+id/ingest_warning_overlay_icon"
+            android:id="@+id/ingest_warning_view_icon"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
             android:src="@android:drawable/ic_dialog_alert" />
 
         <TextView
-            android:id="@+id/ingest_warning_overlay_text"
+            android:id="@+id/ingest_warning_view_text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="10dip"
diff --git a/res/layout/ingest_fullsize.xml b/res/layout/ingest_fullsize.xml
new file mode 100644 (file)
index 0000000..4100ca1
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.gallery3d.ingest.ui.MtpFullscreenView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <com.android.gallery3d.ingest.ui.MtpImageView
+        android:id="@+id/ingest_fullsize_image"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:scaleType="centerInside" />
+
+    <CheckBox
+        android:id="@+id/ingest_fullsize_image_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:text="@string/Import" />
+
+</com.android.gallery3d.ingest.ui.MtpFullscreenView>
\ No newline at end of file
index aaf3262..2f020b6 100644 (file)
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/ingest_switch_view"
+          android:showAsAction="always" />
     <item android:id="@+id/import_items"
           android:showAsAction="always|withText"
           android:title="@string/Import" />
index d6b9815..fae3466 100644 (file)
     <!-- Label for album grid button -->
     <string name="switch_photo_grid">Grid view</string>
 
+    <!-- Label for fullscreen button. [CHAR LIMIT=20] -->
+    <string name="switch_photo_fullscreen">Fullscreen view</string>
+
     <!-- The tilte of a dialog showing trimming in progress. [CHAR LIMIT=20] -->
     <string name="trimming">Trimming</string>
 
index 4e603be..893f595 100644 (file)
@@ -22,11 +22,14 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.res.Configuration;
+import android.database.DataSetObserver;
 import android.mtp.MtpObjectInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.support.v4.view.ViewPager;
 import android.util.SparseBooleanArray;
 import android.view.ActionMode;
 import android.view.Menu;
@@ -36,12 +39,16 @@ import android.view.View;
 import android.widget.AbsListView.MultiChoiceModeListener;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
-import android.widget.GridView;
 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;
@@ -51,14 +58,22 @@ public class IngestActivity extends Activity implements
 
     private IngestService mHelperService;
     private boolean mActive = false;
-    private GridView mGridView;
+    private IngestGridView mGridView;
     private MtpAdapter mAdapter;
     private Handler mHandler;
     private ProgressDialog mProgressDialog;
     private ActionMode mActiveActionMode;
 
-    private View mWarningOverlay;
-    private TextView mWarningOverlayText;
+    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;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -66,18 +81,25 @@ public class IngestActivity extends Activity implements
         doBindHelperService();
 
         setContentView(R.layout.ingest_activity_item_list);
-        mGridView = (GridView) findViewById(R.id.ingest_gridview);
+        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
         public void onItemClick(AdapterView<?> adapterView, View itemView, int position, long arg3) {
+            mLastCheckedPosition = position;
             mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
         }
     };
@@ -124,23 +146,18 @@ public class IngestActivity extends Activity implements
                         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) {
-            switch (item.getItemId()) {
-                case R.id.import_items:
-                    mHelperService.importSelectedItems(
-                            mGridView.getCheckedItemPositions(),
-                            mAdapter);
-                    mode.finish();
-                    return true;
-                default:
-                    return false;
-            }
+            return onOptionsItemSelected(item);
         }
 
         @Override
@@ -149,12 +166,16 @@ public class IngestActivity extends Activity implements
             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
@@ -164,6 +185,34 @@ public class IngestActivity extends Activity implements
         }
     };
 
+    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;
+        }
+    }
+
+    @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;
+    }
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
@@ -175,7 +224,7 @@ public class IngestActivity extends Activity implements
         DateTileView.refreshLocale();
         mActive = true;
         if (mHelperService != null) mHelperService.setClientActivity(this);
-        updateWarningOverlay();
+        updateWarningView();
         super.onResume();
     }
 
@@ -187,31 +236,140 @@ public class IngestActivity extends Activity implements
         super.onPause();
     }
 
-    private void showWarningOverlay(int textResId) {
-        if (mWarningOverlay == null) {
-            mWarningOverlay = findViewById(R.id.ingest_warning_overlay);
-            mWarningOverlayText =
-                    (TextView)mWarningOverlay.findViewById(R.id.ingest_warning_overlay_text);
+    @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);
         }
-        mWarningOverlayText.setText(textResId);
-        mWarningOverlay.setVisibility(View.VISIBLE);
+        mWarningText.setText(textResId);
+        mWarningView.setVisibility(View.VISIBLE);
+        setFullscreenPagerVisibility(false);
         mGridView.setVisibility(View.GONE);
     }
 
-    private void hideWarningOverlay() {
-        if (mWarningOverlay != null) {
-            mWarningOverlay.setVisibility(View.GONE);
-            mGridView.setVisibility(View.VISIBLE);
+    private void hideWarningView() {
+        if (mWarningView != null) {
+            mWarningView.setVisibility(View.GONE);
+            setFullscreenPagerVisibility(false);
         }
     }
 
-    private void updateWarningOverlay() {
+    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;
+        }
+    }
+
+    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);
+        }
+    }
+
+    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 updateWarningView() {
         if (!mAdapter.deviceConnected()) {
-            showWarningOverlay(R.string.ingest_no_device);
+            showWarningView(R.string.ingest_no_device);
         } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
-            showWarningOverlay(R.string.ingest_empty_device);
+            showWarningView(R.string.ingest_empty_device);
         } else {
-            hideWarningOverlay();
+            hideWarningView();
         }
     }
 
@@ -221,7 +379,7 @@ public class IngestActivity extends Activity implements
             mActiveActionMode.finish();
             mActiveActionMode = null;
         }
-        updateWarningOverlay();
+        updateWarningView();
     }
 
     protected void notifyIndexChanged() {
@@ -330,6 +488,7 @@ public class IngestActivity extends Activity implements
         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;
 
         WeakReference<IngestActivity> mParentReference;
 
@@ -352,6 +511,9 @@ public class IngestActivity extends Activity implements
                 case MSG_NOTIFY_CHANGED:
                     parent.UiThreadNotifyIndexChanged();
                     break;
+                case MSG_BULK_CHECKED_CHANGE:
+                    parent.mPositionMappingCheckBroker.onBulkCheckedChange();
+                    break;
                 default:
                     break;
             }
@@ -362,7 +524,9 @@ public class IngestActivity extends Activity implements
         public void onServiceConnected(ComponentName className, IBinder service) {
             mHelperService = ((IngestService.LocalBinder) service).getService();
             mHelperService.setClientActivity(IngestActivity.this);
-            mAdapter.setMtpDeviceIndex(mHelperService.getIndex());
+            MtpDeviceIndex index = mHelperService.getIndex();
+            mAdapter.setMtpDeviceIndex(index);
+            if (mPagerAdapter != null) mPagerAdapter.setMtpDeviceIndex(index);
         }
 
         public void onServiceDisconnected(ComponentName className) {
index 12b056b..5e0ca0b 100644 (file)
@@ -37,7 +37,7 @@ 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.ingest.ui.MtpBitmapCache;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
 import com.android.gallery3d.util.BucketNames;
 
 import java.util.ArrayList;
@@ -66,6 +66,7 @@ public class IngestService extends Service implements ImportTask.Listener,
     private boolean mRedeliverImportFinish = false;
     private Collection<MtpObjectInfo> mRedeliverObjectsNotImported;
     private boolean mRedeliverNotifyIndexChanged = false;
+    private boolean mRedeliverIndexFinish = false;
     private NotificationManager mNotificationManager;
     private NotificationCompat.Builder mNotificationBuilder;
     private long mLastProgressIndexTime = 0;
@@ -108,13 +109,19 @@ public class IngestService extends Service implements ImportTask.Listener,
         mRedeliverImportFinish = false;
         mRedeliverObjectsNotImported = null;
         mRedeliverNotifyIndexChanged = false;
+        mRedeliverIndexFinish = false;
         mDevice = device;
         mIndex.setDevice(mDevice);
         if (mDevice != null) {
             MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo();
-            mDevicePrettyName = deviceInfo.getModel();
-            mNotificationBuilder.setContentTitle(mDevicePrettyName);
-            new Thread(mIndex.getIndexRunnable()).start();
+            if (deviceInfo == null) {
+                setDevice(null);
+                return;
+            } else {
+                mDevicePrettyName = deviceInfo.getModel();
+                mNotificationBuilder.setContentTitle(mDevicePrettyName);
+                new Thread(mIndex.getIndexRunnable()).start();
+            }
         } else {
             mDevicePrettyName = null;
         }
@@ -144,6 +151,10 @@ public class IngestService extends Service implements ImportTask.Listener,
             mClientActivity.notifyIndexChanged();
             mRedeliverNotifyIndexChanged = false;
         }
+        if (mRedeliverIndexFinish) {
+            mClientActivity.onIndexFinish();
+            mRedeliverIndexFinish = false;
+        }
     }
 
     protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) {
@@ -176,8 +187,8 @@ public class IngestService extends Service implements ImportTask.Listener,
     public void deviceRemoved(MtpDevice device) {
         if (device == mDevice) {
             setDevice(null);
+            MtpBitmapFetch.onDeviceDisconnected(device);
         }
-        MtpBitmapCache.onDeviceDisconnected(device);
     }
 
     @Override
@@ -197,6 +208,7 @@ public class IngestService extends Service implements ImportTask.Listener,
 
     @Override
     public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported) {
+        stopForeground(true);
         if (mClientActivity != null) {
             mClientActivity.onImportFinish(objectsNotImported);
         } else {
@@ -207,7 +219,6 @@ public class IngestService extends Service implements ImportTask.Listener,
             mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
                     mNotificationBuilder.build());
         }
-        stopForeground(mClientActivity != null);
     }
 
     @Override
@@ -241,6 +252,7 @@ public class IngestService extends Service implements ImportTask.Listener,
                 .setContentText(getResources().getText(R.string.ingest_scanning_done));
             mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
                     mNotificationBuilder.build());
+            mRedeliverIndexFinish = true;
         }
     }
 
index 28e115a..e873dd1 100644 (file)
@@ -180,55 +180,90 @@ public class MtpDeviceIndex {
      *         order
      */
     public Object get(int position, SortOrder order) {
+        if (mProgress != Progress.Finished) return null;
         if(order == SortOrder.Ascending) {
-            return getAscending(position);
+            DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
+            if (bucket.unifiedStartIndex == position) {
+                return bucket.bucket;
+            } else {
+                return mMtpObjects[bucket.itemsStartIndex + position - 1
+                                   - bucket.unifiedStartIndex];
+            }
         } else {
-            return getDescending(position);
+            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, where 0 is the first item in
-     *            ascending order
-     * @return position-th item in ascending order
+     * @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 Object getAscending(int position) {
+    public MtpObjectInfo getWithoutLabels(int position, SortOrder order) {
         if (mProgress != Progress.Finished) return null;
-        DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
-        if (bucket.unifiedStartIndex == position) {
-            return bucket.bucket;
+        if (order == SortOrder.Ascending) {
+            return mMtpObjects[position];
         } else {
-            return bucket.get(position - 1 - bucket.unifiedStartIndex);
+            return mMtpObjects[mMtpObjects.length - 1 - position];
         }
     }
 
     /**
-     * @param position Index of item to fetch, where 0 is the last item in
-     *            ascending order
-     * @return position-th item in descending 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.
+     * @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 Object getDescending(int position) {
-        if (mProgress != Progress.Finished) return null;
-        int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
-        DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
-        if (bucket.unifiedEndIndex == zeroIndex) {
-            return bucket.bucket;
-        } else {
-            return bucket.get(zeroIndex - bucket.unifiedStartIndex);
+    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;
+            }
         }
+        int mappedPos = mBuckets[bucketNumber].unifiedStartIndex
+                + position - mBuckets[bucketNumber].itemsStartIndex;
+        if (order == SortOrder.Descending) {
+            mappedPos = mUnifiedLookupIndex.length - 1 - mappedPos;
+        }
+        return mappedPos;
     }
 
-    /**
-     * @param position Index of item to fetch from a view of the data that doesn't
-     *            include labels and is in ascending order
-     * @return position-th item in ascending 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];
+    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 {
-            return mMtpObjects[mMtpObjects.length - 1 - position];
+            int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
+            DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
+            if (bucket.unifiedEndIndex == zeroIndex) zeroIndex--;
+            return mMtpObjects.length - 1 - bucket.itemsStartIndex
+                    - zeroIndex + bucket.unifiedStartIndex;
         }
     }
 
@@ -288,6 +323,7 @@ public class MtpDeviceIndex {
         int unifiedStartIndex;
         int unifiedEndIndex;
         int itemsStartIndex;
+        int numItems;
 
         public DateBucket(SimpleDate bucket) {
             this.bucket = bucket;
@@ -302,10 +338,6 @@ public class MtpDeviceIndex {
             Collections.sort(tempElementsList, comparator);
         }
 
-        public MtpObjectInfo get(int position) {
-            return mMtpObjects[itemsStartIndex + position];
-        }
-
         @Override
         public String toString() {
             return bucket.toString();
@@ -413,7 +445,8 @@ public class MtpDeviceIndex {
                 currentUnifiedIndexEntry = nextUnifiedEntry;
 
                 bucket.itemsStartIndex = currentItemsEntry;
-                for (int j = 0; j < bucket.tempElementsList.size(); j++) {
+                bucket.numItems = bucket.tempElementsList.size();
+                for (int j = 0; j < bucket.numItems; j++) {
                     mMtpObjects[currentItemsEntry] = bucket.tempElementsList.get(j);
                     currentItemsEntry++;
                 }
diff --git a/src/com/android/gallery3d/ingest/adapter/CheckBroker.java b/src/com/android/gallery3d/ingest/adapter/CheckBroker.java
new file mode 100644 (file)
index 0000000..6783f23
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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.adapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public abstract class CheckBroker {
+    private Collection<OnCheckedChangedListener> mListeners =
+            new ArrayList<OnCheckedChangedListener>();
+
+    public interface OnCheckedChangedListener {
+        public void onCheckedChanged(int position, boolean isChecked);
+        public void onBulkCheckedChanged();
+    }
+
+    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 abstract boolean isItemChecked(int position);
+
+    public void registerOnCheckedChangeListener(OnCheckedChangedListener l) {
+        mListeners.add(l);
+    }
+
+    public void unregisterOnCheckedChangeListener(OnCheckedChangedListener l) {
+        mListeners.remove(l);
+    }
+}
index 611d880..e8dd69f 100644 (file)
@@ -45,8 +45,7 @@ public class MtpAdapter extends BaseAdapter implements SectionIndexer {
     public MtpAdapter(Activity context) {
         super();
         mContext = context;
-        mInflater = (LayoutInflater)context.getSystemService
-                (Context.LAYOUT_INFLATER_SERVICE);
+        mInflater = LayoutInflater.from(context);
     }
 
     public void setMtpDeviceIndex(MtpDeviceIndex index) {
@@ -54,6 +53,10 @@ public class MtpAdapter extends BaseAdapter implements SectionIndexer {
         notifyDataSetChanged();
     }
 
+    public MtpDeviceIndex getMtpDeviceIndex() {
+        return mModel;
+    }
+
     @Override
     public void notifyDataSetChanged() {
         mGeneration++;
@@ -177,4 +180,13 @@ public class MtpAdapter extends BaseAdapter implements SectionIndexer {
     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
new file mode 100644 (file)
index 0000000..9e7abc0
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.adapter;
+
+import android.content.Context;
+import android.mtp.MtpObjectInfo;
+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;
+
+public class MtpPagerAdapter extends PagerAdapter {
+
+    private LayoutInflater mInflater;
+    private int mGeneration = 0;
+    private CheckBroker mBroker;
+    private MtpDeviceIndex mModel;
+    private SortOrder mSortOrder = SortOrder.Descending;
+
+    private MtpFullscreenView mReusableView = null;
+
+    public MtpPagerAdapter(Context context, CheckBroker broker) {
+        super();
+        mInflater = LayoutInflater.from(context);
+        mBroker = broker;
+    }
+
+    public void setMtpDeviceIndex(MtpDeviceIndex index) {
+        mModel = index;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getCount() {
+        return mModel != null ? mModel.sizeWithoutLabels() : 0;
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        mGeneration++;
+        super.notifyDataSetChanged();
+    }
+
+    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 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 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;
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java b/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java
new file mode 100644 (file)
index 0000000..bbc90f6
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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.graphics.Bitmap;
+
+public class BitmapWithMetadata {
+    public Bitmap bitmap;
+    public int rotationDegrees;
+
+    public BitmapWithMetadata(Bitmap bitmap, int rotationDegrees) {
+        this.bitmap = bitmap;
+        this.rotationDegrees = rotationDegrees;
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
new file mode 100644 (file)
index 0000000..88645e8
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import com.android.camera.Exif;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BitmapPool;
+
+import java.util.ArrayList;
+
+public class MtpBitmapFetch {
+    private static final int BITMAP_POOL_SIZE = 32;
+    private static BitmapPool sThumbnailPool = new BitmapPool(BITMAP_POOL_SIZE);
+    private static int sMaxSize = 0;
+
+    public static void recycleThumbnail(Bitmap b) {
+        if (b != null) {
+            sThumbnailPool.recycle(b);
+        }
+    }
+
+    public static Bitmap getThumbnail(MtpDevice device, MtpObjectInfo info) {
+        byte[] imageBytes = device.getThumbnail(info.getObjectHandle());
+        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 = sThumbnailPool.getBitmap(o.outWidth, o.outHeight);
+        o.inMutable = true;
+        o.inJustDecodeBounds = false;
+        o.inSampleSize = 1;
+        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+    }
+
+    public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info) {
+        return getFullsize(device, info, sMaxSize);
+    }
+
+    public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info, int maxSide) {
+        byte[] imageBytes = device.getObject(info.getObjectHandle(), info.getCompressedSize());
+        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;
+
+        return new BitmapWithMetadata(created, Exif.getOrientation(imageBytes));
+    }
+
+    public static void onDeviceDisconnected(MtpDevice device) {
+        sThumbnailPool.clear();
+    }
+
+    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/ui/IngestGridView.java b/src/com/android/gallery3d/ingest/ui/IngestGridView.java
new file mode 100644 (file)
index 0000000..c821259
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.GridView;
+
+/**
+ * This just extends GridView with the ability to listen for calls
+ * to clearChoices()
+ */
+public class IngestGridView extends GridView {
+
+    public interface OnClearChoicesListener {
+        public void onClearChoices();
+    }
+
+    private OnClearChoicesListener mOnClearChoicesListener = null;
+
+    public IngestGridView(Context context) {
+        super(context);
+    }
+
+    public IngestGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public IngestGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setOnClearChoicesListener(OnClearChoicesListener l) {
+        mOnClearChoicesListener = l;
+    }
+
+    @Override
+    public void clearChoices() {
+        super.clearChoices();
+        if (mOnClearChoicesListener != null) {
+            mOnClearChoicesListener.onClearChoices();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpBitmapCache.java b/src/com/android/gallery3d/ingest/ui/MtpBitmapCache.java
deleted file mode 100644 (file)
index 307531d..0000000
+++ /dev/null
@@ -1,73 +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.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.mtp.MtpDevice;
-import android.util.LruCache;
-
-public class MtpBitmapCache extends LruCache<Integer, Bitmap> {
-    private static final int PER_DEVICE_CACHE_MAX_BYTES = 4194304;
-    private static MtpBitmapCache sInstance;
-
-    public synchronized static MtpBitmapCache getInstanceForDevice(MtpDevice device) {
-        if (sInstance == null || sInstance.mDevice != device) {
-            sInstance = new MtpBitmapCache(PER_DEVICE_CACHE_MAX_BYTES, device);
-        }
-        return sInstance;
-    }
-
-    public synchronized static void onDeviceDisconnected(MtpDevice device) {
-        if (sInstance != null && sInstance.mDevice == device) {
-            synchronized (sInstance) {
-                sInstance.mDevice = null;
-            }
-            sInstance = null;
-        }
-    }
-
-    private MtpDevice mDevice;
-
-    private MtpBitmapCache(int maxSize, MtpDevice device) {
-        super(maxSize);
-        mDevice = device;
-    }
-
-    @Override
-    protected int sizeOf(Integer key, Bitmap value) {
-        return value.getByteCount();
-    }
-
-    public Bitmap getOrCreate(Integer key) {
-        Bitmap b = get(key);
-        return b == null ? createAndInsert(key) : b;
-    }
-
-    private Bitmap createAndInsert(Integer key) {
-        MtpDevice device;
-        synchronized (this) {
-            device = mDevice;
-        }
-        if (device == null) return null;
-        byte[] imageBytes = device.getThumbnail(key);
-        if (imageBytes == null) return null;
-        Bitmap created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
-        put(key, created);
-        return created;
-    }
-}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java b/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java
new file mode 100644 (file)
index 0000000..8d3884d
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.ui;
+
+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;
+
+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);
+    }
+
+    @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);
+        }
+    }
+
+    @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 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
new file mode 100644 (file)
index 0000000..9c19785
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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.ui;
+
+import android.content.Context;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.gallery3d.ingest.data.BitmapWithMetadata;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+
+public class MtpImageView extends ImageView {
+    private static final int FADE_IN_TIME_MS = 80;
+
+    private int mObjectHandle;
+    private int mGeneration;
+
+    private void init() {
+         showPlaceholder();
+    }
+
+    public MtpImageView(Context context) {
+        super(context);
+        init();
+    }
+
+    public MtpImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public MtpImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void showPlaceholder() {
+        setImageResource(android.R.color.transparent);
+    }
+
+    private LoadMtpImageTask mTask;
+
+    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;
+        mTask = new LoadMtpImageTask(device);
+        mTask.execute(object);
+    }
+
+    protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) {
+        return MtpBitmapFetch.getFullsize(device, info);
+    }
+
+    protected void onMtpImageDataFetchedFromDevice(Object result) {
+        BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata)result;
+        setImageBitmap(bitmapWithMetadata.bitmap);
+        setRotation(bitmapWithMetadata.rotationDegrees);
+    }
+
+    private class LoadMtpImageTask extends AsyncTask<MtpObjectInfo, Void, Object> {
+        private MtpDevice mDevice;
+
+        public LoadMtpImageTask(MtpDevice device) {
+            mDevice = device;
+        }
+
+        @Override
+        protected Object doInBackground(MtpObjectInfo... args) {
+            Object result = null;
+            if (!isCancelled()) {
+                result = fetchMtpImageDataFromDevice(mDevice, args[0]);
+            }
+            mDevice = null;
+            return result;
+        }
+
+        @Override
+        protected void onPostExecute(Object result) {
+            if (isCancelled() || result == null) {
+                return;
+            }
+            setAlpha(0f);
+            onMtpImageDataFetchedFromDevice(result);
+            animate().alpha(1f).setDuration(FADE_IN_TIME_MS);
+        }
+
+        @Override
+        protected void onCancelled() {
+        }
+    }
+
+    protected void cancelLoadingAndClear() {
+        if (mTask != null) {
+            mTask.cancel(true);
+        }
+        mTask = null;
+        animate().cancel();
+        setImageResource(android.R.color.transparent);
+        setRotation(0);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        cancelLoadingAndClear();
+        super.onDetachedFromWindow();
+    }
+}
index 2aeda73..3307e78 100644 (file)
@@ -22,26 +22,22 @@ import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.mtp.MtpDevice;
 import android.mtp.MtpObjectInfo;
-import android.os.AsyncTask;
 import android.util.AttributeSet;
-import android.view.View;
 import android.widget.Checkable;
-import android.widget.ImageView;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
 
-public class MtpThumbnailTileView extends ImageView implements Checkable {
-    private static final int FADE_IN_TIME_MS = 80;
+
+public class MtpThumbnailTileView extends MtpImageView implements Checkable {
 
     private Paint mForegroundPaint;
     private boolean mIsChecked;
-    private int mObjectHandle;
-    private int mGeneration;
+    private Bitmap mBitmap;
 
     private void init() {
         mForegroundPaint = new Paint();
         mForegroundPaint.setColor(getResources().getColor(R.color.ingest_highlight_semitransparent));
-        showPlaceholder();
     }
 
     public MtpThumbnailTileView(Context context) {
@@ -66,9 +62,20 @@ public class MtpThumbnailTileView extends ImageView implements Checkable {
     }
 
     @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 (mIsChecked) {
+        if (isChecked()) {
             canvas.drawRect(canvas.getClipBounds(), mForegroundPaint);
         }
     }
@@ -88,65 +95,12 @@ public class MtpThumbnailTileView extends ImageView implements Checkable {
         setChecked(!mIsChecked);
     }
 
-    private void showPlaceholder() {
-        setAlpha(0f);
-    }
-
-    private LoadThumbnailTask mTask;
-
-    public void setMtpDeviceAndObjectInfo(MtpDevice device, MtpObjectInfo object, int gen) {
-        int handle = object.getObjectHandle();
-        if (handle == mObjectHandle && gen == mGeneration) {
-            return;
-        }
-        animate().cancel();
-        if (mTask != null) {
-            mTask.cancel(true);
-        }
-        mGeneration = gen;
-        mObjectHandle = handle;
-        Bitmap thumbnail = MtpBitmapCache.getInstanceForDevice(device)
-                .get(handle);
-        if (thumbnail != null) {
-            setAlpha(1f);
-            setImageBitmap(thumbnail);
-        } else {
-            showPlaceholder();
-            mTask = new LoadThumbnailTask(device);
-            mTask.execute(object);
-        }
-    }
-
-    private class LoadThumbnailTask extends AsyncTask<MtpObjectInfo, Void, Bitmap> {
-        private MtpDevice mDevice;
-
-        public LoadThumbnailTask(MtpDevice device) {
-            mDevice = device;
-        }
-
-        @Override
-        protected Bitmap doInBackground(MtpObjectInfo... args) {
-            Bitmap result = null;
-            if (!isCancelled()) {
-                result = MtpBitmapCache.getInstanceForDevice(mDevice).getOrCreate(
-                        args[0].getObjectHandle());
-            }
-            mDevice = null;
-            return result;
-        }
-
-        @Override
-        protected void onPostExecute(Bitmap result) {
-            if (isCancelled() || result == null) {
-                return;
-            }
-            setAlpha(0f);
-            setImageBitmap(result);
-            animate().alpha(1f).setDuration(FADE_IN_TIME_MS);
-        }
-
-        @Override
-        protected void onCancelled() {
+    @Override
+    protected void cancelLoadingAndClear() {
+        super.cancelLoadingAndClear();
+        if (mBitmap != null) {
+            MtpBitmapFetch.recycleThumbnail(mBitmap);
+            mBitmap = null;
         }
     }
 }