diff options
27 files changed, 1825 insertions, 938 deletions
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java index dc2adf78f..51591cc51 100644 --- a/src/com/android/gallery3d/app/AlbumPage.java +++ b/src/com/android/gallery3d/app/AlbumPage.java @@ -38,7 +38,6 @@ import com.android.gallery3d.data.MediaDetails; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; -import com.android.gallery3d.data.MtpDevice; import com.android.gallery3d.data.Path; import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.FilterShowActivity; @@ -318,7 +317,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster DataManager dm = mActivity.getDataManager(); Activity activity = mActivity; if (mData.getString(Gallery.EXTRA_CROP) != null) { - // TODO: Handle MtpImagew Uri uri = dm.getContentUri(item.getPath()); Intent intent = new Intent(FilterShowActivity.CROP_ACTION, uri) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) @@ -374,7 +372,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mDetailsSource = new MyDetailsSource(); Context context = mActivity.getAndroidContext(); - // Enable auto-select-all for mtp album if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { mSelectionManager.selectAll(); } @@ -554,9 +551,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster inflator.inflate(R.menu.album, menu); actionBar.setTitle(mMediaSet.getName()); - menu.findItem(R.id.action_slideshow) - .setVisible(!(mMediaSet instanceof MtpDevice)); - FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true); menu.findItem(R.id.action_group_by).setVisible(mShowClusterMenu); diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java index 4e89e91d0..4708e6fdb 100644 --- a/src/com/android/gallery3d/app/AlbumSetPage.java +++ b/src/com/android/gallery3d/app/AlbumSetPage.java @@ -262,10 +262,7 @@ public class AlbumSetPage extends ActivityState implements mActivity.getStateManager().startStateForResult( AlbumSetPage.class, REQUEST_DO_ANIMATION, data); } else { - if (!mGetContent && (targetSet.getSupportedOperations() - & MediaObject.SUPPORT_IMPORT) != 0) { - data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true); - } else if (!mGetContent && albumShouldOpenInFilmstrip(targetSet)) { + if (!mGetContent && albumShouldOpenInFilmstrip(targetSet)) { data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT, mSlotView.getSlotRect(slotIndex, mRootPane)); data.putInt(PhotoPage.KEY_INDEX_HINT, 0); diff --git a/src/com/android/gallery3d/app/NotificationIds.java b/src/com/android/gallery3d/app/NotificationIds.java new file mode 100644 index 000000000..d697d854b --- /dev/null +++ b/src/com/android/gallery3d/app/NotificationIds.java @@ -0,0 +1,22 @@ +/* + * 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.app; + +public class NotificationIds { + public static final int INGEST_NOTIFICATION_SCANNING = 10; + public static final int INGEST_NOTIFICATION_IMPORTING = 11; +} diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index ca3ee19ab..8896cd188 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -51,7 +51,6 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback; import com.android.gallery3d.data.MediaSet; -import com.android.gallery3d.data.MtpSource; import com.android.gallery3d.data.Path; import com.android.gallery3d.data.SecureAlbum; import com.android.gallery3d.data.SecureSource; @@ -64,7 +63,6 @@ import com.android.gallery3d.ui.DetailsHelper; import com.android.gallery3d.ui.DetailsHelper.CloseListener; import com.android.gallery3d.ui.DetailsHelper.DetailsSource; import com.android.gallery3d.ui.GLView; -import com.android.gallery3d.ui.ImportCompleteListener; import com.android.gallery3d.ui.MenuExecutor; import com.android.gallery3d.ui.PhotoView; import com.android.gallery3d.ui.SelectionManager; @@ -345,7 +343,7 @@ public abstract class PhotoPage extends ActivityState implements } if (stayedOnCamera) { - if (mAppBridge == null) { + if (mAppBridge == null && mMediaSet.getTotalMediaItemCount() > 1) { launchCamera(); /* We got here by swiping from photo 1 to the placeholder, so make it be the thing that @@ -800,9 +798,6 @@ public abstract class PhotoPage extends ActivityState implements if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { return false; } - if (MtpSource.isMtpPath(mOriginalSetPathString)) { - return false; - } return true; } @@ -1098,12 +1093,6 @@ public abstract class PhotoPage extends ActivityState implements mSelectionManager.toggle(path); mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); return true; - case R.id.action_import: - mSelectionManager.deSelectAll(); - mSelectionManager.toggle(path); - mMenuExecutor.onMenuClicked(item, confirmMsg, - new ImportCompleteListener(mActivity)); - return true; default : return false; } diff --git a/src/com/android/gallery3d/app/UsbDeviceActivity.java b/src/com/android/gallery3d/app/UsbDeviceActivity.java deleted file mode 100644 index 28bd667e3..000000000 --- a/src/com/android/gallery3d/app/UsbDeviceActivity.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2011 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.app; - - -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -/* This Activity does nothing but receive USB_DEVICE_ATTACHED events from the - * USB service and springboards to the main Gallery activity - */ -public final class UsbDeviceActivity extends Activity { - - static final String TAG = "UsbDeviceActivity"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // - Intent intent = new Intent(this, Gallery.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "unable to start Gallery activity", e); - } - finish(); - } -} diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java index 4ec7b6d98..8fcf4008a 100644 --- a/src/com/android/gallery3d/data/DataManager.java +++ b/src/com/android/gallery3d/data/DataManager.java @@ -68,13 +68,9 @@ public class DataManager implements StitchingChangeListener { private static final String TAG = "DataManager"; // This is the path for the media set seen by the user at top level. - private static final String TOP_SET_PATH = ApiHelper.HAS_MTP - ? "/combo/{/mtp,/local/all,/picasa/all}" - : "/combo/{/local/all,/picasa/all}"; + private static final String TOP_SET_PATH = "/combo/{/local/all,/picasa/all}"; - private static final String TOP_IMAGE_SET_PATH = ApiHelper.HAS_MTP - ? "/combo/{/mtp,/local/image,/picasa/image}" - : "/combo/{/local/image,/picasa/image}"; + private static final String TOP_IMAGE_SET_PATH = "/combo/{/local/image,/picasa/image}"; private static final String TOP_VIDEO_SET_PATH = "/combo/{/local/video,/picasa/video}"; @@ -118,9 +114,6 @@ public class DataManager implements StitchingChangeListener { // the order matters, the UriSource must come last addSource(new LocalSource(mApplication)); addSource(new PicasaSource(mApplication)); - if (ApiHelper.HAS_MTP) { - addSource(new MtpSource(mApplication)); - } addSource(new ComboSource(mApplication)); addSource(new ClusterSource(mApplication)); addSource(new FilterSource(mApplication)); diff --git a/src/com/android/gallery3d/data/DataSourceType.java b/src/com/android/gallery3d/data/DataSourceType.java index 27611880d..ab534d0c3 100644 --- a/src/com/android/gallery3d/data/DataSourceType.java +++ b/src/com/android/gallery3d/data/DataSourceType.java @@ -22,12 +22,10 @@ public final class DataSourceType { public static final int TYPE_NOT_CATEGORIZED = 0; public static final int TYPE_LOCAL = 1; public static final int TYPE_PICASA = 2; - public static final int TYPE_MTP = 3; - public static final int TYPE_CAMERA = 4; + public static final int TYPE_CAMERA = 3; private static final Path PICASA_ROOT = Path.fromString("/picasa"); private static final Path LOCAL_ROOT = Path.fromString("/local"); - private static final Path MTP_ROOT = Path.fromString("/mtp"); public static int identifySourceType(MediaSet set) { if (set == null) { @@ -40,7 +38,6 @@ public final class DataSourceType { Path prefix = path.getPrefixPath(); if (prefix == PICASA_ROOT) return TYPE_PICASA; - if (prefix == MTP_ROOT) return TYPE_MTP; if (prefix == LOCAL_ROOT) return TYPE_LOCAL; return TYPE_NOT_CATEGORIZED; diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java index 9c82661f6..270d4cf0b 100644 --- a/src/com/android/gallery3d/data/MediaObject.java +++ b/src/com/android/gallery3d/data/MediaObject.java @@ -35,13 +35,12 @@ public abstract class MediaObject { public static final int SUPPORT_CACHE = 1 << 8; public static final int SUPPORT_EDIT = 1 << 9; public static final int SUPPORT_INFO = 1 << 10; - public static final int SUPPORT_IMPORT = 1 << 11; - public static final int SUPPORT_TRIM = 1 << 12; - public static final int SUPPORT_UNLOCK = 1 << 13; - public static final int SUPPORT_BACK = 1 << 14; - public static final int SUPPORT_ACTION = 1 << 15; - public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 16; - public static final int SUPPORT_MUTE = 1 << 17; + public static final int SUPPORT_TRIM = 1 << 11; + public static final int SUPPORT_UNLOCK = 1 << 12; + public static final int SUPPORT_BACK = 1 << 13; + public static final int SUPPORT_ACTION = 1 << 14; + public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 15; + public static final int SUPPORT_MUTE = 1 << 16; public static final int SUPPORT_ALL = 0xffffffff; // These are the bits returned from getMediaType(): @@ -120,10 +119,6 @@ public abstract class MediaObject { return MEDIA_TYPE_UNKNOWN; } - public boolean Import() { - throw new UnsupportedOperationException(); - } - public MediaDetails getDetails() { MediaDetails details = new MediaDetails(); return details; diff --git a/src/com/android/gallery3d/data/MtpContext.java b/src/com/android/gallery3d/data/MtpContext.java deleted file mode 100644 index 53b6faafb..000000000 --- a/src/com/android/gallery3d/data/MtpContext.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.android.gallery3d.data; - -import android.annotation.TargetApi; -import android.content.Context; -import android.media.MediaScannerConnection; -import android.media.MediaScannerConnection.MediaScannerConnectionClient; -import android.mtp.MtpObjectInfo; -import android.net.Uri; -import android.os.Environment; -import android.util.Log; -import android.widget.Toast; - -import com.android.gallery3d.R; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.util.BucketNames; -import com.android.gallery3d.util.GalleryUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1) -public class MtpContext implements MtpClient.Listener { - private static final String TAG = "MtpContext"; - - private ScannerClient mScannerClient; - private Context mContext; - private MtpClient mClient; - - 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 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 onScanCompleted(String path, Uri uri) { - } - } - - public MtpContext(Context context) { - mContext = context; - mScannerClient = new ScannerClient(context); - mClient = new MtpClient(mContext); - } - - public void pause() { - mClient.removeListener(this); - } - - public void resume() { - mClient.addListener(this); - notifyDirty(); - } - - @Override - public void deviceAdded(android.mtp.MtpDevice device) { - notifyDirty(); - showToast(R.string.camera_connected); - } - - @Override - public void deviceRemoved(android.mtp.MtpDevice device) { - notifyDirty(); - showToast(R.string.camera_disconnected); - } - - private void notifyDirty() { - mContext.getContentResolver().notifyChange(Uri.parse("mtp://"), null); - } - - private void showToast(final int msg) { - Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show(); - } - - public MtpClient getMtpClient() { - return mClient; - } - - public boolean copyFile(String deviceName, MtpObjectInfo objInfo) { - if (GalleryUtils.hasSpaceForSize(objInfo.getCompressedSize())) { - File dest = Environment.getExternalStorageDirectory(); - dest = new File(dest, BucketNames.IMPORTED); - dest.mkdirs(); - String destPath = new File(dest, objInfo.getName()).getAbsolutePath(); - int objectId = objInfo.getObjectHandle(); - if (mClient.importFile(deviceName, objectId, destPath)) { - mScannerClient.scanPath(destPath); - return true; - } - } else { - Log.w(TAG, "No space to import " + objInfo.getName() + - " whose size = " + objInfo.getCompressedSize()); - } - return false; - } - - public boolean copyAlbum(String deviceName, String albumName, - List<MtpObjectInfo> children) { - File dest = Environment.getExternalStorageDirectory(); - dest = new File(dest, albumName); - dest.mkdirs(); - int success = 0; - for (MtpObjectInfo child : children) { - if (!GalleryUtils.hasSpaceForSize(child.getCompressedSize())) continue; - - File importedFile = new File(dest, child.getName()); - String path = importedFile.getAbsolutePath(); - if (mClient.importFile(deviceName, child.getObjectHandle(), path)) { - mScannerClient.scanPath(path); - success++; - } - } - return success == children.size(); - } -} diff --git a/src/com/android/gallery3d/data/MtpDevice.java b/src/com/android/gallery3d/data/MtpDevice.java deleted file mode 100644 index 25ffbc8ba..000000000 --- a/src/com/android/gallery3d/data/MtpDevice.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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.data; - -import android.annotation.TargetApi; -import android.hardware.usb.UsbDevice; -import android.mtp.MtpConstants; -import android.mtp.MtpObjectInfo; -import android.mtp.MtpStorageInfo; -import android.net.Uri; -import android.util.Log; - -import com.android.gallery3d.app.GalleryApp; -import com.android.gallery3d.common.ApiHelper; - -import java.util.ArrayList; -import java.util.List; - -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1) -public class MtpDevice extends MediaSet { - private static final String TAG = "MtpDevice"; - - private final GalleryApp mApplication; - private final int mDeviceId; - private final String mDeviceName; - private final MtpContext mMtpContext; - private final String mName; - private final ChangeNotifier mNotifier; - private final Path mItemPath; - private List<MtpObjectInfo> mJpegChildren; - - public MtpDevice(Path path, GalleryApp application, int deviceId, - String name, MtpContext mtpContext) { - super(path, nextVersionNumber()); - mApplication = application; - mDeviceId = deviceId; - mDeviceName = UsbDevice.getDeviceName(deviceId); - mMtpContext = mtpContext; - mName = name; - mNotifier = new ChangeNotifier(this, Uri.parse("mtp://"), application); - mItemPath = Path.fromString("/mtp/item/" + String.valueOf(deviceId)); - mJpegChildren = new ArrayList<MtpObjectInfo>(); - } - - public MtpDevice(Path path, GalleryApp application, int deviceId, - MtpContext mtpContext) { - this(path, application, deviceId, - MtpDeviceSet.getDeviceName(mtpContext, deviceId), mtpContext); - } - - private List<MtpObjectInfo> loadItems() { - ArrayList<MtpObjectInfo> result = new ArrayList<MtpObjectInfo>(); - - List<MtpStorageInfo> storageList = mMtpContext.getMtpClient() - .getStorageList(mDeviceName); - if (storageList == null) return result; - - for (MtpStorageInfo info : storageList) { - collectJpegChildren(info.getStorageId(), 0, result); - } - - return result; - } - - private void collectJpegChildren(int storageId, int objectId, - ArrayList<MtpObjectInfo> result) { - ArrayList<MtpObjectInfo> dirChildren = new ArrayList<MtpObjectInfo>(); - - queryChildren(storageId, objectId, result, dirChildren); - - for (int i = 0, n = dirChildren.size(); i < n; i++) { - MtpObjectInfo info = dirChildren.get(i); - collectJpegChildren(storageId, info.getObjectHandle(), result); - } - } - - private void queryChildren(int storageId, int objectId, - ArrayList<MtpObjectInfo> jpeg, ArrayList<MtpObjectInfo> dir) { - List<MtpObjectInfo> children = mMtpContext.getMtpClient().getObjectList( - mDeviceName, storageId, objectId); - if (children == null) return; - - for (MtpObjectInfo obj : children) { - int format = obj.getFormat(); - switch (format) { - case MtpConstants.FORMAT_JFIF: - case MtpConstants.FORMAT_EXIF_JPEG: - jpeg.add(obj); - break; - case MtpConstants.FORMAT_ASSOCIATION: - dir.add(obj); - break; - default: - Log.w(TAG, "other type: name = " + obj.getName() - + ", format = " + format); - } - } - } - - public static MtpObjectInfo getObjectInfo(MtpContext mtpContext, int deviceId, - int objectId) { - String deviceName = UsbDevice.getDeviceName(deviceId); - return mtpContext.getMtpClient().getObjectInfo(deviceName, objectId); - } - - @Override - public ArrayList<MediaItem> getMediaItem(int start, int count) { - ArrayList<MediaItem> result = new ArrayList<MediaItem>(); - int begin = start; - int end = Math.min(start + count, mJpegChildren.size()); - - DataManager dataManager = mApplication.getDataManager(); - for (int i = begin; i < end; i++) { - MtpObjectInfo child = mJpegChildren.get(i); - Path childPath = mItemPath.getChild(child.getObjectHandle()); - synchronized (DataManager.LOCK) { - MtpImage image = (MtpImage) dataManager.peekMediaObject(childPath); - if (image == null) { - image = new MtpImage( - childPath, mApplication, mDeviceId, child, mMtpContext); - } else { - image.updateContent(child); - } - result.add(image); - } - } - return result; - } - - @Override - public int getMediaItemCount() { - return mJpegChildren.size(); - } - - @Override - public String getName() { - return mName; - } - - @Override - public long reload() { - if (mNotifier.isDirty()) { - mDataVersion = nextVersionNumber(); - mJpegChildren = loadItems(); - } - return mDataVersion; - } - - @Override - public int getSupportedOperations() { - return SUPPORT_IMPORT; - } - - @Override - public boolean Import() { - return mMtpContext.copyAlbum(mDeviceName, mName, mJpegChildren); - } - - @Override - public boolean isLeafAlbum() { - return true; - } -} diff --git a/src/com/android/gallery3d/data/MtpDeviceSet.java b/src/com/android/gallery3d/data/MtpDeviceSet.java deleted file mode 100644 index bc4bc6321..000000000 --- a/src/com/android/gallery3d/data/MtpDeviceSet.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.data; - -import android.annotation.TargetApi; -import android.mtp.MtpDeviceInfo; -import android.net.Uri; -import android.os.Handler; -import android.util.Log; - -import com.android.gallery3d.R; -import com.android.gallery3d.app.GalleryApp; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.util.Future; -import com.android.gallery3d.util.FutureListener; -import com.android.gallery3d.util.MediaSetUtils; -import com.android.gallery3d.util.ThreadPool.Job; -import com.android.gallery3d.util.ThreadPool.JobContext; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -// MtpDeviceSet -- MtpDevice -- MtpImage -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1) -public class MtpDeviceSet extends MediaSet - implements FutureListener<ArrayList<MediaSet>> { - private static final String TAG = "MtpDeviceSet"; - - private GalleryApp mApplication; - private final ChangeNotifier mNotifier; - private final MtpContext mMtpContext; - private final String mName; - private final Handler mHandler; - - private Future<ArrayList<MediaSet>> mLoadTask; - private ArrayList<MediaSet> mDeviceSet = new ArrayList<MediaSet>(); - private ArrayList<MediaSet> mLoadBuffer; - private boolean mIsLoading; - - public MtpDeviceSet(Path path, GalleryApp application, MtpContext mtpContext) { - super(path, nextVersionNumber()); - mApplication = application; - mNotifier = new ChangeNotifier(this, Uri.parse("mtp://"), application); - mMtpContext = mtpContext; - mName = application.getResources().getString(R.string.set_label_mtp_devices); - mHandler = new Handler(mApplication.getMainLooper()); - } - - private class DevicesLoader implements Job<ArrayList<MediaSet>> { - @Override - public ArrayList<MediaSet> run(JobContext jc) { - DataManager dataManager = mApplication.getDataManager(); - ArrayList<MediaSet> result = new ArrayList<MediaSet>(); - - // Enumerate all devices - List<android.mtp.MtpDevice> devices = mMtpContext.getMtpClient().getDeviceList(); - Log.v(TAG, "loadDevices: " + devices + ", size=" + devices.size()); - for (android.mtp.MtpDevice mtpDevice : devices) { - synchronized (DataManager.LOCK) { - int deviceId = mtpDevice.getDeviceId(); - Path childPath = mPath.getChild(deviceId); - MtpDevice device = (MtpDevice) dataManager.peekMediaObject(childPath); - if (device == null) { - device = new MtpDevice(childPath, mApplication, deviceId, mMtpContext); - } - Log.d(TAG, "add device " + device); - result.add(device); - } - } - Collections.sort(result, MediaSetUtils.NAME_COMPARATOR); - return result; - } - } - - public static String getDeviceName(MtpContext mtpContext, int deviceId) { - android.mtp.MtpDevice device = mtpContext.getMtpClient().getDevice(deviceId); - if (device == null) { - return ""; - } - MtpDeviceInfo info = device.getDeviceInfo(); - if (info == null) { - return ""; - } - String manufacturer = info.getManufacturer().trim(); - String model = info.getModel().trim(); - return manufacturer + " " + model; - } - - @Override - public MediaSet getSubMediaSet(int index) { - return index < mDeviceSet.size() ? mDeviceSet.get(index) : null; - } - - @Override - public int getSubMediaSetCount() { - return mDeviceSet.size(); - } - - @Override - public String getName() { - return mName; - } - - @Override - public synchronized boolean isLoading() { - return mIsLoading; - } - - @Override - public synchronized long reload() { - if (mNotifier.isDirty()) { - if (mLoadTask != null) mLoadTask.cancel(); - mIsLoading = true; - mLoadTask = mApplication.getThreadPool().submit(new DevicesLoader(), this); - } - if (mLoadBuffer != null) { - mDeviceSet = mLoadBuffer; - mLoadBuffer = null; - for (MediaSet device : mDeviceSet) { - device.reload(); - } - mDataVersion = nextVersionNumber(); - } - return mDataVersion; - } - - @Override - public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) { - if (future != mLoadTask) return; - mLoadBuffer = future.get(); - mIsLoading = false; - if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>(); - - mHandler.post(new Runnable() { - @Override - public void run() { - notifyContentChanged(); - } - }); - } -} diff --git a/src/com/android/gallery3d/data/MtpImage.java b/src/com/android/gallery3d/data/MtpImage.java deleted file mode 100644 index 0abb1985d..000000000 --- a/src/com/android/gallery3d/data/MtpImage.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.data; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapRegionDecoder; -import android.hardware.usb.UsbDevice; -import android.mtp.MtpObjectInfo; -import android.net.Uri; -import android.util.Log; - -import com.android.gallery3d.app.GalleryApp; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.provider.GalleryProvider; -import com.android.gallery3d.util.ThreadPool.Job; -import com.android.gallery3d.util.ThreadPool.JobContext; - -import java.text.DateFormat; -import java.util.Date; - -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1) -public class MtpImage extends MediaItem { - private static final String TAG = "MtpImage"; - - private final int mDeviceId; - private int mObjectId; - private int mObjectSize; - private long mDateTaken; - private String mFileName; - private final MtpContext mMtpContext; - private final MtpObjectInfo mObjInfo; - private final int mImageWidth; - private final int mImageHeight; - private final Context mContext; - - MtpImage(Path path, GalleryApp application, int deviceId, - MtpObjectInfo objInfo, MtpContext mtpContext) { - super(path, nextVersionNumber()); - mContext = application.getAndroidContext(); - mDeviceId = deviceId; - mObjInfo = objInfo; - mObjectId = objInfo.getObjectHandle(); - mObjectSize = objInfo.getCompressedSize(); - mDateTaken = objInfo.getDateCreated(); - mFileName = objInfo.getName(); - mImageWidth = objInfo.getImagePixWidth(); - mImageHeight = objInfo.getImagePixHeight(); - mMtpContext = mtpContext; - } - - MtpImage(Path path, GalleryApp app, int deviceId, int objectId, MtpContext mtpContext) { - this(path, app, deviceId, MtpDevice.getObjectInfo(mtpContext, deviceId, objectId), - mtpContext); - } - - @Override - public long getDateInMs() { - return mDateTaken; - } - - @Override - public Job<Bitmap> requestImage(int type) { - return new Job<Bitmap>() { - @Override - public Bitmap run(JobContext jc) { - byte[] thumbnail = mMtpContext.getMtpClient().getThumbnail( - UsbDevice.getDeviceName(mDeviceId), mObjectId); - if (thumbnail == null) { - Log.w(TAG, "decoding thumbnail failed"); - return null; - } - return DecodeUtils.decode(jc, thumbnail, null); - } - }; - } - - @Override - public Job<BitmapRegionDecoder> requestLargeImage() { - return new Job<BitmapRegionDecoder>() { - @Override - public BitmapRegionDecoder run(JobContext jc) { - byte[] bytes = mMtpContext.getMtpClient().getObject( - UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize); - return DecodeUtils.createBitmapRegionDecoder( - jc, bytes, 0, bytes.length, false); - } - }; - } - - public byte[] getImageData() { - return mMtpContext.getMtpClient().getObject( - UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize); - } - - @Override - public boolean Import() { - return mMtpContext.copyFile(UsbDevice.getDeviceName(mDeviceId), mObjInfo); - } - - @Override - public int getSupportedOperations() { - return SUPPORT_FULL_IMAGE | SUPPORT_IMPORT; - } - - public void updateContent(MtpObjectInfo info) { - if (mObjectId != info.getObjectHandle() || mDateTaken != info.getDateCreated()) { - mObjectId = info.getObjectHandle(); - mDateTaken = info.getDateCreated(); - mDataVersion = nextVersionNumber(); - } - } - - @Override - public String getMimeType() { - // Currently only JPEG is supported in MTP. - return "image/jpeg"; - } - - @Override - public int getMediaType() { - return MEDIA_TYPE_IMAGE; - } - - @Override - public long getSize() { - return mObjectSize; - } - - @Override - public Uri getContentUri() { - return GalleryProvider.getUriFor(mContext, mPath); - } - - @Override - public MediaDetails getDetails() { - MediaDetails details = super.getDetails(); - DateFormat formater = DateFormat.getDateTimeInstance(); - details.addDetail(MediaDetails.INDEX_TITLE, mFileName); - details.addDetail(MediaDetails.INDEX_DATETIME, formater.format(new Date(mDateTaken))); - details.addDetail(MediaDetails.INDEX_WIDTH, mImageWidth); - details.addDetail(MediaDetails.INDEX_HEIGHT, mImageHeight); - details.addDetail(MediaDetails.INDEX_SIZE, Long.valueOf(mObjectSize)); - return details; - } - - @Override - public int getWidth() { - return mImageWidth; - } - - @Override - public int getHeight() { - return mImageHeight; - } -} diff --git a/src/com/android/gallery3d/data/MtpSource.java b/src/com/android/gallery3d/data/MtpSource.java deleted file mode 100644 index 47a2e6c66..000000000 --- a/src/com/android/gallery3d/data/MtpSource.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.data; - -import com.android.gallery3d.app.GalleryApp; - -public class MtpSource extends MediaSource { - @SuppressWarnings("unused") - private static final String TAG = "MtpSource"; - - private static final int MTP_DEVICESET = 0; - private static final int MTP_DEVICE = 1; - private static final int MTP_ITEM = 2; - - GalleryApp mApplication; - PathMatcher mMatcher; - MtpContext mMtpContext; - - public MtpSource(GalleryApp application) { - super("mtp"); - mApplication = application; - mMatcher = new PathMatcher(); - mMatcher.add("/mtp", MTP_DEVICESET); - mMatcher.add("/mtp/*", MTP_DEVICE); - mMatcher.add("/mtp/item/*/*", MTP_ITEM); - mMtpContext = new MtpContext(mApplication.getAndroidContext()); - } - - @Override - public MediaObject createMediaObject(Path path) { - switch (mMatcher.match(path)) { - case MTP_DEVICESET: { - return new MtpDeviceSet(path, mApplication, mMtpContext); - } - case MTP_DEVICE: { - int deviceId = mMatcher.getIntVar(0); - return new MtpDevice(path, mApplication, deviceId, mMtpContext); - } - case MTP_ITEM: { - int deviceId = mMatcher.getIntVar(0); - int objectId = mMatcher.getIntVar(1); - return new MtpImage(path, mApplication, deviceId, objectId, mMtpContext); - } - default: - throw new RuntimeException("bad path: " + path); - } - } - - @Override - public void pause() { - mMtpContext.pause(); - } - - @Override - public void resume() { - mMtpContext.resume(); - } - - public static boolean isMtpPath(String s) { - return s != null && Path.fromString(s).getPrefix().equals("mtp"); - } -} diff --git a/src/com/android/gallery3d/ingest/ImportTask.java b/src/com/android/gallery3d/ingest/ImportTask.java new file mode 100644 index 000000000..d82ccd61f --- /dev/null +++ b/src/com/android/gallery3d/ingest/ImportTask.java @@ -0,0 +1,94 @@ +/* + * 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); + } + + 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(); + 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); + } + } finally { + mListener = null; + mWakeLock.release(); + } + } +} diff --git a/src/com/android/gallery3d/ingest/IngestActivity.java b/src/com/android/gallery3d/ingest/IngestActivity.java new file mode 100644 index 000000000..a21ccc2fe --- /dev/null +++ b/src/com/android/gallery3d/ingest/IngestActivity.java @@ -0,0 +1,344 @@ +/* + * 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.app.Activity; +import android.app.ProgressDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.mtp.MtpObjectInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.SparseBooleanArray; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.GridView; + +import com.android.gallery3d.R; +import com.android.gallery3d.ingest.adapter.MtpAdapter; + +import java.lang.ref.WeakReference; +import java.util.Collection; + +public class IngestActivity extends Activity implements + MtpDeviceIndex.ProgressListener, ImportTask.Listener { + + private IngestService mHelperService; + private boolean mActive = false; + private GridView mGridView; + private MtpAdapter mAdapter; + private Handler mHandler; + private ProgressDialog mProgressDialog; + private ActionMode mActiveActionMode; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + doBindHelperService(); + + setContentView(R.layout.ingest_activity_item_list); + mGridView = (GridView) findViewById(R.id.ingest_gridview); + mAdapter = new MtpAdapter(this); + mGridView.setAdapter(mAdapter); + mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener); + mGridView.setOnItemClickListener(mOnItemClickListener); + + mHandler = new ItemListHandler(this); + } + + private OnItemClickListener mOnItemClickListener = new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> adapterView, View itemView, int position, long arg3) { + mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position)); + } + }; + + 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); + } + + mIgnoreItemCheckedStateChanges = false; + } + 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; + } + } + + @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; + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mActiveActionMode = null; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + updateSelectedTitle(mode); + return false; + } + }; + + @Override + protected void onDestroy() { + super.onDestroy(); + doUnbindHelperService(); + } + + @Override + protected void onResume() { + mActive = true; + if (mHelperService != null) mHelperService.setClientActivity(this); + super.onResume(); + } + + @Override + protected void onPause() { + if (mHelperService != null) mHelperService.setClientActivity(null); + mActive = false; + cleanupProgressDialog(); + super.onPause(); + } + + protected void notifyIndexChanged() { + mAdapter.notifyDataSetChanged(); + if (mActiveActionMode != null) { + mActiveActionMode.finish(); + mActiveActionMode = null; + } + } + + 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(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); + } + + @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); + } + + @Override + public void onIndexFinish() { + // Not guaranteed to be called on the UI thread + mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE); + mHandler.sendEmptyMessage(ItemListHandler.MSG_ADAPTER_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); + } + + @Override + public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported) { + // Not guaranteed to be called on the UI thread + mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE); + // 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; + } + + 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 cleanupProgressDialog() { + if (mProgressDialog != null) { + mProgressDialog.hide(); + mProgressDialog = null; + } + } + + // 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_ADAPTER_NOTIFY_CHANGED = 2; + + WeakReference<IngestActivity> mParentReference; + + public ItemListHandler(IngestActivity parent) { + super(); + mParentReference = new WeakReference<IngestActivity>(parent); + } + + 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_ADAPTER_NOTIFY_CHANGED: + parent.notifyIndexChanged(); + break; + default: + break; + } + } + } + + private ServiceConnection mHelperServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + mHelperService = ((IngestService.LocalBinder) service).getService(); + mHelperService.setClientActivity(IngestActivity.this); + mAdapter.setMtpDeviceIndex(mHelperService.getIndex()); + } + + public void onServiceDisconnected(ComponentName className) { + mHelperService = null; + } + }; + + 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 new file mode 100644 index 000000000..2ca4d4ca2 --- /dev/null +++ b/src/com/android/gallery3d/ingest/IngestService.java @@ -0,0 +1,287 @@ +/* + * 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.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +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.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.ingest.ui.MtpBitmapCache; +import com.android.gallery3d.util.BucketNames; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class IngestService extends Service implements ImportTask.Listener, + MtpDeviceIndex.ProgressListener, MtpClient.Listener { + + public class LocalBinder extends Binder { + IngestService getService() { + return IngestService.this; + } + } + + private static final int PROGRESS_UPDATE_INTERVAL_MS = 180; + + private static MtpClient sClient; + + 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 Collection<MtpObjectInfo> mRedeliverObjectsNotImported; + private boolean mRedeliverNotifyIndexChanged = false; + private NotificationManager mNotificationManager; + private NotificationCompat.Builder mNotificationBuilder; + private long mLastProgressIndexTime = 0; + + @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); + + if (sClient == null) { + sClient = new MtpClient(getApplicationContext()); + } + List<MtpDevice> devices = sClient.getDeviceList(); + if (devices.size() > 0) { + setDevice(devices.get(0)); + } + sClient.addListener(this); + } + + @Override + public void onDestroy() { + sClient.removeListener(this); + mIndex.unsetProgressListener(this); + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private void setDevice(MtpDevice device) { + if (mDevice == device) return; + mRedeliverImportFinish = false; + mRedeliverObjectsNotImported = null; + mRedeliverNotifyIndexChanged = false; + mDevice = device; + mIndex.setDevice(mDevice); + if (mDevice != null) { + MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo(); + mDevicePrettyName = deviceInfo.getModel(); + mNotificationBuilder.setContentTitle(mDevicePrettyName); + new Thread(mIndex.getIndexRunnable()).start(); + } else { + mDevicePrettyName = null; + } + if (mClientActivity != null) { + mClientActivity.notifyIndexChanged(); + } else { + mRedeliverNotifyIndexChanged = true; + } + } + + protected MtpDeviceIndex getIndex() { + return mIndex; + } + + protected void setClientActivity(IngestActivity activity) { + if (mClientActivity == activity) return; + mClientActivity = activity; + if (mClientActivity == null) return; + mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_IMPORTING); + mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING); + if (mRedeliverImportFinish) { + mClientActivity.onImportFinish(mRedeliverObjectsNotImported); + mRedeliverImportFinish = false; + mRedeliverObjectsNotImported = null; + } + if (mRedeliverNotifyIndexChanged) { + mClientActivity.notifyIndexChanged(); + mRedeliverNotifyIndexChanged = false; + } + } + + 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)); + } + } + } + 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(); + } + + @Override + public void deviceAdded(MtpDevice device) { + if (mDevice == null) { + setDevice(device); + } + } + + @Override + public void deviceRemoved(MtpDevice device) { + if (device == mDevice) { + setDevice(null); + } + MtpBitmapCache.onDeviceDisconnected(device); + } + + @Override + public void onImportProgress(int visitedCount, int totalCount, + String pathIfSuccessful) { + if (pathIfSuccessful != null) { + mScannerClient.scanPath(pathIfSuccessful); + } + if (mClientActivity != null) { + mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful); + } else { + mNotificationBuilder.setProgress(totalCount, visitedCount, false) + .setContentText(getResources().getText(R.string.ingest_importing)); + mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING, + mNotificationBuilder.build()); + } + } + + @Override + public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported) { + if (mClientActivity != null) { + mClientActivity.onImportFinish(objectsNotImported); + } else { + mRedeliverImportFinish = true; + mRedeliverObjectsNotImported = objectsNotImported; + mNotificationBuilder.setProgress(0, 0, false) + .setContentText(getResources().getText(R.string.import_complete)); + mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING, + mNotificationBuilder.build()); + } + stopForeground(mClientActivity != null); + } + + @Override + public void onObjectIndexed(MtpObjectInfo object, int numVisited) { + 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 onSorting() { + if (mClientActivity != null) mClientActivity.onSorting(); + } + + @Override + public void onIndexFinish() { + 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()); + } + } + + // 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 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 onScanCompleted(String path, Uri uri) { + } + } +} diff --git a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java new file mode 100644 index 000000000..10ccb82ac --- /dev/null +++ b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java @@ -0,0 +1,501 @@ +/* + * 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.List; +import java.util.Map; +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 { + + @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 Map<SimpleDate, DateBucket> mBucketsTemp; + private int mGeneration = 0; + + public enum Progress { + Uninitialized, Initialized, Pending, Started, Sorting, Finished + } + + private Progress mProgress = Progress.Uninitialized; + private int mNumObjects = 0; + 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 Runnable() { + @Override + public void run() { + indexDevice(); + } + }; + } + + 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(order == SortOrder.Ascending) { + return getAscending(position); + } else { + return getDescending(position); + } + } + + /** + * @param position Index of item to fetch, where 0 is the first item in + * ascending order + * @return position-th item in ascending order + */ + public Object getAscending(int position) { + if (mProgress != Progress.Finished) return null; + DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]]; + if (bucket.unifiedStartIndex == position) { + return bucket.bucket; + } else { + return bucket.get(position - 1 - bucket.unifiedStartIndex); + } + } + + /** + * @param position Index of item to fetch, where 0 is the last item in + * ascending order + * @return position-th item in descending order + */ + 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); + } + } + + /** + * @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]; + } else { + return mMtpObjects[mMtpObjects.length - 1 - position]; + } + } + + /** + * @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; + + 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); + } + + public MtpObjectInfo get(int position) { + return mMtpObjects[itemsStartIndex + position]; + } + + @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; + mBucketsTemp = null; + mNumObjects = 0; + mProgress = (mDevice == null) ? Progress.Uninitialized : Progress.Initialized; + } + + /* + * 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; + for (int j = 0; j < bucket.tempElementsList.size(); j++) { + mMtpObjects[currentItemsEntry] = bucket.tempElementsList.get(j); + currentItemsEntry++; + } + bucket.tempElementsList = null; + } + } + + private void indexDevice() { + synchronized (this) { + mProgress = Progress.Started; + } + mBucketsTemp = new HashMap<SimpleDate, DateBucket>(); + for (int storageId : mDevice.getStorageIds()) { + Stack<Integer> pendingDirectories = new Stack<Integer>(); + pendingDirectories.add(0xFFFFFFFF); // start at the root of the + // device + while (!pendingDirectories.isEmpty()) { + int dirHandle = pendingDirectories.pop(); + for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) { + MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle); + switch (objectInfo.getFormat()) { + case MtpConstants.FORMAT_JFIF: + case MtpConstants.FORMAT_EXIF_JPEG: + addObject(objectInfo); + break; + case MtpConstants.FORMAT_ASSOCIATION: + pendingDirectories.add(objectHandle); + break; + } + } + } + } + Collection<DateBucket> values = mBucketsTemp.values(); + mBucketsTemp = null; + mBuckets = values.toArray(new DateBucket[values.size()]); + values = null; + synchronized (this) { + mProgress = Progress.Sorting; + if (mProgressListener != null) { + mProgressListener.onSorting(); + } + } + sortAll(); + buildLookupIndex(); + synchronized (this) { + 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); + } + + /* + * 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(); + } + + 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 new file mode 100644 index 000000000..05db2cde2 --- /dev/null +++ b/src/com/android/gallery3d/ingest/SimpleDate.java @@ -0,0 +1,114 @@ +/* + * 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/MtpAdapter.java b/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java new file mode 100644 index 000000000..e09fcd962 --- /dev/null +++ b/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java @@ -0,0 +1,159 @@ +/* + * 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.app.Activity; +import android.content.Context; +import android.mtp.MtpObjectInfo; +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; + +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; + + public MtpAdapter(Activity context) { + super(); + mContext = context; + mInflater = (LayoutInflater)context.getSystemService + (Context.LAYOUT_INFLATER_SERVICE); + } + + public void setMtpDeviceIndex(MtpDeviceIndex index) { + mModel = index; + notifyDataSetChanged(); + } + + @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)); + 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; + } +} diff --git a/src/com/android/gallery3d/ingest/ui/DateTileView.java b/src/com/android/gallery3d/ingest/ui/DateTileView.java new file mode 100644 index 000000000..19b3c608d --- /dev/null +++ b/src/com/android/gallery3d/ingest/ui/DateTileView.java @@ -0,0 +1,83 @@ +/* + * 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.FrameLayout; +import android.widget.TextView; + +import com.android.gallery3d.R; +import com.android.gallery3d.ingest.SimpleDate; + +import java.text.DateFormatSymbols; + +public class DateTileView extends FrameLayout { + private static final String[] sMonthNames = DateFormatSymbols.getInstance().getShortMonths(); + + private TextView mDateTextView; + private TextView mMonthTextView; + private TextView mYearTextView; + private int mMonth = -1; + private int mYear = -1; + private int mDate = -1; + + public DateTileView(Context context) { + super(context); + } + + public DateTileView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + 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 + 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(int date, int month, int year) { + if (date != mDate) { + mDate = date; + mDateTextView.setText(mDate > 9 ? Integer.toString(mDate) : "0" + mDate); + } + if (month != mMonth) { + mMonth = month; + mMonthTextView.setText(sMonthNames[mMonth]); + } + if (year != mYear) { + mYear = year; + mYearTextView.setText(Integer.toString(mYear)); + } + } +} diff --git a/src/com/android/gallery3d/ingest/ui/MtpBitmapCache.java b/src/com/android/gallery3d/ingest/ui/MtpBitmapCache.java new file mode 100644 index 000000000..66ac4301f --- /dev/null +++ b/src/com/android/gallery3d/ingest/ui/MtpBitmapCache.java @@ -0,0 +1,64 @@ +/* + * 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) { + 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) { + byte[] imageBytes = mDevice.getThumbnail(key); + Bitmap created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); + put(key, created); + return created; + } +} diff --git a/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java b/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java new file mode 100644 index 000000000..ddba6af91 --- /dev/null +++ b/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java @@ -0,0 +1,144 @@ +/* + * 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.graphics.Bitmap; +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; + +public class MtpThumbnailTileView extends ImageView implements Checkable { + private static final int FADE_IN_TIME_MS = 80; + + private Paint mForegroundPaint; + private boolean mIsChecked; + + private void init() { + mForegroundPaint = new Paint(); + mForegroundPaint.setColor(getResources().getColor(R.color.ingest_highlight_semitransparent)); + showPlaceholder(); + } + + 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 + public void draw(Canvas canvas) { + super.draw(canvas); + if (mIsChecked) { + canvas.drawRect(canvas.getClipBounds(), mForegroundPaint); + } + } + + @Override + public boolean isChecked() { + return mIsChecked; + } + + @Override + public void setChecked(boolean checked) { + mIsChecked = checked; + } + + @Override + public void toggle() { + setChecked(!mIsChecked); + } + + private void showPlaceholder() { + setAlpha(0f); + } + + private LoadThumbnailTask mTask; + + public void setMtpDeviceAndObjectInfo(MtpDevice device, MtpObjectInfo object) { + animate().cancel(); + if (mTask != null) { + mTask.cancel(true); + } + Bitmap thumbnail = MtpBitmapCache.getInstanceForDevice(device) + .get(object.getObjectHandle()); + 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() { + } + } +} diff --git a/src/com/android/gallery3d/provider/GalleryProvider.java b/src/com/android/gallery3d/provider/GalleryProvider.java index 45f47a42c..d6c7ccd4d 100644 --- a/src/com/android/gallery3d/provider/GalleryProvider.java +++ b/src/com/android/gallery3d/provider/GalleryProvider.java @@ -34,14 +34,12 @@ import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.data.MtpImage; import com.android.gallery3d.data.Path; import com.android.gallery3d.picasasource.PicasaSource; import com.android.gallery3d.util.GalleryUtils; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.OutputStream; public class GalleryProvider extends ContentProvider { private static final String TAG = "GalleryProvider"; @@ -126,9 +124,6 @@ public class GalleryProvider extends ContentProvider { if (PicasaSource.isPicasaImage(object)) { return queryPicasaItem(object, projection, selection, selectionArgs, sortOrder); - } else if (object instanceof MtpImage) { - return queryMtpItem((MtpImage) object, - projection, selection, selectionArgs, sortOrder); } else { return null; } @@ -137,28 +132,6 @@ public class GalleryProvider extends ContentProvider { } } - private Cursor queryMtpItem(MtpImage image, String[] projection, - String selection, String[] selectionArgs, String sortOrder) { - Object[] columnValues = new Object[projection.length]; - for (int i = 0, n = projection.length; i < n; ++i) { - String column = projection[i]; - if (ImageColumns.DISPLAY_NAME.equals(column)) { - columnValues[i] = image.getName(); - } else if (ImageColumns.SIZE.equals(column)){ - columnValues[i] = image.getSize(); - } else if (ImageColumns.MIME_TYPE.equals(column)) { - columnValues[i] = image.getMimeType(); - } else if (ImageColumns.DATE_TAKEN.equals(column)) { - columnValues[i] = image.getDateInMs(); - } else { - Log.w(TAG, "unsupported column: " + column); - } - } - MatrixCursor cursor = new MatrixCursor(projection); - cursor.addRow(columnValues); - return cursor; - } - private Cursor queryPicasaItem(MediaObject image, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (projection == null) projection = SUPPORTED_PICASA_COLUMNS; @@ -211,9 +184,6 @@ public class GalleryProvider extends ContentProvider { } if (PicasaSource.isPicasaImage(object)) { return PicasaSource.openFile(getContext(), object, mode); - } else if (object instanceof MtpImage) { - return openPipeHelper(null, - new MtpPipeDataWriter((MtpImage) object)); } else { throw new FileNotFoundException("unspported type: " + object); } @@ -255,24 +225,4 @@ public class GalleryProvider extends ContentProvider { } } - private final class MtpPipeDataWriter implements PipeDataWriter<Object> { - private final MtpImage mImage; - - private MtpPipeDataWriter(MtpImage image) { - mImage = image; - } - - @Override - public void writeDataToPipe(ParcelFileDescriptor output, Object args) { - OutputStream os = null; - try { - os = new ParcelFileDescriptor.AutoCloseOutputStream(output); - os.write(mImage.getImageData()); - } catch (IOException e) { - Log.w(TAG, "fail to download: " + mImage.toString(), e); - } finally { - Utils.closeSilently(os); - } - } - } } diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java index 9b84bf75c..cd3ca091d 100644 --- a/src/com/android/gallery3d/ui/ActionModeHandler.java +++ b/src/com/android/gallery3d/ui/ActionModeHandler.java @@ -55,7 +55,7 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE - | MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT; + | MediaObject.SUPPORT_CACHE; public interface ActionModeListener { public boolean onActionItemClicked(MenuItem item); @@ -173,9 +173,7 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi ProgressListener listener = null; String confirmMsg = null; int action = item.getItemId(); - if (action == R.id.action_import) { - listener = new ImportCompleteListener(mActivity); - } else if (action == R.id.action_delete) { + if (action == R.id.action_delete) { confirmMsg = mActivity.getResources().getQuantityString( R.plurals.delete_selection, mSelectionManager.getSelectedCount()); if (mDeleteProgressListener == null) { diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java index cd6c77861..6eeeec045 100644 --- a/src/com/android/gallery3d/ui/AlbumLabelMaker.java +++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java @@ -46,7 +46,6 @@ public class AlbumLabelMaker { private final LazyLoadedBitmap mLocalSetIcon; private final LazyLoadedBitmap mPicasaIcon; private final LazyLoadedBitmap mCameraIcon; - private final LazyLoadedBitmap mMtpIcon; public AlbumLabelMaker(Context context, AlbumSetSlotRenderer.LabelSpec spec) { mContext = context; @@ -57,7 +56,6 @@ public class AlbumLabelMaker { mLocalSetIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_folder); mPicasaIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_picasa); mCameraIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_camera); - mMtpIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_ptp); } public static int getBorderSize() { @@ -70,8 +68,6 @@ public class AlbumLabelMaker { return mCameraIcon.get(); case DataSourceType.TYPE_LOCAL: return mLocalSetIcon.get(); - case DataSourceType.TYPE_MTP: - return mMtpIcon.get(); case DataSourceType.TYPE_PICASA: return mPicasaIcon.get(); } diff --git a/src/com/android/gallery3d/ui/ImportCompleteListener.java b/src/com/android/gallery3d/ui/ImportCompleteListener.java deleted file mode 100644 index 8d6e981ce..000000000 --- a/src/com/android/gallery3d/ui/ImportCompleteListener.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2011 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.ui; - -import android.os.Bundle; -import android.widget.Toast; - -import com.android.gallery3d.R; -import com.android.gallery3d.app.AbstractGalleryActivity; -import com.android.gallery3d.app.AlbumPage; -import com.android.gallery3d.util.MediaSetUtils; - -public class ImportCompleteListener extends WakeLockHoldingProgressListener { - private static final String WAKE_LOCK_LABEL = "Gallery Album Import"; - - public ImportCompleteListener(AbstractGalleryActivity galleryActivity) { - super(galleryActivity, WAKE_LOCK_LABEL); - } - - @Override - public void onProgressComplete(int result) { - super.onProgressComplete(result); - int message; - if (result == MenuExecutor.EXECUTION_RESULT_SUCCESS) { - message = R.string.import_complete; - goToImportedAlbum(); - } else { - message = R.string.import_fail; - } - Toast.makeText(getActivity().getAndroidContext(), message, Toast.LENGTH_LONG).show(); - } - - private void goToImportedAlbum() { - String pathOfImportedAlbum = "/local/all/" + MediaSetUtils.IMPORTED_BUCKET_ID; - Bundle data = new Bundle(); - data.putString(AlbumPage.KEY_MEDIA_PATH, pathOfImportedAlbum); - getActivity().getStateManager().startState(AlbumPage.class, data); - } -} diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java index 46e400483..28fd71e10 100644 --- a/src/com/android/gallery3d/ui/MenuExecutor.java +++ b/src/com/android/gallery3d/ui/MenuExecutor.java @@ -178,7 +178,6 @@ public class MenuExecutor { boolean supportCache = (supported & MediaObject.SUPPORT_CACHE) != 0; boolean supportEdit = (supported & MediaObject.SUPPORT_EDIT) != 0; boolean supportInfo = (supported & MediaObject.SUPPORT_INFO) != 0; - boolean supportImport = (supported & MediaObject.SUPPORT_IMPORT) != 0; setMenuItemVisible(menu, R.id.action_delete, supportDelete); setMenuItemVisible(menu, R.id.action_rotate_ccw, supportRotate); @@ -193,7 +192,6 @@ public class MenuExecutor { setMenuItemVisible(menu, R.id.action_show_on_map, supportShowOnMap); setMenuItemVisible(menu, R.id.action_edit, supportEdit); setMenuItemVisible(menu, R.id.action_details, supportInfo); - setMenuItemVisible(menu, R.id.action_import, supportImport); } public static void updateMenuForPanorama(Menu menu, boolean shareAsPanorama360, @@ -271,9 +269,6 @@ public class MenuExecutor { case R.id.action_show_on_map: title = R.string.show_on_map; break; - case R.id.action_import: - title = R.string.Import; - break; default: return; } @@ -394,11 +389,6 @@ public class MenuExecutor { } break; } - case R.id.action_import: { - MediaObject obj = manager.getMediaObject(path); - result = obj.Import(); - break; - } default: throw new AssertionError(); } |