diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:54 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:05:54 -0800 |
commit | 0eaec58e292ce4eaa6baadecf643f0f65e48d278 (patch) | |
tree | 78ef71512fa619ca4befa57b239be433f6aae74e /src | |
parent | 1d4c75065966c4f6f56900e31f655bfd1b334435 (diff) | |
download | LegacyCamera-0eaec58e292ce4eaa6baadecf643f0f65e48d278.zip LegacyCamera-0eaec58e292ce4eaa6baadecf643f0f65e48d278.tar.gz LegacyCamera-0eaec58e292ce4eaa6baadecf643f0f65e48d278.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'src')
23 files changed, 2876 insertions, 3005 deletions
diff --git a/src/com/android/camera/ActionMenuButton.java b/src/com/android/camera/ActionMenuButton.java new file mode 100644 index 0000000..65e1f0e --- /dev/null +++ b/src/com/android/camera/ActionMenuButton.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008 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.camera; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.text.Layout; +import android.util.AttributeSet; +import android.widget.TextView; + +/** + * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan + * because we want to make the bubble taller than the text and TextView's clip is + * too aggressive. + */ +public class ActionMenuButton extends TextView { + private static final float CORNER_RADIUS = 8.0f; + private static final float PADDING_H = 5.0f; + private static final float PADDING_V = 1.0f; + + private final RectF mRect = new RectF(); + private Paint mPaint; + + public ActionMenuButton(Context context) { + super(context); + init(); + } + + public ActionMenuButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ActionMenuButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setFocusable(true); + + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setColor(getContext().getResources().getColor(R.color.bubble_dark_background)); + } + + @Override + protected void drawableStateChanged() { + invalidate(); + super.drawableStateChanged(); + } + + @Override + public void draw(Canvas canvas) { + final Layout layout = getLayout(); + final RectF rect = mRect; + final int left = getCompoundPaddingLeft(); + final int top = getExtendedPaddingTop(); + + rect.set(left + layout.getLineLeft(0) - PADDING_H, + top + layout.getLineTop(0) - PADDING_V, + Math.min(left + layout.getLineRight(0) + PADDING_H, mScrollX + mRight - mLeft), + top + layout.getLineBottom(0) + PADDING_V); + canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, mPaint); + + super.draw(canvas); + } +} diff --git a/src/com/android/camera/BufferedInputStream.java b/src/com/android/camera/BufferedInputStream.java deleted file mode 100644 index 1505e87..0000000 --- a/src/com/android/camera/BufferedInputStream.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.camera; -/* -* This file derives from the Apache version of the BufferedInputStream. -* Mods to support passing in the buffer rather than creating one directly. -*/ -import java.io.InputStream; -import java.io.FilterInputStream; -import java.io.IOException; - -public class BufferedInputStream extends FilterInputStream { - protected byte[] buf; - protected int count; - protected int marklimit; - protected int markpos = -1; - protected int pos; - - private boolean closed = false; - - public BufferedInputStream(InputStream in, byte [] buffer) { - super(in); - buf = buffer; - if (buf == null) { - throw new java.security.InvalidParameterException(); - } - } - - @Override - public synchronized int available() throws IOException { - return count - pos + in.available(); - } - - @Override - public synchronized void close() throws IOException { - if (null != in) { - super.close(); - in = null; - } - buf = null; - closed = true; - } - - private int fillbuf() throws IOException { - if (markpos == -1 || (pos - markpos >= marklimit)) { - /* Mark position not set or exceeded readlimit */ - int result = in.read(buf); - if (result > 0) { - markpos = -1; - pos = 0; - count = result == -1 ? 0 : result; - } - return result; - } - if (markpos == 0 && marklimit > buf.length) { - /* Increase buffer size to accomodate the readlimit */ - int newLength = buf.length * 2; - if (newLength > marklimit) { - newLength = marklimit; - } - byte[] newbuf = new byte[newLength]; - System.arraycopy(buf, 0, newbuf, 0, buf.length); - buf = newbuf; - } else if (markpos > 0) { - System.arraycopy(buf, markpos, buf, 0, buf.length - markpos); - } - /* Set the new position and mark position */ - pos -= markpos; - count = markpos = 0; - int bytesread = in.read(buf, pos, buf.length - pos); - count = bytesread <= 0 ? pos : pos + bytesread; - return bytesread; - } - - @Override - public synchronized void mark(int readlimit) { - marklimit = readlimit; - markpos = pos; - } - - @Override - public boolean markSupported() { - return true; - } - - @Override - public synchronized int read() throws IOException { - if (in == null) { - // K0059=Stream is closed - throw new IOException(); //$NON-NLS-1$ - } - - /* Are there buffered bytes available? */ - if (pos >= count && fillbuf() == -1) { - return -1; /* no, fill buffer */ - } - - /* Did filling the buffer fail with -1 (EOF)? */ - if (count - pos > 0) { - return buf[pos++] & 0xFF; - } - return -1; - } - - @Override - public synchronized int read(byte[] buffer, int offset, int length) - throws IOException { - if (closed) { - // K0059=Stream is closed - throw new IOException(); //$NON-NLS-1$ - } - // avoid int overflow - if (offset > buffer.length - length || offset < 0 || length < 0) { - throw new IndexOutOfBoundsException(); - } - if (length == 0) { - return 0; - } - if (null == buf) { - throw new IOException(); //$NON-NLS-1$ - } - - int required; - if (pos < count) { - /* There are bytes available in the buffer. */ - int copylength = count - pos >= length ? length : count - pos; - System.arraycopy(buf, pos, buffer, offset, copylength); - pos += copylength; - if (copylength == length || in.available() == 0) { - return copylength; - } - offset += copylength; - required = length - copylength; - } else { - required = length; - } - - while (true) { - int read; - /* - * If we're not marked and the required size is greater than the - * buffer, simply read the bytes directly bypassing the buffer. - */ - if (markpos == -1 && required >= buf.length) { - read = in.read(buffer, offset, required); - if (read == -1) { - return required == length ? -1 : length - required; - } - } else { - if (fillbuf() == -1) { - return required == length ? -1 : length - required; - } - read = count - pos >= required ? required : count - pos; - System.arraycopy(buf, pos, buffer, offset, read); - pos += read; - } - required -= read; - if (required == 0) { - return length; - } - if (in.available() == 0) { - return length - required; - } - offset += read; - } - } - - @Override - public synchronized void reset() throws IOException { - if (closed) { - // K0059=Stream is closed - throw new IOException(); //$NON-NLS-1$ - } - if (-1 == markpos) { - // K005a=Mark has been invalidated. - throw new IOException(); //$NON-NLS-1$ - } - pos = markpos; - } - - @Override - public synchronized long skip(long amount) throws IOException { - if (null == in) { - // K0059=Stream is closed - throw new IOException(); //$NON-NLS-1$ - } - if (amount < 1) { - return 0; - } - - if (count - pos >= amount) { - pos += amount; - return amount; - } - long read = count - pos; - pos = count; - - if (markpos != -1) { - if (amount <= marklimit) { - if (fillbuf() == -1) { - return read; - } - if (count - pos >= amount - read) { - pos += amount - read; - return amount; - } - // Couldn't get all the bytes, skip what we read - read += (count - pos); - pos = count; - return read; - } - markpos = -1; - } - return read + in.skip(amount - read); - } -} diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index ef99842..b79e7d1 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -16,8 +16,16 @@ package com.android.camera; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; + import android.app.Activity; import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -46,11 +54,10 @@ import android.os.Handler; import android.os.Message; import android.os.StatFs; import android.os.SystemClock; -import android.pim.DateFormat; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.provider.MediaStore.Images; -import android.provider.MediaStore.Video; +import android.text.format.DateFormat; import android.util.Config; import android.util.Log; import android.view.KeyEvent; @@ -69,57 +76,60 @@ import android.view.animation.AnimationUtils; import android.widget.ImageView; import android.widget.Toast; -import java.util.ArrayList; - public class Camera extends Activity implements View.OnClickListener, SurfaceHolder.Callback { - + private static final String TAG = "camera"; - + private static final boolean DEBUG = false; - + private static final int CROP_MSG = 1; private static final int KEEP = 2; private static final int RESTART_PREVIEW = 3; private static final int CLEAR_SCREEN_DELAY = 4; - + private static final int SCREEN_DELAY = 2 * 60 * 1000; + private static final int POST_PICTURE_ALERT_TIMEOUT = 6 * 1000; private static final int FOCUS_BEEP_VOLUME = 100; private static final int NO_STORAGE_ERROR = -1; private static final int CANNOT_STAT_ERROR = -2; - + public static final int MENU_SWITCH_TO_VIDEO = 0; - public static final int MENU_FLASH_SETTING = 1; - public static final int MENU_FLASH_AUTO = 2; - public static final int MENU_FLASH_ON = 3; - public static final int MENU_FLASH_OFF = 4; - public static final int MENU_SETTINGS = 5; - public static final int MENU_GALLERY_PHOTOS = 6; + public static final int MENU_SWITCH_TO_CAMERA = 1; + public static final int MENU_FLASH_SETTING = 2; + public static final int MENU_FLASH_AUTO = 3; + public static final int MENU_FLASH_ON = 4; + public static final int MENU_FLASH_OFF = 5; + public static final int MENU_SETTINGS = 6; + public static final int MENU_GALLERY_PHOTOS = 7; + public static final int MENU_GALLERY_VIDEOS = 8; public static final int MENU_SAVE_SELECT_PHOTOS = 30; public static final int MENU_SAVE_NEW_PHOTO = 31; public static final int MENU_SAVE_SELECTVIDEO = 32; public static final int MENU_SAVE_TAKE_NEW_VIDEO = 33; public static final int MENU_SAVE_GALLERY_PHOTO = 34; - public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35; - public static final int MENU_SAVE_CAMERA_DONE = 36; + public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35; + public static final int MENU_SAVE_CAMERA_DONE = 36; public static final int MENU_SAVE_CAMERA_VIDEO_DONE = 37; - + Toast mToast; OrientationListener mOrientationListener; int mLastOrientation = OrientationListener.ORIENTATION_UNKNOWN; SharedPreferences mPreferences; - + static final int IDLE = 1; static final int SNAPSHOT_IN_PROGRESS = 2; static final int SNAPSHOT_COMPLETED = 3; - + int mStatus = IDLE; + static final String sTempCropFilename = "crop-temp"; android.hardware.Camera mCameraDevice; - SurfaceView mSurfaceView; + VideoPreview mSurfaceView; SurfaceHolder mSurfaceHolder = null; ImageView mBlackout = null; + int mOriginalViewFinderWidth, mOriginalViewFinderHeight; int mViewFinderWidth, mViewFinderHeight; boolean mPreviewing = false; @@ -127,7 +137,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol Capturer mCaptureObject; ImageCapture mImageCapture = null; - + boolean mPausing = false; boolean mIsFocusing = false; @@ -139,43 +149,39 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol boolean mDidRegister = false; int mCurrentZoomIndex = 0; - - private static final int STILL_MODE = 1; - private static final int VIDEO_MODE = 2; - private int mMode = STILL_MODE; ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); - + boolean mMenuSelectionMade; - + View mPostPictureAlert; LocationManager mLocationManager = null; private Animation mFocusBlinkAnimation; private View mFocusIndicator; private ToneGenerator mFocusToneGenerator; - + + private ShutterCallback mShutterCallback = new ShutterCallback(); private RawPictureCallback mRawPictureCallback = new RawPictureCallback(); - private JpegPictureCallback mJpegPictureCallback = new JpegPictureCallback(); private AutoFocusCallback mAutoFocusCallback = new AutoFocusCallback(); private long mShutterPressTime; private int mPicturesRemaining; private boolean mKeepAndRestartPreview; - private Handler mHandler = new MainHandler(); + private Handler mHandler = new MainHandler(); private ProgressDialog mSavingProgress; - + interface Capturer { - Uri getLastCaptureUri(); - void onSnap(); + Uri getLastCaptureUri(); + void onSnap(); void dismissFreezeFrame(boolean keep); - void cancelSave(); - void cancelAutoDismiss(); - void setDone(boolean wait); + void cancelSave(); + void cancelAutoDismiss(); + void setDone(boolean wait); } - + private void cancelSavingNotification() { if (mToast != null) { mToast.cancel(); @@ -194,15 +200,15 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mSavingProgress.cancel(); mSavingProgress = null; } - + mKeepAndRestartPreview = true; - + if (msg.obj != null) { mHandler.post((Runnable)msg.obj); } break; } - + case RESTART_PREVIEW: { if (mStatus == SNAPSHOT_IN_PROGRESS) { // We are still in the processing of taking the picture, wait. @@ -210,11 +216,12 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol // TODO remove polling mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, 100); } else if (mStatus == SNAPSHOT_COMPLETED){ + hidePostPictureAlert(); mCaptureObject.dismissFreezeFrame(true); } break; } - + case CLEAR_SCREEN_DELAY: { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); break; @@ -222,13 +229,13 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } }; - + LocationListener [] mLocationListeners = new LocationListener[] { new LocationListener(LocationManager.GPS_PROVIDER), new LocationListener(LocationManager.NETWORK_PROVIDER) }; - + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -248,17 +255,17 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } }; - + private class LocationListener implements android.location.LocationListener { Location mLastLocation; boolean mValid = false; String mProvider; - + public LocationListener(String provider) { mProvider = provider; mLastLocation = new Location(mProvider); } - + public void onLocationChanged(Location newLocation) { if (newLocation.getLatitude() == 0.0 && newLocation.getLongitude() == 0.0) { // Hack to filter out 0.0,0.0 locations @@ -267,7 +274,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mLastLocation.set(newLocation); mValid = true; } - + public void onProviderEnabled(String provider) { } @@ -280,12 +287,12 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mValid = false; } } - + public Location current() { return mValid ? mLastLocation : null; } }; - + private long mRawPictureCallbackTime; private boolean mImageSavingItem = false; @@ -302,25 +309,31 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } }; - - private final class RawPictureCallback implements PictureCallback { + + private final class RawPictureCallback implements PictureCallback { public void onPictureTaken(byte [] rawData, android.hardware.Camera camera) { if (Config.LOGV) Log.v(TAG, "got RawPictureCallback..."); mRawPictureCallbackTime = System.currentTimeMillis(); mBlackout.setVisibility(View.INVISIBLE); if (!isPickIntent() && mPreferences.getBoolean("pref_camera_postpicturemenu_key", true)) { - mPostPictureAlert.setVisibility(View.VISIBLE); + showPostPictureAlert(); } } }; - + private final class JpegPictureCallback implements PictureCallback { + Location mLocation; + + public JpegPictureCallback(Location loc) { + mLocation = loc; + } + public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) { if (Config.LOGV) Log.v(TAG, "got JpegPictureCallback..."); - mImageCapture.storeImage(jpegData, camera); + mImageCapture.storeImage(jpegData, camera, mLocation); mStatus = SNAPSHOT_COMPLETED; @@ -335,15 +348,15 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol if (mKeepAndRestartPreview) { mKeepAndRestartPreview = false; mPostPictureAlert.setVisibility(View.INVISIBLE); - + // Post this message so that we can finish processing the request. This also // prevents the preview from showing up before mPostPictureAlert is dismissed. mHandler.sendEmptyMessage(RESTART_PREVIEW); } - + } }; - + private final class AutoFocusCallback implements android.hardware.Camera.AutoFocusCallback { public void onAutoFocus(boolean focused, android.hardware.Camera camera) { mIsFocusing = false; @@ -354,42 +367,44 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mCaptureObject.onSnap(); clearFocus(); } else { - mFocusToneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP2); + ToneGenerator tg = mFocusToneGenerator; + if (tg != null) + tg.startTone(ToneGenerator.TONE_PROP_BEEP2); } mCaptureOnFocus = false; } updateFocusIndicator(); } }; - + private class ImageCapture implements Capturer { - + private boolean mCancel = false; private boolean mCapturing = false; - + private Uri mLastContentUri; private ImageManager.IAddImage_cancelable mAddImageCancelable; Bitmap mCaptureOnlyBitmap; - + /** These member variables are used for various debug timings */ private long mThreadTimeStart; private long mThreadTimeEnd; private long mWallTimeStart; private long mWallTimeEnd; - + public ImageCapture() { } - /** + /** * This method sets whether or not we are capturing a picture. This method must be called * with the ImageCapture.this lock held. */ public void setCapturingLocked(boolean capturing) { mCapturing = capturing; } - + /* * Tell the ImageCapture thread to exit when possible. */ @@ -409,7 +424,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } else { Toast.makeText(Camera.this, R.string.camera_tossing, Toast.LENGTH_SHORT).show(); } - + if (mStatus == SNAPSHOT_IN_PROGRESS) { // If we are still in the process of taking a picture, then just post a message. mHandler.sendEmptyMessage(RESTART_PREVIEW); @@ -417,31 +432,31 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol restartPreview(); } } - + private void startTiming() { mWallTimeStart = SystemClock.elapsedRealtime(); mThreadTimeStart = Debug.threadCpuTimeNanos(); } - + private void stopTiming() { mThreadTimeEnd = Debug.threadCpuTimeNanos(); mWallTimeEnd = SystemClock.elapsedRealtime(); } - - private void storeImage(byte[] data) { + + private void storeImage(byte[] data, Location loc) { try { if (DEBUG) { startTiming(); } - + long dateTaken = System.currentTimeMillis(); mLastContentUri = ImageManager.instance().addImage( Camera.this, mContentResolver, - DateFormat.format("yyyy-MM-dd kk.mm.ss", System.currentTimeMillis()).toString(), + createName(dateTaken), "", - System.currentTimeMillis(), + dateTaken, // location for the database goes here - null, + loc, 0, // the dsp will use the right orientation so don't "double set it" ImageManager.CAMERA_IMAGE_BUCKET_NAME, null); @@ -456,46 +471,46 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mAddImageCancelable.get(); mAddImageCancelable = null; } - + if (DEBUG) { stopTiming(); Log.d(TAG, "Storing image took " + (mWallTimeEnd - mWallTimeStart) + " ms. " + - "Thread time was " + ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + + "Thread time was " + ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + " ms."); } } catch (Exception ex) { Log.e(TAG, "Exception while compressing image.", ex); } } - - public void storeImage(byte[] data, android.hardware.Camera camera) { + + public void storeImage(byte[] data, android.hardware.Camera camera, Location loc) { boolean captureOnly = isPickIntent(); - + if (!captureOnly) { - storeImage(data); + storeImage(data, loc); sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", mLastContentUri)); } else { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 4; - + if (DEBUG) { startTiming(); } - + mCaptureOnlyBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); - + if (DEBUG) { stopTiming(); Log.d(TAG, "Decoded mCaptureOnly bitmap (" + mCaptureOnlyBitmap.getWidth() + - "x" + mCaptureOnlyBitmap.getHeight() + " ) in " + - (mWallTimeEnd - mWallTimeStart) + " ms. Thread time was " + + "x" + mCaptureOnlyBitmap.getHeight() + " ) in " + + (mWallTimeEnd - mWallTimeStart) + " ms. Thread time was " + ((mThreadTimeEnd - mThreadTimeStart) / 1000000) + " ms."); } - + openOptionsMenu(); } - - + + mCapturing = false; if (mPausing) { closeCamera(); @@ -510,9 +525,9 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol if (!mCapturing) { return; } - + mCancel = true; - + if (mAddImageCancelable != null) { mAddImageCancelable.cancel(); } @@ -526,25 +541,25 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol if (mCameraDevice == null) { return; } - + mCancel = false; mCapturing = true; capture(captureOnly); } - + public Uri getLastCaptureUri() { return mLastContentUri; } - + public Bitmap getLastBitmap() { return mCaptureOnlyBitmap; } - + private void capture(boolean captureOnly) { mPreviewing = false; mCaptureOnlyBitmap = null; - + final int latchedOrientation = ImageManager.roundOrientation(mLastOrientation + 90); Boolean recordLocation = mPreferences.getBoolean("pref_camera_recordlocation_key", false); @@ -554,35 +569,38 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol // get large. 85 is a good compromise between the two. parameters.set("jpeg-quality", 85); parameters.set("rotation", latchedOrientation); + + parameters.remove("gps-latitude"); + parameters.remove("gps-longitude"); + parameters.remove("gps-altitude"); + parameters.remove("gps-timestamp"); + if (loc != null) { - parameters.set("gps-latitude", String.valueOf(loc.getLatitude())); - parameters.set("gps-longitude", String.valueOf(loc.getLongitude())); - parameters.set("gps-altitude", String.valueOf(loc.getAltitude())); - parameters.set("gps-timestamp", String.valueOf(loc.getTime())); - } else { - parameters.remove("gps-latitude"); - parameters.remove("gps-longitude"); - parameters.remove("gps-altitude"); - parameters.remove("gps-timestamp"); + double lat = loc.getLatitude(); + double lon = loc.getLongitude(); + + if (lat != 0D && lon != 0d) { + parameters.set("gps-latitude", String.valueOf(lat)); + parameters.set("gps-longitude", String.valueOf(lon)); + if (loc.hasAltitude()) + parameters.set("gps-altitude", String.valueOf(loc.getAltitude())); + if (loc.getTime() != 0) + parameters.set("gps-timestamp", String.valueOf(loc.getTime())); + } else { + loc = null; + } } Size pictureSize = parameters.getPictureSize(); - Size previewSize = parameters.getPreviewSize(); // resize the SurfaceView to the aspect-ratio of the still image // and so that we can see the full image that was taken - ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams(); - if (pictureSize.width*previewSize.height < previewSize.width*pictureSize.height) { - lp.width = (pictureSize.width * previewSize.height) / pictureSize.height; - } else { - lp.height = (pictureSize.height * previewSize.width) / pictureSize.width; - } - mSurfaceView.requestLayout(); + mSurfaceView.setAspectRatio(pictureSize.width, pictureSize.height); mCameraDevice.setParameters(parameters); - - mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback); - + + mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, new JpegPictureCallback(loc)); + mBlackout.setVisibility(View.VISIBLE); // Comment this out for now until we can decode the preview frame. This currently // just animates black-on-black because the surface flinger blacks out the surface @@ -612,7 +630,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol showStorageToast(); return; } - + mStatus = SNAPSHOT_IN_PROGRESS; mKeepAndRestartPreview = !mPreferences.getBoolean("pref_camera_postpicturemenu_key", true); @@ -626,6 +644,11 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } + + static private String createName(long dateTaken) { + return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); + } + static public Matrix GetDisplayMatrix(Bitmap b, ImageView v) { Matrix m = new Matrix(); float bw = (float)b.getWidth(); @@ -649,12 +672,12 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol private void postAfterKeep(final Runnable r) { Resources res = getResources(); - + if (mSavingProgress != null) { - mSavingProgress = ProgressDialog.show(this, res.getString(R.string.savingImage), + mSavingProgress = ProgressDialog.show(this, res.getString(R.string.savingImage), res.getString(R.string.wait)); } - + Message msg = mHandler.obtainMessage(KEEP); msg.obj = r; msg.sendToTarget(); @@ -672,38 +695,35 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); requestWindowFeature(Window.FEATURE_PROGRESS); - + Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.camera); - mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview); - + mSurfaceView = (VideoPreview) findViewById(R.id.camera_preview); + // don't set mSurfaceHolder here. We have it set ONLY within // surfaceCreated / surfaceDestroyed, other parts of the code // assume that when it is set, the surface is also set. SurfaceHolder holder = mSurfaceView.getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - + mBlackout = (ImageView) findViewById(R.id.blackout); mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); mPostPictureAlert = findViewById(R.id.post_picture_panel); View b; - - b = findViewById(R.id.save); - b.setOnClickListener(this); - + b = findViewById(R.id.discard); b.setOnClickListener(this); - + b = findViewById(R.id.share); b.setOnClickListener(this); b = findViewById(R.id.setas); b.setOnClickListener(this); - + try { mClickSound = new MediaPlayer(); AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.camera_click); @@ -719,27 +739,27 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } catch (Exception ex) { Log.w(TAG, "Couldn't create click sound", ex); } - + mOrientationListener = new OrientationListener(this) { public void onOrientationChanged(int orientation) { mLastOrientation = orientation; } }; - + mFocusIndicator = findViewById(R.id.focus_indicator); mFocusBlinkAnimation = AnimationUtils.loadAnimation(this, R.anim.auto_focus_blink); mFocusBlinkAnimation.setRepeatCount(Animation.INFINITE); mFocusBlinkAnimation.setRepeatMode(Animation.REVERSE); } - + @Override public void onStart() { super.onStart(); - + final View hintView = findViewById(R.id.hint_toast); if (hintView != null) hintView.setVisibility(View.GONE); - + Thread t = new Thread(new Runnable() { public void run() { final boolean storageOK = calculatePicturesRemaining() > 0; @@ -761,7 +781,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol hintView.setVisibility(View.GONE); } }, 3000); - } else { + } else { mHandler.post(new Runnable() { public void run() { hintView.setVisibility(View.GONE); @@ -773,15 +793,17 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol }); t.start(); } - + public void onClick(View v) { switch (v.getId()) { + /* case R.id.save: { - mPostPictureAlert.setVisibility(View.INVISIBLE); + mPostPictureAlert.setVisibility(View.GONE); postAfterKeep(null); break; } - + */ + case R.id.discard: { if (mCaptureObject != null) { mCaptureObject.cancelSave(); @@ -791,12 +813,12 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } mCaptureObject.dismissFreezeFrame(true); } - mPostPictureAlert.setVisibility(View.INVISIBLE); + mPostPictureAlert.setVisibility(View.GONE); break; } - + case R.id.share: { - mPostPictureAlert.setVisibility(View.INVISIBLE); + mPostPictureAlert.setVisibility(View.GONE); postAfterKeep(new Runnable() { public void run() { Uri u = mCaptureObject.getLastCaptureUri(); @@ -807,15 +829,15 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol try { startActivity(Intent.createChooser(intent, getText(R.string.sendImage))); } catch (android.content.ActivityNotFoundException ex) { - Toast.makeText(Camera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); + Toast.makeText(Camera.this, R.string.no_way_to_share_image, Toast.LENGTH_SHORT).show(); } } }); break; } - + case R.id.setas: { - mPostPictureAlert.setVisibility(View.INVISIBLE); + mPostPictureAlert.setVisibility(View.GONE); postAfterKeep(new Runnable() { public void run() { Uri u = mCaptureObject.getLastCaptureUri(); @@ -831,9 +853,6 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } } - - void keepVideo() { - }; private void showStorageToast() { String noStorageText = null; @@ -854,14 +873,10 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol public void onResume() { super.onResume(); mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); - + mPausing = false; mOrientationListener.enable(); - if (isPickIntent()) { - mMode = STILL_MODE; - } - // install an intent filter to receive SD card related events. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); @@ -874,14 +889,19 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mImageCapture = new ImageCapture(); restartPreview(); - + if (mPreferences.getBoolean("pref_camera_recordlocation_key", false)) startReceivingLocationUpdates(); - + updateFocusIndicator(); - mFocusToneGenerator = new ToneGenerator(AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME); - + try { + mFocusToneGenerator = new ToneGenerator(AudioManager.STREAM_SYSTEM, FOCUS_BEEP_VOLUME); + } catch (RuntimeException e) { + Log.w(TAG, "Exception caught while creating local tone generator: " + e); + mFocusToneGenerator = null; + } + mBlackout.setVisibility(View.INVISIBLE); } @@ -905,7 +925,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mPausing = true; mOrientationListener.disable(); - + stopPreview(); if (!mImageCapture.mCapturing) { @@ -916,8 +936,11 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mDidRegister = false; } stopReceivingLocationUpdates(); - mFocusToneGenerator.release(); - mFocusToneGenerator = null; + + if (mFocusToneGenerator != null) { + mFocusToneGenerator.release(); + mFocusToneGenerator = null; + } super.onPause(); } @@ -935,6 +958,10 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } setResult(resultCode, intent); finish(); + + File path = getFileStreamPath(sTempCropFilename); + path.delete(); + break; } } @@ -950,13 +977,13 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } } - + private void clearFocus() { mIsFocusing = false; mIsFocused = false; mIsFocusButtonPressed = false; } - + private void updateFocusIndicator() { mHandler.post(new Runnable() { public void run() { @@ -976,10 +1003,12 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol }); } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) { @@ -1049,12 +1078,10 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - if (mMode == STILL_MODE) { - // if we're creating the surface, start the preview as well. - boolean preview = holder.isCreating(); - setViewFinder(w, h, preview); - mCaptureObject = mImageCapture; - } + // if we're creating the surface, start the preview as well. + boolean preview = holder.isCreating(); + setViewFinder(w, h, preview); + mCaptureObject = mImageCapture; } public void surfaceCreated(SurfaceHolder holder) { @@ -1065,7 +1092,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol stopPreview(); mSurfaceHolder = null; } - + private void closeCamera() { if (mCameraDevice != null) { mCameraDevice.release(); @@ -1073,26 +1100,23 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mPreviewing = false; } } - + private boolean ensureCameraDevice() { if (mCameraDevice == null) { mCameraDevice = android.hardware.Camera.open(); } return mCameraDevice != null; } - + private void restartPreview() { - SurfaceView surfaceView = mSurfaceView; - if (surfaceView == null || + VideoPreview surfaceView = mSurfaceView; + if (surfaceView == null || surfaceView.getWidth() == 0 || surfaceView.getHeight() == 0) { return; } // make sure the surfaceview fills the whole screen when previewing - ViewGroup.LayoutParams lp = surfaceView.getLayoutParams(); - lp.width = ViewGroup.LayoutParams.FILL_PARENT; - lp.height = ViewGroup.LayoutParams.FILL_PARENT; - surfaceView.requestLayout(); - setViewFinder(mViewFinderWidth, mViewFinderHeight, true); + surfaceView.setAspectRatio(VideoPreview.DONT_CARE); + setViewFinder(mOriginalViewFinderWidth, mOriginalViewFinderHeight, true); mStatus = IDLE; // Calculate this in advance of each shot so we don't add to shutter latency. It's true that @@ -1102,37 +1126,41 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol // let the user take a picture, and delete that file if needed to save the new photo. calculatePicturesRemaining(); } - + private void setViewFinder(int w, int h, boolean startPreview) { if (mPausing) return; - - if (mPreviewing && - w == mViewFinderWidth && + + if (mPreviewing && + w == mViewFinderWidth && h == mViewFinderHeight) { return; } - + if (!ensureCameraDevice()) return; - + if (mSurfaceHolder == null) return; - + if (isFinishing()) return; - + if (mPausing) return; - + // remember view finder size mViewFinderWidth = w; mViewFinderHeight = h; + if (mOriginalViewFinderHeight == 0) { + mOriginalViewFinderWidth = w; + mOriginalViewFinderHeight = h; + } if (startPreview == false) return; - /* + /* * start the preview if we're asked to... */ @@ -1140,21 +1168,31 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol // stop the preview first (this will blank the screen). if (mPreviewing) stopPreview(); - + // this blanks the screen if the surface changed, no-op otherwise - mCameraDevice.setPreviewDisplay(mSurfaceHolder); - + try { + mCameraDevice.setPreviewDisplay(mSurfaceHolder); + } catch (IOException exception) { + mCameraDevice.release(); + mCameraDevice = null; + // TODO: add more exception handling logic here + return; + } // request the preview size, the hardware may not honor it, // if we depended on it we would have to query the size again android.hardware.Camera.Parameters p = mCameraDevice.getParameters(); p.setPreviewSize(w, h); - mCameraDevice.setParameters(p); - - + try { + mCameraDevice.setParameters(p); + } catch (IllegalArgumentException e) { + // Ignore this error, it happens in the simulator. + } + + final long wallTimeStart = SystemClock.elapsedRealtime(); final long threadTimeStart = Debug.threadCpuTimeNanos(); - + final Object watchDogSync = new Object(); Thread watchDog = new Thread(new Runnable() { public void run() { @@ -1169,7 +1207,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } if (mPreviewing) break; - + int delay = (int) (SystemClock.elapsedRealtime() - wallTimeStart) / 1000; if (delay >= next_warning) { if (delay < 120) { @@ -1194,19 +1232,24 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol if (Config.LOGV) Log.v(TAG, "calling mCameraDevice.startPreview"); - mCameraDevice.startPreview(); + try { + mCameraDevice.startPreview(); + } catch (Throwable e) { + // TODO: change Throwable to IOException once android.hardware.Camera.startPreview + // properly declares that it throws IOException. + } mPreviewing = true; - + synchronized (watchDogSync) { watchDogSync.notify(); } - + long threadTimeEnd = Debug.threadCpuTimeNanos(); long wallTimeEnd = SystemClock.elapsedRealtime(); if ((wallTimeEnd - wallTimeStart) > 3000) { Log.w(TAG, "startPreview() to " + (wallTimeEnd - wallTimeStart) + " ms. Thread time was" + (threadTimeEnd - threadTimeStart) / 1000000 + " ms."); - } + } } private void stopPreview() { @@ -1217,10 +1260,13 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } void gotoGallery() { - Uri target = mMode == STILL_MODE ? Images.Media.INTERNAL_CONTENT_URI - : Video.Media.INTERNAL_CONTENT_URI; + Uri target = Images.Media.INTERNAL_CONTENT_URI; Intent intent = new Intent(Intent.ACTION_VIEW, target); - startActivity(intent); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Could not start gallery activity", e); + } } void keep() { @@ -1236,27 +1282,27 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mCaptureObject.cancelSave(); } }; - + private ImageManager.IImage getImageForURI(Uri uri) { ImageManager.IImageList list = ImageManager.instance().allImages( this, - mContentResolver, - dataLocation(), - ImageManager.INCLUDE_IMAGES, + mContentResolver, + dataLocation(), + ImageManager.INCLUDE_IMAGES, ImageManager.SORT_ASCENDING); ImageManager.IImage image = list.getImageForUri(uri); list.deactivate(); return image; } - - + + private void startReceivingLocationUpdates() { if (mLocationManager != null) { try { mLocationManager.requestLocationUpdates( - LocationManager.NETWORK_PROVIDER, - 1000, - 0F, + LocationManager.NETWORK_PROVIDER, + 1000, + 0F, mLocationListeners[1]); } catch (java.lang.SecurityException ex) { // ok @@ -1267,9 +1313,9 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } try { mLocationManager.requestLocationUpdates( - LocationManager.GPS_PROVIDER, - 1000, - 0F, + LocationManager.GPS_PROVIDER, + 1000, + 0F, mLocationListeners[0]); } catch (java.lang.SecurityException ex) { // ok @@ -1280,7 +1326,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } } - + private void stopReceivingLocationUpdates() { if (mLocationManager != null) { for (int i = 0; i < mLocationListeners.length; i++) { @@ -1292,18 +1338,20 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } } } - + private Location getCurrentLocation() { Location l = null; - - // go in worst to best order - for (int i = 0; i < mLocationListeners.length && l == null; i++) { + + // go in best to worst order + for (int i = 0; i < mLocationListeners.length; i++) { l = mLocationListeners[i].current(); + if (l != null) + break; } - + return l; } - + @Override public void onOptionsMenuClosed(Menu menu) { super.onOptionsMenuClosed(menu); @@ -1315,7 +1363,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol mHandler.sendEmptyMessage(RESTART_PREVIEW); } } - + @Override public boolean onMenuOpened(int featureId, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL) { @@ -1327,28 +1375,25 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } return super.onMenuOpened(featureId, menu); } - + @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); mMenuSelectionMade = false; - + for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) { if (i != MenuHelper.GENERIC_ITEM) { menu.setGroupVisible(i, false); } } - - if (mMode == STILL_MODE) { - if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) { - menu.setGroupVisible(MenuHelper.IMAGE_SAVING_ITEM, true); - mImageSavingItem = true; - } else { - menu.setGroupVisible(MenuHelper.IMAGE_MODE_ITEM, true); - mImageSavingItem = false; - } - } else if (mMode == VIDEO_MODE) { + + if (mStatus == SNAPSHOT_IN_PROGRESS || mStatus == SNAPSHOT_COMPLETED) { + menu.setGroupVisible(MenuHelper.IMAGE_SAVING_ITEM, true); + mImageSavingItem = true; + } else { + menu.setGroupVisible(MenuHelper.IMAGE_MODE_ITEM, true); + mImageSavingItem = false; } if (mCaptureObject != null) @@ -1361,7 +1406,7 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol String action = getIntent().getAction(); return (Intent.ACTION_PICK.equals(action) || MediaStore.ACTION_IMAGE_CAPTURE.equals(action)); } - + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); @@ -1372,39 +1417,106 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol Bitmap bitmap = mImageCapture.getLastBitmap(); mCaptureObject.setDone(true); - // TODO scale the image down to something ridiculous until IPC gets straightened out - float scale = .5F; - Matrix m = new Matrix(); - m.setScale(scale, scale); + String cropValue = null; + Uri saveUri = null; - bitmap = Bitmap.createBitmap(bitmap, 0, 0, - bitmap.getWidth(), - bitmap.getHeight(), - m, true); - Bundle myExtras = getIntent().getExtras(); - String cropValue = myExtras != null ? myExtras.getString("crop") : null; - if (cropValue != null) { + if (myExtras != null) { + saveUri = (Uri) myExtras.getParcelable("output"); + cropValue = myExtras.getString("crop"); + } + + + if (cropValue == null) { + /* + * First handle the no crop case -- just return the value. If the caller + * specifies a "save uri" then write the data to it's stream. Otherwise, + * pass back a scaled down version of the bitmap directly in the extras. + */ + if (saveUri != null) { + OutputStream outputStream = null; + try { + outputStream = mContentResolver.openOutputStream(saveUri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream); + outputStream.close(); + + setResult(RESULT_OK); + finish(); + } catch (IOException ex) { + // + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ex) { + + } + } + } + } else { + float scale = .5F; + Matrix m = new Matrix(); + m.setScale(scale, scale); + + bitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), + bitmap.getHeight(), + m, true); + + setResult(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap)); + finish(); + } + } + else { + /* + * Save the image to a temp file and invoke the cropper + */ + Uri tempUri = null; + FileOutputStream tempStream = null; + try { + File path = getFileStreamPath(sTempCropFilename); + path.delete(); + tempStream = openFileOutput(sTempCropFilename, 0); + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, tempStream); + tempStream.close(); + tempUri = Uri.fromFile(path); + } catch (FileNotFoundException ex) { + setResult(Activity.RESULT_CANCELED); + finish(); + return true; + } catch (IOException ex) { + setResult(Activity.RESULT_CANCELED); + finish(); + return true; + } finally { + if (tempStream != null) { + try { + tempStream.close(); + } catch (IOException ex) { + + } + } + } + Bundle newExtras = new Bundle(); if (cropValue.equals("circle")) newExtras.putString("circleCrop", "true"); - newExtras.putParcelable("data", bitmap); + if (saveUri != null) + newExtras.putParcelable("output", saveUri); + else + newExtras.putBoolean("return-data", true); Intent cropIntent = new Intent(); cropIntent.setClass(Camera.this, CropImage.class); + cropIntent.setData(tempUri); cropIntent.putExtras(newExtras); + startActivityForResult(cropIntent, CROP_MSG); - } else { - Bundle extras = new Bundle(); - extras.putParcelable("data", bitmap); - setResult(RESULT_OK, new Intent("inline-data") - .putExtra("data", bitmap)); - finish(); } return true; } }); - + menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_NEW_PHOTO, 0, R.string.camera_takenewphoto).setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { keep(); @@ -1422,9 +1534,10 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol MenuHelper.addImageMenuItems( menu, MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU, + true, Camera.this, mHandler, - + // Handler for deletion new Runnable() { public void run() { @@ -1461,20 +1574,12 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } }); gallery.setIcon(android.R.drawable.ic_menu_gallery); - - mGalleryItems.add(menu.add(MenuHelper.VIDEO_SAVING_ITEM, MENU_SAVE_GALLERY_VIDEO_PHOTO, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - keepVideo(); - gotoGallery(); - return true; - } - })); - } + } return true; } - + SelectedImageGetter mSelectedImageGetter = - new SelectedImageGetter() { + new SelectedImageGetter() { public ImageManager.IImage getCurrentImage() { return getImageForURI(getCurrentImageUri()); } @@ -1502,16 +1607,29 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol } return mPicturesRemaining; } - + private void addBaseMenuItems(Menu menu) { - MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - gotoGallery(); - return true; - } - }); - gallery.setIcon(android.R.drawable.ic_menu_gallery); - mGalleryItems.add(gallery); + MenuHelper.addSwitchModeMenuItem(menu, this, true); + { + MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + gotoGallery(); + return true; + } + }); + gallery.setIcon(android.R.drawable.ic_menu_gallery); + mGalleryItems.add(gallery); + } + { + MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + gotoGallery(); + return true; + } + }); + gallery.setIcon(android.R.drawable.ic_menu_gallery); + mGalleryItems.add(gallery); + } MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -1523,5 +1641,20 @@ public class Camera extends Activity implements View.OnClickListener, SurfaceHol }); item.setIcon(android.R.drawable.ic_menu_preferences); } + + private void showPostPictureAlert() { + mPostPictureAlert.setVisibility(View.VISIBLE); + mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, POST_PICTURE_ALERT_TIMEOUT); + } + + private void hidePostPictureAlert() { + cancelRestartPreviewTimeout(); + mPostPictureAlert.setVisibility(View.INVISIBLE); + } + + private void cancelRestartPreviewTimeout() { + mHandler.removeMessages(RESTART_PREVIEW); + } + } diff --git a/src/com/android/camera/CameraButtonIntentReceiver.java b/src/com/android/camera/CameraButtonIntentReceiver.java index ccf5821..5e4d3c3 100644 --- a/src/com/android/camera/CameraButtonIntentReceiver.java +++ b/src/com/android/camera/CameraButtonIntentReceiver.java @@ -19,11 +19,9 @@ package com.android.camera; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; -import android.util.Config; -import android.util.Log; import android.view.KeyEvent; -class CameraButtonIntentReceiver extends BroadcastReceiver { +public class CameraButtonIntentReceiver extends BroadcastReceiver { public CameraButtonIntentReceiver() { } diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index fb77e34..0c0f31b 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -16,50 +16,29 @@ package com.android.camera; -import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.Preference; import android.preference.PreferenceActivity; -import android.content.Context; /** * CameraSettings */ -class CameraSettings extends PreferenceActivity +public class CameraSettings extends PreferenceActivity { public CameraSettings() { } - + protected int resourceId() { return R.xml.camera_preferences; } - + /** Called with the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); addPreferencesFromResource(resourceId()); - - Preference p = findPreference("pref_camera_upload_albumname_key"); - if (p != null) { - SharedPreferences sp = p.getSharedPreferences(); - p.setSummary( - String.format(getResources().getString(R.string.pref_camera_upload_albumname_summary), - p.getSharedPreferences().getString(p.getKey(), UploadService.sUploadAlbumName))); - p.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference p, Object newObjValue) { - String newValue = (String) newObjValue; - if (newValue == null || newValue.length() == 0) - return false; - if (android.util.Config.LOGV) - android.util.Log.v("camera", "onPreferenceChange ... " + newValue); - p.setSummary(String.format(getResources().getString(R.string.pref_camera_upload_albumname_summary), newValue)); - return true; - } - }); - } + } } diff --git a/src/com/android/camera/ExifInterface.java b/src/com/android/camera/ExifInterface.java index de2fd5c..2db021a 100644 --- a/src/com/android/camera/ExifInterface.java +++ b/src/com/android/camera/ExifInterface.java @@ -59,7 +59,7 @@ public class ExifInterface { private HashMap<String, String> mCachedAttributes = null; static { - System.loadLibrary("exif"); + System.loadLibrary("exif"); } public ExifInterface(String fileName) { diff --git a/src/com/android/camera/GalleryPicker.java b/src/com/android/camera/GalleryPicker.java index 9f1664b..da946a0 100644 --- a/src/com/android/camera/GalleryPicker.java +++ b/src/com/android/camera/GalleryPicker.java @@ -29,6 +29,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -56,29 +57,31 @@ import android.widget.AdapterView.AdapterContextMenuInfo; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; public class GalleryPicker extends Activity { static private final String TAG = "GalleryPicker"; - + GridView mGridView; Drawable mFrameGalleryMask; Drawable mCellOutline; + Drawable mVideoOverlay; BroadcastReceiver mReceiver; GalleryPickerAdapter mAdapter; - + Dialog mMediaScanningDialog; - + MenuItem mFlipItem; SharedPreferences mPrefs; - + boolean mPausing = false; - + private static long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2; - + public GalleryPicker() { } - + private void rebake(boolean unmounted, boolean scanning) { if (mMediaScanningDialog != null) { mMediaScanningDialog.cancel(); @@ -86,16 +89,16 @@ public class GalleryPicker extends Activity { } if (scanning) { mMediaScanningDialog = ProgressDialog.show( - this, - null, - getResources().getString(R.string.wait), - true, + this, + null, + getResources().getString(R.string.wait), + true, true); } mAdapter.notifyDataSetChanged(); mAdapter.init(!unmounted && !scanning); } - + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -103,12 +106,12 @@ public class GalleryPicker extends Activity { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); setContentView(R.layout.gallerypicker); - - mGridView = (GridView) findViewById(R.id.albums); + + mGridView = (GridView) findViewById(R.id.albums); mGridView.setSelector(android.R.color.transparent); mReceiver = new BroadcastReceiver() { - + @Override public void onReceive(Context context, Intent intent) { if (Config.LOGV) Log.v(TAG, "onReceiveIntent " + intent.getAction()); @@ -136,7 +139,7 @@ public class GalleryPicker extends Activity { } } }; - + mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { launchFolderGallery(position); @@ -144,34 +147,35 @@ public class GalleryPicker extends Activity { }); mGridView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, final ContextMenu.ContextMenuInfo menuInfo) { - menu.setHeaderTitle( - mAdapter.baseTitleForPosition(((AdapterContextMenuInfo)menuInfo).position)); - menu.add(0, 207, 0, R.string.slide_show) - .setOnMenuItemClickListener(new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; - int position = info.position; - - Uri targetUri; - synchronized (mAdapter.mFirstImageUris) { - if (position >= mAdapter.mFirstImageUris.size()) { - // the list of ids does not include the "all" list - targetUri = mAdapter.firstImageUri(mAdapter.mIds.get(position-1)); - } else { + int position = ((AdapterContextMenuInfo)menuInfo).position; + menu.setHeaderTitle(mAdapter.baseTitleForPosition(position)); + if ((mAdapter.getIncludeMediaTypes(position) & ImageManager.INCLUDE_IMAGES) != 0) { + menu.add(0, 207, 0, R.string.slide_show) + .setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; + int position = info.position; + + Uri targetUri; + synchronized (mAdapter.mItems) { + if (position < 0 || position >= mAdapter.mItems.size()) { + return true; + } // the mFirstImageUris list includes the "all" uri - targetUri = mAdapter.mFirstImageUris.get(position); + targetUri = mAdapter.mItems.get(position).mFirstImageUri; } + if (targetUri != null && position > 0) { + targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", + mAdapter.mItems.get(info.position).mId).build(); + } + // Log.v(TAG, "URI to launch slideshow " + targetUri); + Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); + intent.putExtra("slideshow", true); + startActivity(intent); + return true; } - if (targetUri != null && position > 0) { - targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", mAdapter.mIds.get(info.position-1)).build(); - } -// Log.v(TAG, "URI to launch slideshow " + targetUri); - Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); - intent.putExtra("slideshow", true); - startActivity(intent); - return true; - } - }); + }); + } menu.add(0, 208, 0, R.string.view) .setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -183,56 +187,118 @@ public class GalleryPicker extends Activity { } }); } - + private void launchFolderGallery(int position) { - android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI; - if (position > 0) { - uri = uri.buildUpon().appendQueryParameter("bucketId", mAdapter.mIds.get(position-1)).build(); - } - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if (position > 0) { - intent.putExtra("windowTitle", mAdapter.mNames.get(position-1)); - } - startActivity(intent); + mAdapter.mItems.get(position).launch(this); } - + class ItemInfo { Bitmap bitmap; int count; - int overlayId; } - + + static class Item implements Comparable<Item>{ + // The type is also used as the sort order + public final static int TYPE_ALL_IMAGES = 0; + public final static int TYPE_ALL_VIDEOS = 1; + public final static int TYPE_CAMERA_IMAGES = 2; + public final static int TYPE_CAMERA_VIDEOS = 3; + public final static int TYPE_NORMAL_FOLDERS = 4; + + public int mType; + public String mId; + public String mName; + public Uri mFirstImageUri; + public ItemInfo mThumb; + + public Item(int type, String id, String name) { + mType = type; + mId = id; + mName = name; + } + + public boolean needsBucketId() { + return mType >= TYPE_CAMERA_IMAGES; + } + + public void launch(Activity activity) { + android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI; + if (needsBucketId()) { + uri = uri.buildUpon().appendQueryParameter("bucketId",mId).build(); + } + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.putExtra("windowTitle", mName); + intent.putExtra("mediaTypes", getIncludeMediaTypes()); + activity.startActivity(intent); + } + + public int getIncludeMediaTypes() { + return convertItemTypeToIncludedMediaType(mType); + } + + public static int convertItemTypeToIncludedMediaType(int itemType) { + switch (itemType) { + case TYPE_ALL_IMAGES: + case TYPE_CAMERA_IMAGES: + return ImageManager.INCLUDE_IMAGES; + case TYPE_ALL_VIDEOS: + case TYPE_CAMERA_VIDEOS: + return ImageManager.INCLUDE_VIDEOS; + case TYPE_NORMAL_FOLDERS: + default: + return ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS; + } + } + + public int getOverlay() { + switch (mType) { + case TYPE_ALL_IMAGES: + case TYPE_CAMERA_IMAGES: + return R.drawable.frame_overlay_gallery_camera; + case TYPE_ALL_VIDEOS: + case TYPE_CAMERA_VIDEOS: + return R.drawable.frame_overlay_gallery_video; + case TYPE_NORMAL_FOLDERS: + return R.drawable.frame_overlay_gallery_folder; + default: + return -1; + } + } + + // sort based on the sort order, then the case-insensitive display name, then the id. + public int compareTo(Item other) { + int x = mType - other.mType; + if (x == 0) { + x = mName.compareToIgnoreCase(other.mName); + if (x == 0) { + x = mId.compareTo(other.mId); + } + } + return x; + } + } + class GalleryPickerAdapter extends BaseAdapter { - ArrayList<String> mIds = new ArrayList<String>(); - ArrayList<String> mNames = new ArrayList<String>(); - ArrayList<Uri> mFirstImageUris = new ArrayList<Uri>(); - - ArrayList<View> mAllViews = new ArrayList<View>(); - SparseArray<ItemInfo> mThumbs = new SparseArray<ItemInfo>(); - + ArrayList<Item> mItems = new ArrayList<Item>(); + boolean mDone = false; CameraThread mWorkerThread; public void init(boolean assumeMounted) { - mAllViews.clear(); - mThumbs.clear(); - + mItems.clear(); + ImageManager.IImageList images; if (assumeMounted) { images = ImageManager.instance().allImages( GalleryPicker.this, - getContentResolver(), - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_IMAGES, + getContentResolver(), + ImageManager.DataLocation.ALL, + ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS, ImageManager.SORT_DESCENDING); } else { images = ImageManager.instance().emptyImageList(); } - mIds.clear(); - mNames.clear(); - mFirstImageUris.clear(); - if (mWorkerThread != null) { try { mDone = true; @@ -243,72 +309,56 @@ public class GalleryPicker extends Activity { mWorkerThread = null; } } - + String cameraItem = ImageManager.CAMERA_IMAGE_BUCKET_ID; final HashMap<String, String> hashMap = images.getBucketIds(); String cameraBucketId = null; - for (String key : hashMap.keySet()) { + for (Map.Entry<String, String> entry: hashMap.entrySet()) { + String key = entry.getKey(); if (key.equals(cameraItem)) { cameraBucketId = key; } else { - mIds.add(key); + mItems.add(new Item(Item.TYPE_NORMAL_FOLDERS, key, entry.getValue())); } } images.deactivate(); notifyDataSetInvalidated(); - - // sort baesd on the display name. if two display names compare equal - // then sort based on the id - java.util.Collections.sort(mIds, new java.util.Comparator<String>() { - public int compare(String first, String second) { - int x = hashMap.get(first).compareTo(hashMap.get(second)); - if (x == 0) - x = first.compareTo(second); - return x; - } - }); - for (String s : mIds) { - mNames.add(hashMap.get(s)); - } + // If just one + addBucketIfNotEmpty(Item.TYPE_ALL_IMAGES, null, R.string.all_images); + addBucketIfNotEmpty(Item.TYPE_ALL_VIDEOS, null, R.string.all_videos); if (cameraBucketId != null) { - mIds.add(0, cameraBucketId); - mNames.add(0, "Camera"); + addBucketIfNotEmpty(Item.TYPE_CAMERA_IMAGES, cameraBucketId, + R.string.gallery_camera_bucket_name); + addBucketIfNotEmpty(Item.TYPE_CAMERA_VIDEOS, cameraBucketId, + R.string.gallery_camera_videos_bucket_name); } - final boolean foundCameraBucket = cameraBucketId != null; - + + java.util.Collections.sort(mItems); + mDone = false; mWorkerThread = new CameraThread(new Runnable() { public void run() { try { // no images, nothing to do - if (mIds.size() == 0) + if (mItems.size() == 0) return; - - for (int i = 0; i < mIds.size() + 1 && !mDone; i++) { - String id = i == 0 ? null : mIds.get(i-1); - ImageManager.IImageList list = ImageManager.instance().allImages( - GalleryPicker.this, - getContentResolver(), - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_IMAGES, - ImageManager.SORT_DESCENDING, - id); + + for (int i = 0; i < mItems.size() && !mDone; i++) { + final Item item = mItems.get(i); + ImageManager.IImageList list = createImageList( + item.getIncludeMediaTypes(), item.mId); try { if (mPausing) { break; } if (list.getCount() > 0) - mFirstImageUris.add(i, list.getImageAt(0).fullSizeImageUri()); + item.mFirstImageUri = list.getImageAt(0).fullSizeImageUri(); - int overlay = -1; - if (i == 1 && foundCameraBucket) - overlay = R.drawable.frame_overlay_gallery_camera; final Bitmap b = makeMiniThumbBitmap(142, 142, list); final int pos = i; final int count = list.getCount(); - final int overlayId = overlay; final Thread currentThread = Thread.currentThread(); mHandler.post(new Runnable() { public void run() { @@ -318,19 +368,18 @@ public class GalleryPicker extends Activity { } return; } - + ItemInfo info = new ItemInfo(); info.bitmap = b; info.count = count; - info.overlayId = overlayId; - mThumbs.put(pos, info); - + item.mThumb = info; + final GridView grid = GalleryPicker.this.mGridView; final int firstVisible = grid.getFirstVisiblePosition(); - + // Minor optimization -- only notify if the specified position is visible if ((pos >= firstVisible) && (pos < firstVisible + grid.getChildCount())) { - GalleryPickerAdapter.this.notifyDataSetChanged(); + GalleryPickerAdapter.this.notifyDataSetChanged(); } } }); @@ -339,29 +388,22 @@ public class GalleryPicker extends Activity { } } } catch (Exception ex) { - Log.e(TAG, "got exception generating collage views " + ex.toString()); + Log.e(TAG, "got exception generating collage views ", ex); } } }); mWorkerThread.start(); mWorkerThread.toBackground(); } - - Uri firstImageUri(String id) { - ImageManager.IImageList list = ImageManager.instance().allImages( - GalleryPicker.this, - getContentResolver(), - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_IMAGES, - ImageManager.SORT_DESCENDING, - id); - Uri uri = list.getImageAt(0).fullSizeImageUri(); - list.deactivate(); - return uri; + + private void addBucketIfNotEmpty(int itemType, String bucketId, int labelId) { + if (!isEmptyBucket(Item.convertItemTypeToIncludedMediaType(itemType), bucketId)) { + mItems.add(new Item(itemType, bucketId, getResources().getString(labelId))); + } } public int getCount() { - return mIds.size() + 1; // add 1 for the everything bucket + return mItems.size(); } public Object getItem(int position) { @@ -371,41 +413,39 @@ public class GalleryPicker extends Activity { public long getItemId(int position) { return position; } - + private String baseTitleForPosition(int position) { - if (position == 0) { - return getResources().getString(R.string.all_images); - } else { - return mNames.get(position-1); - } + return mItems.get(position).mName; + } + + private int getIncludeMediaTypes(int position) { + return mItems.get(position).getIncludeMediaTypes(); } public View getView(final int position, View convertView, ViewGroup parent) { View v; - + if (convertView == null) { LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.gallery_picker_item, null); } else { v = convertView; } - + TextView titleView = (TextView) v.findViewById(R.id.title); - GalleryPickerItem iv = (GalleryPickerItem) v.findViewById(R.id.thumbnail); - ItemInfo info = mThumbs.get(position); + iv.setOverlay(mItems.get(position).getOverlay()); + ItemInfo info = mItems.get(position).mThumb; if (info != null) { iv.setImageBitmap(info.bitmap); - iv.setOverlay(info.overlayId); String title = baseTitleForPosition(position) + " (" + info.count + ")"; titleView.setText(title); } else { iv.setImageResource(android.R.color.transparent); - iv.setOverlay(-1); titleView.setText(baseTitleForPosition(position)); } - + return v; } }; @@ -415,13 +455,13 @@ public class GalleryPicker extends Activity { super.onPause(); mPausing = true; unregisterReceiver(mReceiver); - + // free up some ram mAdapter = null; mGridView.setAdapter(null); System.gc(); } - + @Override public void onResume() { super.onResume(); @@ -433,7 +473,7 @@ public class GalleryPicker extends Activity { boolean scanning = ImageManager.isMediaScannerScanning(this); rebake(false, scanning); - + // install an intent filter to receive SD card related events. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); @@ -455,10 +495,10 @@ public class GalleryPicker extends Activity { StatFs stat = new StatFs(storageDirectory); long remaining = (long)stat.getAvailableBlocks() * (long)stat.getBlockSize(); if (remaining < LOW_STORAGE_THRESHOLD) { - + mHandler.post(new Runnable() { public void run() { - Toast.makeText(GalleryPicker.this.getApplicationContext(), + Toast.makeText(GalleryPicker.this.getApplicationContext(), R.string.not_enough_space, 5000).show(); } }); @@ -467,8 +507,10 @@ public class GalleryPicker extends Activity { } }); t.start(); - - if (!scanning && mAdapter.mIds.size() <= 1) { + + // If we just have one folder, open it. (Probably never triggered because we always have + // At least two folders now.) + if (!scanning && mAdapter.mItems.size() <= 1) { android.net.Uri uri = Images.Media.INTERNAL_CONTENT_URI; Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); @@ -476,26 +518,27 @@ public class GalleryPicker extends Activity { return; } } - + private void setBackgrounds(Resources r) { mFrameGalleryMask = r.getDrawable(R.drawable.frame_gallery_preview_album_mask); mCellOutline = r.getDrawable(android.R.drawable.gallery_thumb); + mVideoOverlay = r.getDrawable(R.drawable.ic_gallery_video_overlay); } - + Handler mHandler = new Handler(); - + private void placeImage(Bitmap image, Canvas c, Paint paint, int imageWidth, int widthPadding, int imageHeight, int heightPadding, int offsetX, int offsetY, int pos) { int row = pos / 2; int col = pos - (row * 2); - + int xPos = (col * (imageWidth + widthPadding)) - offsetX; int yPos = (row * (imageHeight + heightPadding)) - offsetY; - + c.drawBitmap(image, xPos, yPos, paint); } - + private Bitmap makeMiniThumbBitmap(int width, int height, ImageManager.IImageList images) { int count = images.getCount(); // We draw three different version of the folder image depending on the number of images in the folder. @@ -507,30 +550,20 @@ public class GalleryPicker extends Activity { int imageHeight = height; int offsetWidth = 0; int offsetHeight = 0; - if (count < 4) { - count = 1; - // uncomment for 2 pictures per frame -// if (count == 2 || count == 3) { -// count = 2; -// imageWidth = imageWidth * 2 / 3; -// imageHeight = imageHeight * 2 / 3; -// offsetWidth = imageWidth / 3 - padding; -// offsetHeight = -imageHeight / 3 + padding * 2; - } else if (count >= 4) { - count = 4; - imageWidth = (imageWidth - padding) / 2; // 2 here because we show two images - imageHeight = (imageHeight - padding) / 2; // per row and column - } + + imageWidth = (imageWidth - padding) / 2; // 2 here because we show two images + imageHeight = (imageHeight - padding) / 2; // per row and column + final Paint p = new Paint(); final Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas c = new Canvas(b); - + final Matrix m = new Matrix(); - + // draw the whole canvas as transparent p.setColor(0x00000000); c.drawPaint(p); - + // draw the mask normally p.setColor(0xFFFFFFFF); mFrameGalleryMask.setBounds(0, 0, width, height); @@ -542,17 +575,34 @@ public class GalleryPicker extends Activity { pdpaint.setStyle(Paint.Style.FILL); c.drawRect(0, 0, width, height, pdpaint); - - for (int i = 0; i < count; i++) { + + for (int i = 0; i < 4; i++) { if (mPausing) { return null; } + + Bitmap temp = null; ImageManager.IImage image = i < count ? images.getImageAt(i) : null; - if (image == null) { - break; + + if (image != null) { + temp = image.miniThumbBitmap(); } - Bitmap temp = image.miniThumbBitmap(); + if (temp != null) { + if (ImageManager.isVideo(image)) { + Bitmap newMap = temp.copy(temp.getConfig(), true); + Canvas overlayCanvas = new Canvas(newMap); + int overlayWidth = mVideoOverlay.getIntrinsicWidth(); + int overlayHeight = mVideoOverlay.getIntrinsicHeight(); + int left = (newMap.getWidth() - overlayWidth) / 2; + int top = (newMap.getHeight() - overlayHeight) / 2; + Rect newBounds = new Rect(left, top, left + overlayWidth, top + overlayHeight); + mVideoOverlay.setBounds(newBounds); + mVideoOverlay.draw(overlayCanvas); + temp.recycle(); + temp = newMap; + } + Bitmap temp2 = ImageLoader.transform(m, temp, imageWidth, imageHeight, true); if (temp2 != temp) temp.recycle(); @@ -567,22 +617,24 @@ public class GalleryPicker extends Activity { mCellOutline.draw(tempCanvas); placeImage(thumb, c, pdpaint, imageWidth, padding, imageHeight, padding, offsetWidth, offsetHeight, i); - + thumb.recycle(); - + if (temp != null) temp.recycle(); } + return b; } - + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - + + MenuHelper.addCaptureMenuItems(menu, this); mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); - menu.add(0, 0, 0, R.string.camerasettings) + menu.add(0, 0, 5, R.string.camerasettings) .setOnMenuItemClickListener(new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { Intent preferences = new Intent(); @@ -597,11 +649,32 @@ public class GalleryPicker extends Activity { return true; } - @Override + @Override public boolean onPrepareOptionsMenu(android.view.Menu menu) { int keyboard = getResources().getConfiguration().keyboardHidden; mFlipItem.setEnabled(keyboard == android.content.res.Configuration.KEYBOARDHIDDEN_YES); return true; } + + private boolean isEmptyBucket(int mediaTypes, String bucketId) { + // TODO: Find a more efficient way of calculating this + ImageManager.IImageList list = createImageList(mediaTypes, bucketId); + try { + return list.isEmpty(); + } + finally { + list.deactivate(); + } + } + + private ImageManager.IImageList createImageList(int mediaTypes, String bucketId) { + return ImageManager.instance().allImages( + this, + getContentResolver(), + ImageManager.DataLocation.ALL, + mediaTypes, + ImageManager.SORT_DESCENDING, + bucketId); + } } diff --git a/src/com/android/camera/GallerySettings.java b/src/com/android/camera/GallerySettings.java index 3af6867..8cbeba2 100644 --- a/src/com/android/camera/GallerySettings.java +++ b/src/com/android/camera/GallerySettings.java @@ -25,7 +25,7 @@ import android.content.Context; /** * GallerySettings */ -class GallerySettings extends CameraSettings +public class GallerySettings extends CameraSettings { public GallerySettings() { diff --git a/src/com/android/camera/HighlightView.java b/src/com/android/camera/HighlightView.java index 594bab6..408beab 100644 --- a/src/com/android/camera/HighlightView.java +++ b/src/com/android/camera/HighlightView.java @@ -376,35 +376,35 @@ public class HighlightView float heightUnits = widthUnits; switch (keyCode) - { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (mMode == ModifyMode.Move) - moveBy(-widthUnits, 0); - else if (mMode == ModifyMode.Grow) - growBy(-widthUnits, 0); - break; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (mMode == ModifyMode.Move) - moveBy(widthUnits, 0); + { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (mMode == ModifyMode.Move) + moveBy(-widthUnits, 0); else if (mMode == ModifyMode.Grow) - growBy(widthUnits, 0); - break; - - case KeyEvent.KEYCODE_DPAD_UP: - if (mMode == ModifyMode.Move) - moveBy(0, -heightUnits); + growBy(-widthUnits, 0); + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (mMode == ModifyMode.Move) + moveBy(widthUnits, 0); + else if (mMode == ModifyMode.Grow) + growBy(widthUnits, 0); + break; + + case KeyEvent.KEYCODE_DPAD_UP: + if (mMode == ModifyMode.Move) + moveBy(0, -heightUnits); else if (mMode == ModifyMode.Grow) - growBy(0, -heightUnits); - break; + growBy(0, -heightUnits); + break; - case KeyEvent.KEYCODE_DPAD_DOWN: - if (mMode == ModifyMode.Move) - moveBy(0, heightUnits); + case KeyEvent.KEYCODE_DPAD_DOWN: + if (mMode == ModifyMode.Move) + moveBy(0, heightUnits); else if (mMode == ModifyMode.Grow) - growBy(0, heightUnits); - break; - } + growBy(0, heightUnits); + break; + } } enum ModifyMode { None, Move,Grow }; diff --git a/src/com/android/camera/ImageGallery2.java b/src/com/android/camera/ImageGallery2.java index c8abdae..44d297b 100644 --- a/src/com/android/camera/ImageGallery2.java +++ b/src/com/android/camera/ImageGallery2.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; @@ -51,11 +52,14 @@ import android.view.Window; import android.widget.TextView; import android.widget.Toast; import android.preference.PreferenceManager; +import android.provider.MediaStore; import android.widget.Scroller; import java.util.Calendar; import java.util.GregorianCalendar; +import com.android.camera.ImageManager.IImage; + public class ImageGallery2 extends Activity { private static final String TAG = "ImageGallery2"; private ImageManager.IImageList mAllImages; @@ -66,10 +70,11 @@ public class ImageGallery2 extends Activity { public final static int VIEW_MSG = 3; private static final String INSTANCE_STATE_TAG = "scrollY"; - + private Dialog mMediaScanningDialog; - + private MenuItem mFlipItem; + private MenuItem mSlideShowItem; private SharedPreferences mPrefs; public ImageGallery2() { @@ -115,11 +120,15 @@ public class ImageGallery2 extends Activity { } }); - menu.setHeaderTitle(R.string.context_menu_header); - if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { + boolean isImage = ImageManager.isImage(mSelectedImageGetter.getCurrentImage()); + + menu.setHeaderTitle(isImage ? R.string.context_menu_header + : R.string.video_context_menu_header); + if ((mInclusion & (ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS)) != 0) { MenuHelper.MenuItemsResult r = MenuHelper.addImageMenuItems( menu, MenuHelper.INCLUDE_ALL, + isImage, ImageGallery2.this, mHandler, mDeletePhotoRunnable, @@ -136,37 +145,15 @@ public class ImageGallery2 extends Activity { if (r != null) r.gettingReadyToOpen(menu, mSelectedImageGetter.getCurrentImage()); - addSlideShowMenu(menu, 1000); - } - - if ((mInclusion & ImageManager.INCLUDE_VIDEOS) != 0) { - MenuHelper.MenuItemsResult r = MenuHelper.addVideoMenuItems( - menu, - MenuHelper.INCLUDE_ALL, - ImageGallery2.this, - mHandler, - mSelectedImageGetter, - new Runnable() { - public void run() { - ImageManager.IImage image = mSelectedImageGetter.getCurrentImage(); - if (image != null) { - mGvs.clearCache(); - mAllImages.removeImage(mSelectedImageGetter.getCurrentImage()); - mGvs.invalidate(); - mGvs.start(); - mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE : View.VISIBLE); - } - } - }, - null, null); - if (r != null) - r.gettingReadyToOpen(menu, mSelectedImageGetter.getCurrentImage()); + if (isImage) { + addSlideShowMenu(menu, 1000); + } } } }); } } - + private MenuItem addSlideShowMenu(Menu menu, int position) { return menu.add(0, 207, position, R.string.slide_show) .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @@ -194,7 +181,7 @@ public class ImageGallery2 extends Activity { }) .setIcon(android.R.drawable.ic_menu_slideshow); } - + private Runnable mDeletePhotoRunnable = new Runnable() { public void run() { mGvs.clearCache(); @@ -204,7 +191,7 @@ public class ImageGallery2 extends Activity { mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE : View.VISIBLE); } }; - + private SelectedImageGetter mSelectedImageGetter = new SelectedImageGetter() { public Uri getCurrentImageUri() { ImageManager.IImage image = getCurrentImage(); @@ -227,20 +214,20 @@ public class ImageGallery2 extends Activity { super.onConfigurationChanged(newConfig); mTargetScroll = mGvs.getScrollY(); } - + private Runnable mLongPressCallback = new Runnable() { public void run() { mGvs.showContextMenu(); } }; - + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { // The keyUp doesn't get called when the longpress menu comes up. We only get here when the user // lets go of the center key before the longpress menu comes up. mHandler.removeCallbacks(mLongPressCallback); - + // open the photo if (mSelectedImageGetter.getCurrentImage() != null) { mGvs.onSelect(mGvs.mCurrentSelection); @@ -282,7 +269,8 @@ public class ImageGallery2 extends Activity { mHandler.postDelayed(mLongPressCallback, ViewConfiguration.getLongPressTimeout()); break; case KeyEvent.KEYCODE_DEL: - MenuHelper.deletePhoto(this, mDeletePhotoRunnable); + MenuHelper.deleteImage(this, mDeletePhotoRunnable, + mSelectedImageGetter.getCurrentImage()); break; default: handled = false; @@ -402,10 +390,10 @@ public class ImageGallery2 extends Activity { } if (scanning) { mMediaScanningDialog = ProgressDialog.show( - this, - null, - getResources().getString(R.string.wait), - true, + this, + null, + getResources().getString(R.string.wait), + true, true); mAllImages = ImageManager.instance().emptyImageList(); } else { @@ -418,7 +406,7 @@ public class ImageGallery2 extends Activity { checkThumbnails(); } } - + @Override protected void onSaveInstanceState(Bundle state) { super.onSaveInstanceState(state); @@ -431,7 +419,7 @@ public class ImageGallery2 extends Activity { super.onRestoreInstanceState(state); mTargetScroll = state.getInt(INSTANCE_STATE_TAG, 0); } - + int mTargetScroll; @Override @@ -474,7 +462,7 @@ public class ImageGallery2 extends Activity { rebake(true, false); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { Toast.makeText(ImageGallery2.this, getResources().getString(R.string.wait), 5000); - rebake(false, true); + rebake(false, true); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { if (Config.LOGV) Log.v(TAG, "rebake because of ACTION_MEDIA_SCANNER_FINISHED"); @@ -489,10 +477,10 @@ public class ImageGallery2 extends Activity { registerReceiver(mReceiver, intentFilter); MenuHelper.requestOrientation(this, mPrefs); - + rebake(false, ImageManager.isMediaScannerScanning(this)); } - + private void stopCheckingThumbnails() { mStopThumbnailChecking = true; if (mThumbnailCheckThread != null) { @@ -523,7 +511,7 @@ public class ImageGallery2 extends Activity { if (mStopThumbnailChecking) { return false; } - + if (!mLayoutComplete) { return true; } @@ -563,11 +551,11 @@ public class ImageGallery2 extends Activity { Log.v(TAG, "check thumbnails thread finishing; took " + (t2-t1)); } }); - + mThumbnailCheckThread.setName("check_thumbnails"); mThumbnailCheckThread.start(); mThumbnailCheckThread.toBackground(); - + ImageManager.IImageList list = allImages(true); mNoImagesView.setVisibility(list.getCount() > 0 ? View.GONE : View.VISIBLE); } @@ -575,21 +563,12 @@ public class ImageGallery2 extends Activity { @Override public boolean onCreateOptionsMenu(android.view.Menu menu) { MenuItem item; - if (false) { - if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { - item = menu.add(0, 0, 0, R.string.upload_all); - item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - UploadAction.uploadImage(ImageGallery2.this, null); - return true; - } - }); - item.setIcon(android.R.drawable.ic_menu_upload); - } - } - addSlideShowMenu(menu, 0); + MenuHelper.addCaptureMenuItems(menu, this); + if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { + mSlideShowItem = addSlideShowMenu(menu, 5); - mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); + mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); + } item = menu.add(0, 0, 1000, R.string.camerasettings); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @@ -605,15 +584,32 @@ public class ImageGallery2 extends Activity { return true; } - - @Override + + @Override public boolean onPrepareOptionsMenu(android.view.Menu menu) { - int keyboard = getResources().getConfiguration().keyboardHidden; - mFlipItem.setEnabled(keyboard == android.content.res.Configuration.KEYBOARDHIDDEN_YES); + if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { + boolean imageSelected = isImageSelected(); + boolean videoSelected = isVideoSelected(); + int keyboard = getResources().getConfiguration().keyboardHidden; + mFlipItem.setEnabled(imageSelected + && (keyboard == android.content.res.Configuration.KEYBOARDHIDDEN_YES)); + // TODO: Only enable slide show if there is at least one image in the folder. + mSlideShowItem.setEnabled(!videoSelected); + } return true; } + private boolean isImageSelected() { + IImage image = mSelectedImageGetter.getCurrentImage(); + return (image != null) && ImageManager.isImage(image); + } + + private boolean isVideoSelected() { + IImage image = mSelectedImageGetter.getCurrentImage(); + return (image != null) && ImageManager.isVideo(image); + } + private synchronized ImageManager.IImageList allImages(boolean assumeMounted) { if (mAllImages == null) { mNoImagesView = findViewById(R.id.no_images); @@ -636,6 +632,11 @@ public class ImageGallery2 extends Activity { leftText.setText(R.string.photos_gallery_title); } if (type.equals("vnd.android.cursor.dir/video") || type.equals("video/*")) { + mInclusion = ImageManager.INCLUDE_VIDEOS; + if (isPickIntent()) + leftText.setText(R.string.pick_videos_gallery_title); + else + leftText.setText(R.string.videos_gallery_title); } } Bundle extras = intent.getExtras(); @@ -644,6 +645,11 @@ public class ImageGallery2 extends Activity { leftText.setText(title); } + if (extras != null) { + mInclusion = (ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS) + & extras.getInt("mediaTypes", mInclusion); + } + if (extras != null && extras.getBoolean("pick-drm")) { Log.d(TAG, "pick-drm is true"); mInclusion = ImageManager.INCLUDE_DRM_IMAGES; @@ -651,7 +657,8 @@ public class ImageGallery2 extends Activity { } } if (Config.LOGV) - Log.v(TAG, "computing images... mSortAscending is " + mSortAscending + "; assumeMounted is " + assumeMounted); + Log.v(TAG, "computing images... mSortAscending is " + mSortAscending + + "; assumeMounted is " + assumeMounted); Uri uri = getIntent().getData(); if (!assumeMounted) { mAllImages = ImageManager.instance().emptyImageList(); @@ -758,7 +765,7 @@ public class ImageGallery2 extends Activity { velocityY = maxVelocity; else if (velocityY < -maxVelocity) velocityY = -maxVelocity; - + select(-1); if (mFling) { mScroller = new Scroller(getContext()); @@ -891,8 +898,8 @@ public class ImageGallery2 extends Activity { if (mGallery.isFinishing() || mGallery.mPausing) { return; - } - + } + clearCache(); mCurrentSpec = mCellSizeChoices[mSizeChoice]; @@ -905,7 +912,7 @@ public class ImageGallery2 extends Activity { mCurrentSpec.mLeftEdgePadding = ((right - left) - ((mCurrentSpec.mColumns - 1) * mCurrentSpec.mCellSpacing) - (mCurrentSpec.mColumns * mCurrentSpec.mCellWidth)) / 2; mCurrentSpec.mRightEdgePadding = mCurrentSpec.mLeftEdgePadding; - + int rows = (mGallery.mAllImages.getCount() + mCurrentSpec.mColumns - 1) / mCurrentSpec.mColumns; mMaxScrollY = mCurrentSpec.mCellSpacing + (rows * (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight)) - (bottom - top) + mMaxOvershoot; mMinScrollY = 0 - mMaxOvershoot; @@ -950,9 +957,10 @@ public class ImageGallery2 extends Activity { private int mWorkCounter = 0; private boolean mDone = false; - + private Thread mWorkerThread; - private Bitmap mErrorBitmap; + private Bitmap mMissingImageThumbnailBitmap; + private Bitmap mMissingVideoThumbnailBitmap; public void dump() { synchronized (ImageBlockManager.this) { @@ -1027,12 +1035,20 @@ public class ImageGallery2 extends Activity { } // Create this bitmap lazily, and only once for all the ImageBlocks to use - public Bitmap getErrorBitmap() { - if (mErrorBitmap == null) { - mErrorBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(), - android.R.drawable.ic_menu_report_image); + public Bitmap getErrorBitmap(ImageManager.IImage image) { + if (ImageManager.isImage(image)) { + if (mMissingImageThumbnailBitmap == null) { + mMissingImageThumbnailBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(), + R.drawable.ic_missing_thumbnail_picture); + } + return mMissingImageThumbnailBitmap; + } else { + if (mMissingVideoThumbnailBitmap == null) { + mMissingVideoThumbnailBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(), + R.drawable.ic_missing_thumbnail_video); + } + return mMissingVideoThumbnailBitmap; } - return mErrorBitmap; } private ImageBlock getBlockForPos(int pos) { @@ -1299,6 +1315,7 @@ public class ImageGallery2 extends Activity { int mRequestedMask; // columns which have been requested to the loader int mCompletedMask; // columns which have been completed from the loader boolean mIsVisible; + Drawable mVideoOverlay; public void dump(StringBuilder line1, StringBuilder line2) { synchronized (ImageBlock.this) { @@ -1315,7 +1332,7 @@ public class ImageGallery2 extends Activity { mBlockNumber = -1; mCellOutline = GridViewSpecial.this.getResources().getDrawable(android.R.drawable.gallery_thumb); } - + private void recycleBitmaps() { synchronized (ImageBlock.this) { mBitmap.recycle(); @@ -1434,13 +1451,13 @@ public class ImageGallery2 extends Activity { // change in the future. int w = mCurrentSpec.mCellWidth; int h = mCurrentSpec.mCellHeight; - + int bw = b.getWidth(); int bh = b.getHeight(); int deltaW = bw - w; int deltaH = bh - h; - + if (deltaW < 10 && deltaH < 10) { int halfDeltaW = deltaW / 2; int halfDeltaH = deltaH / 2; @@ -1460,7 +1477,7 @@ public class ImageGallery2 extends Activity { } } else { // If the thumbnail cannot be drawn, put up an error icon instead - Bitmap error = mImageBlockManager.getErrorBitmap(); + Bitmap error = mImageBlockManager.getErrorBitmap(image); int width = error.getWidth(); int height = error.getHeight(); Rect source = new Rect(0, 0, width, height); @@ -1469,6 +1486,19 @@ public class ImageGallery2 extends Activity { Rect dest = new Rect(left, top, left + width, top + height); mCanvas.drawBitmap(error, source, dest, mPaint); } + if (ImageManager.isVideo(image)) { + if (mVideoOverlay == null) { + mVideoOverlay = getResources().getDrawable( + R.drawable.ic_gallery_video_overlay); + } + int width = mVideoOverlay.getIntrinsicWidth(); + int height = mVideoOverlay.getIntrinsicHeight(); + int left = (mCurrentSpec.mCellWidth - width) / 2 + xPos; + int top = (mCurrentSpec.mCellHeight - height) / 2 + yPos; + Rect newBounds = new Rect(left, top, left + width, top + height); + mVideoOverlay.setBounds(newBounds); + mVideoOverlay.draw(mCanvas); + } paintSel(base + baseOffset, xPos, yPos); } @@ -1524,7 +1554,7 @@ public class ImageGallery2 extends Activity { // Log.v(TAG, "wanted block " + mBlockNumber + " but got " + startBlock); return; } - + if (mBitmap == null) { return; } @@ -1644,11 +1674,11 @@ public class ImageGallery2 extends Activity { } Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); - // this should be unnecessary but if you remove this line then executing - // the subsequent startActivity causes the user to have to choose among - // ViewImage and a number of bogus entries (like attaching the image to - // a contact). - intent.setClass(mContext, ViewImage.class); + if (img instanceof ImageManager.VideoObject) { + intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + try { mContext.startActivity(intent); } catch (Exception ex) { diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java index 8d3f90a..1fe93b5 100755 --- a/src/com/android/camera/ImageManager.java +++ b/src/com/android/camera/ImageManager.java @@ -25,8 +25,11 @@ import android.database.Cursor; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Matrix; import android.location.Location; +import android.media.MediaMetadataRetriever; +import android.media.MediaPlayer; import android.net.Uri; import android.os.Environment; import android.os.Handler; @@ -36,8 +39,10 @@ import android.provider.DrmStore; import android.provider.MediaStore; import android.provider.MediaStore.Images.ImageColumns; import android.provider.MediaStore.Images.Thumbnails; +import android.provider.MediaStore.Video.VideoColumns; import android.provider.MediaStore.Images; import android.provider.MediaStore.MediaColumns; +import android.provider.MediaStore.Video; import android.util.Config; import android.util.Log; @@ -69,7 +74,7 @@ public class ImageManager { private static final String TAG = "ImageManager"; private static final int MINI_THUMB_DATA_FILE_VERSION = 3; - + static public void debug_where(String tag, String msg) { try { throw new Exception(); @@ -98,11 +103,11 @@ public class ImageManager { private static int computeSampleSize(BitmapFactory.Options options, int target) { int w = options.outWidth; int h = options.outHeight; - + int candidateW = w / target; int candidateH = h / target; int candidate = Math.max(candidateW, candidateH); - + if (candidate == 0) return 1; @@ -110,7 +115,7 @@ public class ImageManager { if ((w > target) && (w / candidate) < target) candidate -= 1; } - + if (candidate > 1) { if ((h > target) && (h / candidate) < target) candidate -= 1; @@ -121,7 +126,7 @@ public class ImageManager { return candidate; } - /* + /* * All implementors of ICancelable should inherit from BaseCancelable * since it provides some convenience methods such as acknowledgeCancel * and checkCancel. @@ -129,10 +134,10 @@ public class ImageManager { public abstract class BaseCancelable implements ICancelable { boolean mCancel = false; boolean mFinished = false; - + /* * Subclasses should call acknowledgeCancel when they're finished with - * their operation. + * their operation. */ protected void acknowledgeCancel() { synchronized (this) { @@ -161,7 +166,7 @@ public class ImageManager { } catch (InterruptedException ex) { // now what??? TODO } - + return retVal; } } @@ -205,13 +210,13 @@ public class ImageManager { protected BaseImage(long id, long miniThumbId, ContentResolver cr, BaseImageList container, int cursorRow) { mContentResolver = cr; mId = id; - mMiniThumbMagic = miniThumbId; + mMiniThumbMagic = miniThumbId; mContainer = container; mCursorRow = cursorRow; } - + abstract Bitmap.CompressFormat compressionType(); - + public void commitChanges() { Cursor c = getCursor(); synchronized (c) { @@ -221,7 +226,7 @@ public class ImageManager { } } } - + /** * Take a given bitmap and compress it to a file as described * by the Uri parameter. @@ -248,7 +253,7 @@ public class ImageManager { } return false; } - + public boolean get() { try { long t1 = System.currentTimeMillis(); @@ -297,7 +302,7 @@ public class ImageManager { return false; if (!(other instanceof Image)) return false; - + return fullSizeImageUri().equals(((Image)other).fullSizeImageUri()); } @@ -327,11 +332,11 @@ public class ImageManager { ParcelFileDescriptor mPFD; BitmapFactory.Options mOptions = new BitmapFactory.Options(); long mCancelInitiationTime; - + public LoadBitmapCancelable(ParcelFileDescriptor pfdInput) { mPFD = pfdInput; } - + public boolean doCancelWork() { if (VERBOSE) Log.v(TAG, "requesting bitmap load cancel"); @@ -339,7 +344,7 @@ public class ImageManager { mOptions.requestCancelDecode(); return true; } - + public Bitmap get() { try { Bitmap b = makeBitmap(targetWidthHeight, fullSizeImageUri(), mPFD, mOptions); @@ -386,7 +391,7 @@ public class ImageManager { return null; } } - + public long fullSizeImageId() { return mId; } @@ -394,11 +399,11 @@ public class ImageManager { public Uri fullSizeImageUri() { return mContainer.contentUri(mId); } - + public IImageList getContainer() { return mContainer; } - + Cursor getCursor() { return mContainer.getCursor(); } @@ -411,7 +416,7 @@ public class ImageManager { return c.getLong(mContainer.indexDateTaken()); } } - + protected int getDegreesRotated() { return 0; } @@ -597,7 +602,7 @@ public class ImageManager { } } } - + public int getHeight() { ParcelFileDescriptor input = null; try { @@ -660,7 +665,7 @@ public class ImageManager { protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, BitmapFactory.Options options) { return mContainer.makeBitmap(targetWidthHeight, uri, pfdInput, options); } - + /* (non-Javadoc) * @see com.android.camera.IImage#thumb1() */ @@ -702,7 +707,7 @@ public class ImageManager { return null; } } - + public void onRemove() { mContainer.mCache.remove(mId); } @@ -736,7 +741,7 @@ public class ImageManager { } } } - + /* (non-Javadoc) * @see com.android.camera.IImage#setName() */ @@ -748,7 +753,7 @@ public class ImageManager { } } } - + public void setPicasaId(String id) { Cursor c = null; try { @@ -772,7 +777,7 @@ public class ImageManager { c.close(); } } - + /* (non-Javadoc) * @see com.android.camera.IImage#thumbUri() */ @@ -782,7 +787,7 @@ public class ImageManager { uri = uri.buildUpon().appendQueryParameter("thumb", "1").build(); return uri; } - + @Override public String toString() { return fullSizeImageUri().toString(); @@ -810,16 +815,17 @@ public class ImageManager { mSort = sort; mUri = uri; mBaseUri = uri; + mBucketId = bucketId; mContentResolver = cr; } - + String randomAccessFilePath(int version) { String directoryName = Environment.getExternalStorageDirectory().toString() + "/dcim/.thumbnails"; String path = directoryName + "/.thumbdata" + version + "-" + mUri.hashCode(); return path; } - + RandomAccessFile miniThumbDataFile() { if (mMiniThumbData == null) { String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION); @@ -834,7 +840,7 @@ public class ImageManager { try { mMiniThumbData = new RandomAccessFile(f, "rw"); } catch (IOException ex) { - + } } return mMiniThumbData; @@ -853,14 +859,14 @@ public class ImageManager { return thumb; } OutputStream thumbOut = mContentResolver.openOutputStream(uri); - thumb.compress(Bitmap.CompressFormat.JPEG, 60, thumbOut); + thumb.compress(Bitmap.CompressFormat.JPEG, 60, thumbOut); thumbOut.close(); return thumb; } catch (Exception ex) { Log.d(TAG, "unable to store thumbnail: " + ex); return thumb; - } + } } /** @@ -924,11 +930,11 @@ public class ImageManager { } return uri; } - + java.util.Random mRandom = new java.util.Random(System.currentTimeMillis()); protected SomewhatFairLock mLock = new SomewhatFairLock(); - + class SomewhatFairLock { private Object mSync = new Object(); private boolean mLocked = false; @@ -954,7 +960,7 @@ public class ImageManager { mLocked = true; } } - + void unlock() { // if (VERBOSE) Log.v(TAG, "unlocking... thread " + Thread.currentThread().getId()); synchronized (mSync) { @@ -991,7 +997,7 @@ public class ImageManager { // which will produce much better scaling quality // and is significantly faster. options.inSampleSize = computeSampleSize(options, THUMBNAIL_TARGET_SIZE); - + if (VERBOSE) { Log.v(TAG, "in createThumbnailFromExif using inSampleSize of " + options.inSampleSize); } @@ -1019,7 +1025,20 @@ public class ImageManager { } return bitmap; } - + + private Bitmap createVideoThumbnail(String filePath) { + Bitmap bitmap = null; + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); + retriever.setDataSource(filePath); + bitmap = retriever.captureFrame(); + } finally { + retriever.release(); + } + return bitmap; + } + // returns id public long checkThumbnail(BaseImage existingImage, Cursor c, int i) { long magic, fileMagic = 0, id; @@ -1042,7 +1061,7 @@ public class ImageManager { } if (magic != 0) { - // check the mini thumb file for the right data. Right is defined as + // check the mini thumb file for the right data. Right is defined as // having the right magic number at the offset reserved for this "id". RandomAccessFile r = miniThumbDataFile(); if (r != null) { @@ -1072,18 +1091,23 @@ public class ImageManager { // If we can't retrieve the thumbnail, first check if there is one embedded in the // EXIF data. If not, or it's not big enough, decompress the full size image. Bitmap bitmap = null; - + String filePath = null; synchronized (c) { if (c.moveToPosition(i)) { filePath = c.getString(indexData()); } } - if (filePath != null) { bitmap = createThumbnailFromEXIF(filePath, id); if (bitmap == null) { - bitmap = createThumbnailFromUri(c, id); + String mimeType = c.getString(indexMimeType()); + boolean isVideo = isVideoMimeType(mimeType); + if (isVideo) { + bitmap = createVideoThumbnail(filePath); + } else { + bitmap = createThumbnailFromUri(c, id); + } } synchronized (c) { int degrees = 0; @@ -1141,7 +1165,7 @@ public class ImageManager { Log.v(TAG, ">>>>>>>>>>> need to check " + c.getCount() + " rows"); c.close(); - + if (!ImageManager.hasStorage()) { if (VERBOSE) Log.v(TAG, "bailing from the image checker thread -- no storage"); @@ -1158,7 +1182,7 @@ public class ImageManager { return; } } - + c = getCursor(); try { if (VERBOSE) Log.v(TAG, "checkThumbnails found " + c.getCount()); @@ -1188,7 +1212,7 @@ public class ImageManager { } } } - + public void commitChanges() { synchronized (mCursor) { mCursor.commitUpdates(); @@ -1208,16 +1232,21 @@ public class ImageManager { return ContentUris.withAppendedId(mBaseUri, id); } } - + public void deactivate() { mCursorDeactivated = true; - mCursor.deactivate(); + try { + mCursor.deactivate(); + } catch (IllegalStateException e) { + // IllegalStateException may be thrown if the cursor is stale. + Log.e(TAG, "Caught exception while deactivating cursor.", e); + } if (mMiniThumbData != null) { try { mMiniThumbData.close(); mMiniThumbData = null; } catch (IOException ex) { - + } } } @@ -1244,6 +1273,11 @@ public class ImageManager { return 0; } } + + public boolean isEmpty() { + return getCount() == 0; + } + protected Cursor getCursor() { synchronized (mCursor) { if (mCursorDeactivated) { @@ -1252,11 +1286,11 @@ public class ImageManager { return mCursor; } } - + protected void activateCursor() { requery(); } - + public IImage getImageAt(int i) { Cursor c = getCursor(); synchronized (c) { @@ -1307,7 +1341,7 @@ public class ImageManager { RandomAccessFile r = miniThumbDataFile(); if (r == null) return null; - + long pos = id * sBytesPerMiniThumb; RandomAccessFile f = r; synchronized (f) { @@ -1349,7 +1383,7 @@ public class ImageManager { } index += 1; } while (c.moveToNext()); - } + } return -1; } } @@ -1368,7 +1402,7 @@ public class ImageManager { protected abstract int indexTitle(); protected abstract int indexDisplayName(); protected abstract int indexThumbId(); - + protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { return null; } @@ -1400,7 +1434,7 @@ public class ImageManager { return false; } - + /* (non-Javadoc) * @see com.android.camera.IImageList#removeImageAt(int) */ @@ -1460,7 +1494,7 @@ public class ImageManager { } r.seek(pos); r.writeByte(0); // we have no data in this slot - + // if magic is 0 then leave it alone if (magic == 0) r.skipBytes(8); @@ -1468,11 +1502,11 @@ public class ImageManager { r.writeLong(magic); r.writeInt(data.length); r.write(data); - // f.flush(); + // f.flush(); r.seek(pos); r.writeByte(1); // we have data in this slot long t3 = System.currentTimeMillis(); - + if (VERBOSE) Log.v(TAG, "saveMiniThumbToFile took " + (t3-t0) + "; " + (t1-t0) + " " + (t2-t1) + " " + (t3-t2)); } } catch (IOException ex) { @@ -1486,9 +1520,9 @@ public class ImageManager { mHandler = h; } } - + public class CanceledException extends Exception { - + } public enum DataLocation { NONE, INTERNAL, EXTERNAL, ALL } @@ -1502,17 +1536,17 @@ public class ImageManager { * from an ICancelable can be retrieved using the get* method. If the * operation was canceled then null is returned. The act of canceling * is to call "cancel" -- from another thread. - * - * In general an object which implements ICancelable will need to + * + * In general an object which implements ICancelable will need to * check, periodically, whether they are canceled or not. This works * well for some things and less well for others. - * + * * Right now the actual jpeg encode does not check cancelation but * the part of encoding which writes the data to disk does. Note, * though, that there is what appears to be a bug in the jpeg encoder * in that if the stream that's being written is closed it crashes * rather than returning an error. TODO fix that. - * + * * When an object detects that it is canceling it must, before exiting, * call acknowledgeCancel. This is necessary because the caller of * cancel() will block until acknowledgeCancel is called. @@ -1526,7 +1560,7 @@ public class ImageManager { */ public boolean cancel(); } - + public interface IGetBitmap_cancelable extends ICancelable { // returns the bitmap or null if there was an error or we were canceled public Bitmap get(); @@ -1543,9 +1577,9 @@ public class ImageManager { * @return the bitmap for the full size image. */ public abstract Bitmap fullSizeBitmap(int targetWidthOrHeight); - + /** - * + * * @return an object which can be canceled while the bitmap is loading */ public abstract IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthOrHeight); @@ -1589,7 +1623,7 @@ public class ImageManager { public abstract String getDisplayName(); public abstract String getPicasaId(); - + public abstract int getRow(); public abstract int getWidth(); @@ -1599,13 +1633,13 @@ public class ImageManager { public abstract long imageId(); public abstract boolean isReadonly(); - + public abstract boolean isDrm(); - + public abstract Bitmap miniThumbBitmap(); - + public abstract void onRemove(); - + public abstract boolean rotateImageBy(int degrees); /** @@ -1622,26 +1656,27 @@ public class ImageManager { * Sets the name of the image. */ public abstract void setName(String name); - + public abstract void setPicasaId(String id); - + /** * Get the bitmap for the medium thumbnail. * @return the bitmap for the medium thumbnail. */ public abstract Bitmap thumbBitmap(); - + public abstract Uri thumbUri(); - + public abstract String getDataPath(); } + public interface IImageList { public HashMap<String, String> getBucketIds(); public interface OnChange { public void onChange(IImageList list); } - + public interface ThumbCheckCallback { public boolean checking(int current, int count); } @@ -1658,7 +1693,13 @@ public class ImageManager { * @return the number of images */ public abstract int getCount(); - + + /** + * @return true if the count of image objects is zero. + */ + + public abstract boolean isEmpty(); + /** * Returns the image at the ith position. * @@ -1666,7 +1707,7 @@ public class ImageManager { * @return the image at the ith position */ public abstract IImage getImageAt(int i); - + /** * Returns the image with a particular Uri. * @@ -1674,40 +1715,26 @@ public class ImageManager { * @return the image with a particular Uri. */ public abstract IImage getImageForUri(Uri uri);; - + public abstract boolean removeImage(IImage image); /** * Removes the image at the ith position. * @param i the position */ public abstract void removeImageAt(int i); - + public abstract void removeOnChangeListener(OnChange changeCallback); public abstract void setOnChangeListener(OnChange changeCallback, Handler h); } - - class VideoObject extends Image { - public VideoObject() { - super(0, 0, null, null, 0, 0); - } - - public String getTags() { - return null; - } - - public String setTags(String tags) { - return null; - } - } class Image extends BaseImage implements IImage { int mRotation; - + protected Image(long id, long miniThumbId, ContentResolver cr, BaseImageList container, int cursorRow, int rotation) { super(id, miniThumbId, cr, container, cursorRow); mRotation = rotation; } - + public String getDataPath() { String path = null; Cursor c = getCursor(); @@ -1724,7 +1751,7 @@ public class ImageManager { protected int getDegreesRotated() { return mRotation; } - + protected void setDegreesRotated(int degrees) { Cursor c = getCursor(); mRotation = degrees; @@ -1743,12 +1770,12 @@ public class ImageManager { String mimeType = getMimeType(); if (mimeType == null) return Bitmap.CompressFormat.JPEG; - + if (mimeType.equals("image/png")) return Bitmap.CompressFormat.PNG; else if (mimeType.equals("image/png")) return Bitmap.CompressFormat.PNG; - + return Bitmap.CompressFormat.JPEG; } @@ -1782,16 +1809,16 @@ public class ImageManager { } return 0; } - + public boolean isReadonly() { String mimeType = getMimeType(); return !"image/jpeg".equals(mimeType) && !"image/png".equals(mimeType); } - + public boolean isDrm() { return false; } - + /** * Remove tag if already there. Otherwise, does nothing. * @param tag @@ -1822,17 +1849,17 @@ public class ImageManager { * @see com.android.camera.IImage#saveModifiedImage(android.graphics.Bitmap) */ public IGetBoolean_cancelable saveImageContents( - final Bitmap image, + final Bitmap image, final byte [] jpegData, final int orientation, final boolean newFile, final Cursor cursor) { final class SaveImageContentsCancelable extends BaseCancelable implements IGetBoolean_cancelable { IGetBoolean_cancelable mCurrentCancelable = null; - + SaveImageContentsCancelable() { } - + public boolean doCancelWork() { synchronized (this) { if (mCurrentCancelable != null) @@ -1840,22 +1867,22 @@ public class ImageManager { } return true; } - + public boolean get() { try { Bitmap thumbnail = null; - + long t1 = System.currentTimeMillis(); Uri uri = mContainer.contentUri(mId); synchronized (this) { checkCanceled(); mCurrentCancelable = compressImageToFile(image, jpegData, uri); } - + long t2 = System.currentTimeMillis(); if (!mCurrentCancelable.get()) return false; - + synchronized (this) { String filePath; synchronized (cursor) { @@ -1890,7 +1917,7 @@ public class ImageManager { saveMiniThumb(rotate(thumbnail, orientation)); long t5 = System.currentTimeMillis(); checkCanceled(); - + if (VERBOSE) Log.v(TAG, String.format("Timing data %d %d %d %d", t2-t1, t3-t2, t4-t3, t5-t4)); return true; } catch (CanceledException ex) { @@ -1911,7 +1938,7 @@ public class ImageManager { } return new SaveImageContentsCancelable(); } - + private void setExifRotation(int degrees) { try { Cursor c = getCursor(); @@ -1952,7 +1979,7 @@ public class ImageManager { Log.e(TAG, "unable to save exif data with new orientation " + fullSizeImageUri()); } } - + /** * Save the rotated image by updating the Exif "Orientation" tag. * @param degrees @@ -1979,10 +2006,10 @@ public class ImageManager { if (mContainer.mThumbUri != null) { try { c = mContentResolver.query( - mContainer.mThumbUri, - THUMB_PROJECTION, - Thumbnails.IMAGE_ID + "=?", - new String[] { String.valueOf(fullSizeImageId()) }, + mContainer.mThumbUri, + THUMB_PROJECTION, + Thumbnails.IMAGE_ID + "=?", + new String[] { String.valueOf(fullSizeImageId()) }, null); if (c != null && c.moveToFirst()) { Uri thumbUri = ContentUris.withAppendedId(mContainer.mThumbUri, c.getLong(((ImageList)mContainer).INDEX_THUMB_ID)); @@ -2011,7 +2038,7 @@ public class ImageManager { c.close(); } } - + if (bitmap == null) { bitmap = fullSizeBitmap(THUMBNAIL_TARGET_SIZE, false); if (VERBOSE) { @@ -2038,7 +2065,7 @@ public class ImageManager { final static private String sWhereClause = "(" + Images.Media.MIME_TYPE + "=? or " + Images.Media.MIME_TYPE + "=?" + ")"; final static private String[] sAcceptableImageTypes = new String[] { "image/jpeg", "image/png" }; - + private static final String[] IMAGE_PROJECTION = new String[] { "_id", "_data", @@ -2047,7 +2074,7 @@ public class ImageManager { ImageColumns.ORIENTATION, ImageColumns.MIME_TYPE }; - + /** * Represents an ordered collection of Image objects. * Provides an api to add and remove an image. @@ -2059,7 +2086,7 @@ public class ImageManager { final int INDEX_DATE_TAKEN = indexOf(IMAGE_PROJECTION, ImageColumns.DATE_TAKEN); final int INDEX_MINI_THUMB_MAGIC = indexOf(IMAGE_PROJECTION, ImageColumns.MINI_THUMB_MAGIC); final int INDEX_ORIENTATION = indexOf(IMAGE_PROJECTION, ImageColumns.ORIENTATION); - + final int INDEX_THUMB_ID = indexOf(THUMB_PROJECTION, BaseColumns._ID); final int INDEX_THUMB_IMAGE_ID = indexOf(THUMB_PROJECTION, Images.Thumbnails.IMAGE_ID); final int INDEX_THUMB_WIDTH = indexOf(THUMB_PROJECTION, Images.Thumbnails.WIDTH); @@ -2068,12 +2095,12 @@ public class ImageManager { boolean mIsRegistered = false; ContentObserver mContentObserver; DataSetObserver mDataSetObserver; - + public HashMap<String, String> getBucketIds() { Cursor c = Images.Media.query( mContentResolver, mBaseUri.buildUpon().appendQueryParameter("distinct", "true").build(), - new String[] { + new String[] { ImageColumns.BUCKET_DISPLAY_NAME, ImageColumns.BUCKET_ID }, @@ -2098,7 +2125,6 @@ public class ImageManager { mBaseUri = imageUri; mThumbUri = thumbUri; mSort = sort; - mBucketId = bucketId; mContentResolver = cr; @@ -2118,7 +2144,7 @@ public class ImageManager { // For now ignore them since there shouldn't be anyone modifying the database on the fly. if (true) return; - + synchronized (mCursor) { requery(); } @@ -2126,7 +2152,7 @@ public class ImageManager { mListener.onChange(ImageList.this); } }; - + mContentObserver = new ContentObserver(null) { @Override public boolean deliverSelfNotifications() { @@ -2139,7 +2165,7 @@ public class ImageManager { updateRunnable.run(); } }; - + mDataSetObserver = new DataSetObserver() { @Override public void onChanged() { @@ -2152,23 +2178,23 @@ public class ImageManager { if (VERBOSE) Log.v(TAG, "MyDataSetObserver.onInvalidated: " + mCursorDeactivated); } }; - + registerObservers(); } - + private void registerObservers() { if (mIsRegistered) return; - + mCursor.registerContentObserver(mContentObserver); mCursor.registerDataSetObserver(mDataSetObserver); mIsRegistered = true; } - + private void unregisterObservers() { if (!mIsRegistered) return; - + mCursor.unregisterContentObserver(mContentObserver); mCursor.unregisterDataSetObserver(mDataSetObserver); mIsRegistered = false; @@ -2183,21 +2209,21 @@ public class ImageManager { super.activateCursor(); registerObservers(); } - + protected String whereClause() { if (mBucketId != null) { - return sWhereClause + " and " + Images.Media.BUCKET_ID + " = " + mBucketId; + return sWhereClause + " and " + Images.Media.BUCKET_ID + " = '" + mBucketId + "'"; } else { return sWhereClause; } } - + protected String[] whereClauseArgs() { return sAcceptableImageTypes; } - + protected Cursor createCursor() { - Cursor c = + Cursor c = Images.Media.query( mContentResolver, mBaseUri, @@ -2209,7 +2235,7 @@ public class ImageManager { Log.v(TAG, "createCursor got cursor with count " + (c == null ? -1 : c.getCount())); return c; } - + protected int indexOrientation() { return INDEX_ORIENTATION; } protected int indexDateTaken() { return INDEX_DATE_TAKEN; } protected int indexDescription() { return -1; } @@ -2225,7 +2251,7 @@ public class ImageManager { protected int indexTitle() { return -1; } protected int indexDisplayName() { return -1; } protected int indexThumbId() { return INDEX_THUMB_ID; } - + protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { return new Image(id, miniThumbId, mContentResolver, this, index, rotation); } @@ -2234,12 +2260,12 @@ public class ImageManager { Bitmap b = null; try { - if (pfd == null) + if (pfd == null) pfd = makeInputStream(uri); if (pfd == null) return null; - + if (options == null) options = new BitmapFactory.Options(); @@ -2272,7 +2298,7 @@ public class ImageManager { } return b; } - + private ParcelFileDescriptor makeInputStream(Uri uri) { try { return mContentResolver.openFileDescriptor(uri, "r"); @@ -2286,11 +2312,11 @@ public class ImageManager { // which could happen, I suppose, if the first two values were // duplicated String ascending = (mSort == SORT_ASCENDING ? " ASC" : " DESC"); - return + return Images.Media.DATE_TAKEN + ascending + "," + Images.Media._ID + ascending; } - + } /** @@ -2301,11 +2327,11 @@ public class ImageManager { DrmStore.Audio._ID, DrmStore.Audio.DATA, DrmStore.Audio.MIME_TYPE, - }; - + }; + final int INDEX_ID = indexOf(DRM_IMAGE_PROJECTION, DrmStore.Audio._ID); final int INDEX_MIME_TYPE = indexOf(DRM_IMAGE_PROJECTION, DrmStore.Audio.MIME_TYPE); - + public DrmImageList(Context ctx, ContentResolver cr, Uri imageUri, int sort, String bucketId) { super(ctx, cr, imageUri, null, sort, bucketId); } @@ -2323,16 +2349,16 @@ public class ImageManager { public long checkThumbnail(BaseImage existingImage, Cursor c, int i) { return 0; } - + class DrmImage extends Image { protected DrmImage(long id, ContentResolver cr, BaseImageList container, int cursorRow) { super(id, 0, cr, container, cursorRow, 0); } - + public boolean isDrm() { return true; } - + public boolean isReadonly() { return true; } @@ -2340,16 +2366,21 @@ public class ImageManager { public Bitmap miniThumbBitmap() { return fullSizeBitmap(MINI_THUMB_TARGET_SIZE); } - + public Bitmap thumbBitmap() { return fullSizeBitmap(THUMBNAIL_TARGET_SIZE); } + + public String getDisplayName() { + return getTitle(); + } } - - protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index) { + + @Override + protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, long timestamp, int index, int rotation) { return new DrmImage(id, mContentResolver, this, index); } - + protected int indexOrientation() { return -1; } protected int indexDateTaken() { return -1; } protected int indexDescription() { return -1; } @@ -2363,11 +2394,11 @@ public class ImageManager { protected int indexTitle() { return -1; } protected int indexDisplayName() { return -1; } protected int indexThumbId() { return -1; } - + // TODO review this probably should be based on DATE_TAKEN same as images private String sortOrder() { String ascending = (mSort == SORT_ASCENDING ? " ASC" : " DESC"); - return + return DrmStore.Images.TITLE + ascending + "," + DrmStore.Images._ID; } @@ -2385,7 +2416,7 @@ public class ImageManager { // The second component indicates which sublist we're referring // to (an int which is used to index into mSubList). ArrayList<Long> mSkipList = null; - + int [] mSkipCounts = null; public HashMap<String, String> getBucketIds() { @@ -2395,7 +2426,7 @@ public class ImageManager { } return hashMap; } - + public ImageListUber(IImageList [] sublist, int sort) { mSubList = sublist.clone(); mSort = sort; @@ -2412,7 +2443,7 @@ public class ImageManager { } } } - + public void checkThumbnails(ThumbCheckCallback cb) { // TODO this isn't quite right because we need to get the // total from each sub item and provide that in the callback @@ -2421,7 +2452,7 @@ public class ImageManager { for (int i = 0; i < length; i++) sublist[i].checkThumbnails(cb); } - + public void commitChanges() { final IImageList sublist[] = mSubList; final int length = sublist.length; @@ -2448,6 +2479,17 @@ public class ImageManager { return count; } + public boolean isEmpty() { + final IImageList sublist[] = mSubList; + final int length = sublist.length; + for (int i = 0; i < length; i++) { + if (! sublist[i].isEmpty()) { + return false; + } + } + return true; + } + // mSkipCounts is used to tally the counts as we traverse // the mSkipList. It's a member variable only so that // we don't have to allocate each time through. Otherwise @@ -2456,7 +2498,7 @@ public class ImageManager { public synchronized IImage getImageAt(int index) { if (index < 0 || index > getCount()) throw new IndexOutOfBoundsException("index " + index + " out of range max is " + getCount()); - + // first make sure our allocations are in order if (mSkipCounts == null || mSubList.length > mSkipCounts.length) mSkipCounts = new int[mSubList.length]; @@ -2610,7 +2652,7 @@ public class ImageManager { if (changeCallback == mListener) mListener = null; } - + public void setOnChangeListener(OnChange changeCallback, Handler h) { mListener = changeCallback; mHandler = h; @@ -2642,7 +2684,7 @@ public class ImageManager { public long getDateTaken() { return 0; } - + public String getMimeType() { throw new UnsupportedOperationException(); } @@ -2658,7 +2700,7 @@ public class ImageManager { public double getLatitude() { return 0D; } - + public double getLongitude() { return 0D; } @@ -2678,7 +2720,7 @@ public class ImageManager { public int getRow() { throw new UnsupportedOperationException(); } - + public int getHeight() { return 0; } @@ -2686,7 +2728,7 @@ public class ImageManager { public int getWidth() { return 0; } - + public boolean hasLatLong() { return false; } @@ -2694,7 +2736,7 @@ public class ImageManager { public boolean isReadonly() { return true; } - + public boolean isDrm() { return false; } @@ -2706,7 +2748,7 @@ public class ImageManager { public boolean rotateImageBy(int degrees) { return false; } - + public void setDescription(String description) { throw new UnsupportedOperationException(); } @@ -2718,13 +2760,13 @@ public class ImageManager { public void setName(String name) { throw new UnsupportedOperationException(); } - + public void setPicasaId(long id) { } public void setPicasaId(String id) { } - + public Uri thumbUri() { throw new UnsupportedOperationException(); } @@ -2739,11 +2781,11 @@ public class ImageManager { UriImage() { } - + public String getDataPath() { return mUri.getPath(); } - + InputStream getInputStream() { try { if (mUri.getScheme().equals("file")) { @@ -2758,7 +2800,7 @@ public class ImageManager { return null; } } - + ParcelFileDescriptor getPFD() { try { if (mUri.getScheme().equals("file")) { @@ -2773,7 +2815,7 @@ public class ImageManager { return null; } } - + /* (non-Javadoc) * @see com.android.camera.ImageManager.IImage#fullSizeBitmap(int) */ @@ -2790,7 +2832,7 @@ public class ImageManager { options.inJustDecodeBounds = false; options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; - + Bitmap b = BitmapFactory.decodeFileDescriptor(pfdInput.getFileDescriptor(), null, options); if (VERBOSE) { Log.v(TAG, "B: got bitmap " + b + " with sampleSize " + options.inSampleSize); @@ -2808,11 +2850,11 @@ public class ImageManager { ParcelFileDescriptor pfdInput; BitmapFactory.Options mOptions = new BitmapFactory.Options(); long mCancelInitiationTime; - + public LoadBitmapCancelable(ParcelFileDescriptor pfd) { pfdInput = pfd; } - + public boolean doCancelWork() { if (VERBOSE) Log.v(TAG, "requesting bitmap load cancel"); @@ -2820,7 +2862,7 @@ public class ImageManager { mOptions.requestCancelDecode(); return true; } - + public Bitmap get() { try { Bitmap b = makeBitmap(targetWidthOrHeight, fullSizeImageUri(), pfdInput, mOptions); @@ -2848,12 +2890,12 @@ public class ImageManager { return null; } } - + @Override public Uri fullSizeImageUri() { return mUri; } - + @Override public InputStream fullSizeImageData() { return getInputStream(); @@ -2862,26 +2904,26 @@ public class ImageManager { public long imageId() { return 0; } - + public Bitmap miniThumbBitmap() { return thumbBitmap(); } - + @Override public String getTitle() { return mUri.toString(); } - + @Override public String getDisplayName() { return getTitle(); } - - @Override + + @Override public String getDescription() { return ""; } - + public Bitmap thumbBitmap() { Bitmap b = fullSizeBitmap(THUMBNAIL_TARGET_SIZE); if (b != null) { @@ -2894,7 +2936,7 @@ public class ImageManager { return null; } } - + private BitmapFactory.Options snifBitmapOptions() { ParcelFileDescriptor input = getPFD(); if (input == null) @@ -2920,13 +2962,13 @@ public class ImageManager { BitmapFactory.Options options = snifBitmapOptions(); return (options!=null) ? options.outMimeType : ""; } - + @Override public int getHeight() { BitmapFactory.Options options = snifBitmapOptions(); return (options!=null) ? options.outHeight : 0; } - + @Override public int getWidth() { BitmapFactory.Options options = snifBitmapOptions(); @@ -2948,11 +2990,15 @@ public class ImageManager { public void deactivate() { // nothing to do here } - + public int getCount() { return 1; } + public boolean isEmpty() { + return false; + } + public IImage getImageAt(int i) { if (i == 0) return mSingleImage; @@ -2980,7 +3026,7 @@ public class ImageManager { protected int indexDateTaken() { return -1; } - + @Override protected int indexMimeType() { return -1; @@ -2990,12 +3036,12 @@ public class ImageManager { protected int indexDescription() { return -1; } - + @Override protected int indexId() { return -1; } - + @Override protected int indexData() { return -1; @@ -3040,7 +3086,7 @@ public class ImageManager { protected int indexThumbId() { return -1; } - + private InputStream makeInputStream(Uri uri) { InputStream input = null; try { @@ -3087,14 +3133,14 @@ public class ImageManager { public ThreadSafeOutputStream(OutputStream delegate) { mDelegateStream = delegate; } - + @Override synchronized public void close() throws IOException { try { mClosed = true; mDelegateStream.close(); } catch (IOException ex) { - + } } @@ -3113,7 +3159,7 @@ public class ImageManager { synchronized (this) { if (mClosed) return; - + int writeLength = Math.min(8192, length); mDelegateStream.write(b, offset, writeLength); offset += writeLength; @@ -3129,14 +3175,349 @@ public class ImageManager { mDelegateStream.write(oneByte); } } - + + class VideoList extends BaseImageList implements IImageList { + private final String[] sProjection = new String[] { + Video.Media._ID, + Video.Media.DATA, + Video.Media.DATE_TAKEN, + Video.Media.TITLE, + Video.Media.DISPLAY_NAME, + Video.Media.DESCRIPTION, + Video.Media.IS_PRIVATE, + Video.Media.TAGS, + Video.Media.CATEGORY, + Video.Media.LANGUAGE, + Video.Media.LATITUDE, + Video.Media.LONGITUDE, + Video.Media.MINI_THUMB_MAGIC, + Video.Media.MIME_TYPE, + }; + + final int INDEX_ID = indexOf(sProjection, Video.Media._ID); + final int INDEX_DATA = indexOf(sProjection, Video.Media.DATA); + final int INDEX_DATE_TAKEN = indexOf(sProjection, Video.Media.DATE_TAKEN); + final int INDEX_TITLE = indexOf(sProjection, Video.Media.TITLE); + final int INDEX_DISPLAY_NAME = indexOf(sProjection, Video.Media.DISPLAY_NAME); + final int INDEX_MIME_TYPE = indexOf(sProjection, Video.Media.MIME_TYPE); + final int INDEX_DESCRIPTION = indexOf(sProjection, Video.Media.DESCRIPTION); + final int INDEX_PRIVATE = indexOf(sProjection, Video.Media.IS_PRIVATE); + final int INDEX_TAGS = indexOf(sProjection, Video.Media.TAGS); + final int INDEX_CATEGORY = indexOf(sProjection, Video.Media.CATEGORY); + final int INDEX_LANGUAGE = indexOf(sProjection, Video.Media.LANGUAGE); + final int INDEX_LATITUDE = indexOf(sProjection, Video.Media.LATITUDE); + final int INDEX_LONGITUDE = indexOf(sProjection, Video.Media.LONGITUDE); + final int INDEX_MINI_THUMB_MAGIC = indexOf(sProjection, Video.Media.MINI_THUMB_MAGIC); + final int INDEX_THUMB_ID = indexOf(sProjection, BaseColumns._ID); + + public VideoList(Context ctx, ContentResolver cr, Uri uri, Uri thumbUri, + int sort, String bucketId) { + super(ctx, cr, uri, sort, bucketId); + + mCursor = createCursor(); + if (mCursor == null) { + Log.e(TAG, "unable to create video cursor for " + mBaseUri); + throw new UnsupportedOperationException(); + } + + if (Config.LOGV) { + Log.v(TAG, "for " + mUri.toString() + " got cursor " + mCursor + " with length " + + (mCursor != null ? mCursor.getCount() : -1)); + } + + if (mCursor == null) { + throw new UnsupportedOperationException(); + } + if (mCursor != null && mCursor.moveToFirst()) { + int row = 0; + do { + long imageId = mCursor.getLong(indexId()); + long dateTaken = mCursor.getLong(indexDateTaken()); + long miniThumbId = mCursor.getLong(indexMiniThumbId()); + mCache.put(imageId, new VideoObject(imageId, miniThumbId, mContentResolver, + this, dateTaken, row++)); + } while (mCursor.moveToNext()); + } + } + + public HashMap<String, String> getBucketIds() { + Cursor c = Images.Media.query( + mContentResolver, + mBaseUri.buildUpon().appendQueryParameter("distinct", "true").build(), + new String[] { + VideoColumns.BUCKET_DISPLAY_NAME, + VideoColumns.BUCKET_ID + }, + whereClause(), + whereClauseArgs(), + sortOrder()); + + HashMap<String, String> hash = new HashMap<String, String>(); + if (c != null && c.moveToFirst()) { + do { + Log.e(TAG, "id: " + c.getString(1) + " display_name: " + c.getString(0)); + hash.put(c.getString(1), c.getString(0)); + } while (c.moveToNext()); + } + return hash; + } + + protected String whereClause() { + if (mBucketId != null) { + return Images.Media.BUCKET_ID + " = '" + mBucketId + "'"; + } else { + return null; + } + } + + protected String[] whereClauseArgs() { + return null; + } + + protected Cursor createCursor() { + Cursor c = + Images.Media.query( + mContentResolver, + mBaseUri, + sProjection, + whereClause(), + whereClauseArgs(), + sortOrder()); + if (VERBOSE) + Log.v(TAG, "createCursor got cursor with count " + (c == null ? -1 : c.getCount())); + return c; + } + + protected int indexOrientation() { return -1; } + protected int indexDateTaken() { return INDEX_DATE_TAKEN; } + protected int indexDescription() { return INDEX_DESCRIPTION; } + protected int indexMimeType() { return INDEX_MIME_TYPE; } + protected int indexData() { return INDEX_DATA; } + protected int indexId() { return INDEX_ID; } + protected int indexLatitude() { return INDEX_LATITUDE; } + protected int indexLongitude() { return INDEX_LONGITUDE; } + protected int indexMiniThumbId() { return INDEX_MINI_THUMB_MAGIC; } + protected int indexPicasaWeb() { return -1; } + protected int indexPrivate() { return INDEX_PRIVATE; } + protected int indexTitle() { return INDEX_TITLE; } + protected int indexDisplayName() { return -1; } + protected int indexThumbId() { return INDEX_THUMB_ID; } + + protected IImage make(long id, long miniThumbId, ContentResolver cr, IImageList list, + long timestamp, int index) { + return new VideoObject(id, miniThumbId, mContentResolver, this, timestamp, index); + } + + @Override + protected Bitmap makeBitmap(int targetWidthHeight, Uri uri, ParcelFileDescriptor pfdInput, + BitmapFactory.Options options) { + MediaPlayer mp = new MediaPlayer(); + Bitmap thumbnail = sDefaultThumbnail; + try { + mp.setDataSource(mContext, uri); +// int duration = mp.getDuration(); +// int at = duration > 2000 ? 1000 : duration / 2; + int at = 1000; + thumbnail = mp.getFrameAt(at); + if (Config.LOGV) { + if ( thumbnail != null) { + Log.v(TAG, "getFrameAt @ " + at + " returned " + thumbnail + "; " + + thumbnail.getWidth() + " " + thumbnail.getHeight()); + } else { + Log.v(TAG, "getFrame @ " + at + " failed for " + uri); + } + } + } catch (IOException ex) { + } catch (IllegalArgumentException ex) { + } catch (SecurityException ex) { + } finally { + mp.release(); + } + return thumbnail; + } + + private final Bitmap sDefaultThumbnail = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565); + + private String sortOrder() { + return Video.Media.DATE_MODIFIED + (mSort == SORT_ASCENDING ? " ASC " : " DESC"); + } + } + + /** + * Represents a particular video and provides access + * to the underlying data and two thumbnail bitmaps + * as well as other information such as the id, and + * the path to the actual video data. + */ + class VideoObject extends BaseImage implements IImage { + /** + * Constructor. + * + * @param id the image id of the image + * @param cr the content resolver + */ + protected VideoObject(long id, long miniThumbId, ContentResolver cr, VideoList container, + long dateTaken, int row) { + super(id, miniThumbId, cr, container, row); + } + + protected Bitmap.CompressFormat compressionType() { + return Bitmap.CompressFormat.JPEG; + } + + @Override + public boolean equals(Object other) { + if (other == null) + return false; + if (!(other instanceof VideoObject)) + return false; + + return fullSizeImageUri().equals(((VideoObject)other).fullSizeImageUri()); + } + + public String getDataPath() { + String path = null; + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + int column = ((VideoList)getContainer()).indexData(); + if (column >= 0) + path = c.getString(column); + } + } + return path; + } + + /* (non-Javadoc) + * @see com.android.camera.IImage#fullSizeBitmap() + */ + public Bitmap fullSizeBitmap(int targetWidthHeight) { + return sNoImageBitmap; + } + + public IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthHeight) { + return null; + } + + /* (non-Javadoc) + * @see com.android.camera.IImage#fullSizeImageData() + */ + public InputStream fullSizeImageData() { + try { + InputStream input = mContentResolver.openInputStream( + fullSizeImageUri()); + return input; + } catch (IOException ex) { + return null; + } + } + + /* (non-Javadoc) + * @see com.android.camera.IImage#fullSizeImageId() + */ + public long fullSizeImageId() { + return mId; + } + + public String getCategory() { + return getStringEntry(((VideoList)mContainer).INDEX_CATEGORY); + } + + public int getHeight() { + return 0; + } + + public String getLanguage() { + return getStringEntry(((VideoList)mContainer).INDEX_LANGUAGE); + } + + public String getPicasaId() { + return null; + } + + private String getStringEntry(int entryName) { + String entry = null; + Cursor c = getCursor(); + synchronized(c) { + if (c.moveToPosition(getRow())) { + entry = c.getString(entryName); + } + } + return entry; + } + + public String getTags() { + return getStringEntry(((VideoList)mContainer).INDEX_TAGS); + } + + public int getWidth() { + return 0; + } + + /* (non-Javadoc) + * @see com.android.camera.IImage#imageId() + */ + public long imageId() { + return mId; + } + + public boolean isReadonly() { + return false; + } + + public boolean isDrm() { + return false; + } + + public boolean rotateImageBy(int degrees) { + return false; + } + + public void setCategory(String category) { + setStringEntry(category, ((VideoList)mContainer).INDEX_CATEGORY); + } + + public void setLanguage(String language) { + setStringEntry(language, ((VideoList)mContainer).INDEX_LANGUAGE); + } + + private void setStringEntry(String entry, int entryName) { + Cursor c = getCursor(); + synchronized (c) { + if (c.moveToPosition(getRow())) { + c.updateString(entryName, entry); + } + } + } + + public void setTags(String tags) { + setStringEntry(tags, ((VideoList)mContainer).INDEX_TAGS); + } + + /* (non-Javadoc) + * @see com.android.camera.IImage#thumb1() + */ + public Bitmap thumbBitmap() { + return fullSizeBitmap(320); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("" + mId); + return sb.toString(); + } + + private final Bitmap sNoImageBitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.RGB_565); + } + /* * How much quality to use when storing the thumbnail. */ private static ImageManager sInstance = null; private static final int MINI_THUMB_TARGET_SIZE = 96; private static final int THUMBNAIL_TARGET_SIZE = 320; - + private static final String[] THUMB_PROJECTION = new String[] { BaseColumns._ID, // 0 Images.Thumbnails.IMAGE_ID, // 1 @@ -3147,6 +3528,10 @@ public class ImageManager { private static Uri sStorageURI = Images.Media.EXTERNAL_CONTENT_URI; private static Uri sThumbURI = Images.Thumbnails.EXTERNAL_CONTENT_URI; + + private static Uri sVideoStorageURI = Uri.parse("content://media/external/video/media"); + + private static Uri sVideoThumbURI = Uri.parse("content://media/external/video/thumbnails"); /** * Returns an ImageList object that contains * all of the images. @@ -3157,13 +3542,13 @@ public class ImageManager { * @return the singleton ImageList */ static final public int SORT_ASCENDING = 1; - + static final public int SORT_DESCENDING = 2; static final public int INCLUDE_IMAGES = (1 << 0); static final public int INCLUDE_DRM_IMAGES = (1 << 1); static final public int INCLUDE_VIDEOS = (1 << 2); - + static public DataLocation getDefaultDataLocation() { return DataLocation.EXTERNAL; } @@ -3190,7 +3575,7 @@ public class ImageManager { static public byte [] miniThumbData(Bitmap source) { if (source == null) return null; - + float scale; if (source.getWidth() < source.getHeight()) { scale = MINI_THUMB_TARGET_SIZE / (float)source.getWidth(); @@ -3199,7 +3584,7 @@ public class ImageManager { } Matrix matrix = new Matrix(); matrix.setScale(scale, scale); - Bitmap miniThumbnail = ImageLoader.transform(matrix, source, + Bitmap miniThumbnail = ImageLoader.transform(matrix, source, MINI_THUMB_TARGET_SIZE, MINI_THUMB_TARGET_SIZE, false); if (miniThumbnail != source) { @@ -3223,7 +3608,7 @@ public class ImageManager { if (degrees != 0 && b != null) { Matrix m = new Matrix(); m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2); - + Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true); // TODO should recycle here but that needs more testing/verification // b.recycle(); @@ -3231,12 +3616,12 @@ public class ImageManager { } return b; } - + public static int roundOrientation(int orientationInput) { int orientation = orientationInput; if (orientation == -1) orientation = 0; - + orientation = orientation % 360; int retVal; if (orientation < (0*90) + 45) { @@ -3255,6 +3640,35 @@ public class ImageManager { return retVal; } + + /** + * @return true if the mimetype is an image mimetype. + */ + public static boolean isImageMimeType(String mimeType) { + return mimeType.startsWith("image/"); + } + + /** + * @return true if the mimetype is a video mimetype. + */ + public static boolean isVideoMimeType(String mimeType) { + return mimeType.startsWith("video/"); + } + + /** + * @return true if the image is an image. + */ + public static boolean isImage(IImage image) { + return isImageMimeType(image.getMimeType()); + } + + /** + * @return true if the image is a video. + */ + public static boolean isVideo(IImage image) { + return isVideoMimeType(image.getMimeType()); + } + public Uri addImage( final Context ctx, final ContentResolver cr, @@ -3274,11 +3688,12 @@ public class ImageManager { values.put(Images.Media.ORIENTATION, orientation); File parentFile = new File(directory); + // Lowercase the path for hashing. This avoids duplicate buckets if the filepath + // case is changed externally. + // Keep the original case for display. String path = parentFile.toString().toLowerCase(); - String name = parentFile.getName().toLowerCase(); - - values.put(Images.ImageColumns.BUCKET_ID, path.hashCode()); - values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, name); + String name = parentFile.getName(); + if (VERBOSE) Log.v(TAG, "addImage id is " + path.hashCode() + "; name " + name + "; path is " + path); if (location != null) { @@ -3288,24 +3703,24 @@ public class ImageManager { values.put(Images.Media.LATITUDE, location.getLatitude()); values.put(Images.Media.LONGITUDE, location.getLongitude()); } - + if (directory != null && filename != null) { String value = directory + "/" + filename; values.put("_data", value); } - + long t3 = System.currentTimeMillis(); Uri uri = cr.insert(sStorageURI, values); - + // The line above will create a filename that ends in .jpg // That filename is what will be handed to gmail when a user shares a photo. // Gmail gets the name of the picture attachment from the "DISPLAY_NAME" field. // Extract the filename and jam it into the display name. Cursor c = cr.query( - uri, - new String [] { ImageColumns._ID, Images.Media.DISPLAY_NAME, "_data" }, - null, - null, + uri, + new String [] { ImageColumns._ID, Images.Media.DISPLAY_NAME, "_data" }, + null, + null, null); if (c.moveToFirst()) { String filePath = c.getString(2); @@ -3331,7 +3746,7 @@ public class ImageManager { final byte [] jpegData) { class AddImageCancelable extends BaseCancelable implements IAddImage_cancelable { private IGetBoolean_cancelable mSaveImageCancelable; - + public boolean doCancelWork() { if (VERBOSE) { Log.v(TAG, "calling AddImageCancelable.cancel() " + mSaveImageCancelable); @@ -3347,7 +3762,7 @@ public class ImageManager { if (source == null && jpegData == null) { throw new IllegalArgumentException("source cannot be null"); } - + try { long t1 = System.currentTimeMillis(); synchronized (this) { @@ -3356,15 +3771,15 @@ public class ImageManager { } } long id = ContentUris.parseId(uri); - + BaseImageList il = new ImageList(ctx, cr, sStorageURI, sThumbURI, SORT_ASCENDING, null); ImageManager.Image image = new Image(id, 0, cr, il, il.getCount(), 0); long t5 = System.currentTimeMillis(); Cursor c = cr.query( - uri, - new String [] { ImageColumns._ID, ImageColumns.MINI_THUMB_MAGIC, "_data" }, - null, - null, + uri, + new String [] { ImageColumns._ID, ImageColumns.MINI_THUMB_MAGIC, "_data" }, + null, + null, null); c.moveToPosition(0); @@ -3372,7 +3787,7 @@ public class ImageManager { checkCanceled(); mSaveImageCancelable = image.saveImageContents(source, jpegData, orientation, true, c); } - + if (mSaveImageCancelable.get()) { long t6 = System.currentTimeMillis(); if (VERBOSE) Log.v(TAG, "saveImageContents took " + (t6-t5)); @@ -3403,7 +3818,7 @@ public class ImageManager { } return new AddImageCancelable(); } - + static public IImageList makeImageList(Uri uri, Context ctx, int sort) { ContentResolver cr = ctx.getContentResolver(); String uriString = (uri != null) ? uri.toString() : ""; @@ -3411,7 +3826,7 @@ public class ImageManager { // DRM images in a better way. Is there a constant // for content://drm somewhere?? IImageList imageList; - + if (uriString.startsWith("content://drm")) { imageList = ImageManager.instance().allImages( ctx, @@ -3435,7 +3850,7 @@ public class ImageManager { } return imageList; } - + public IImageList emptyImageList() { return new IImageList() { @@ -3456,6 +3871,10 @@ public class ImageManager { return 0; } + public boolean isEmpty() { + return true; + } + public IImage getImageAt(int i) { return null; } @@ -3476,10 +3895,10 @@ public class ImageManager { public void setOnChangeListener(com.android.camera.ImageManager.IImageList.OnChange changeCallback, Handler h) { } - + }; } - + public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort) { return allImages(ctx, cr, location, inclusion, sort, null, null); } @@ -3487,12 +3906,12 @@ public class ImageManager { public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId) { return allImages(ctx, cr, location, inclusion, sort, bucketId, null); } - + public IImageList allImages(Context ctx, ContentResolver cr, DataLocation location, int inclusion, int sort, String bucketId, Uri specificImageUri) { if (VERBOSE) { Log.v(TAG, "allImages " + location + " " + ((inclusion&INCLUDE_IMAGES)!=0) + " + v=" + ((inclusion&INCLUDE_VIDEOS)!=0)); } - + if (cr == null) { return null; } else { @@ -3510,7 +3929,7 @@ public class ImageManager { try { if (specificImageUri.getScheme().equalsIgnoreCase("content")) l.add(new ImageList(ctx, cr, specificImageUri, sThumbURI, sort, bucketId)); - else + else l.add(new SingleImageList(cr, specificImageUri)); } catch (UnsupportedOperationException ex) { } @@ -3522,11 +3941,17 @@ public class ImageManager { } catch (UnsupportedOperationException ex) { } } + if ((inclusion & INCLUDE_VIDEOS) != 0) { + try { + l.add(new VideoList(ctx, cr, sVideoStorageURI, sVideoThumbURI, sort, bucketId)); + } catch (UnsupportedOperationException ex) { + } + } } if (location == DataLocation.INTERNAL || location == DataLocation.ALL) { if ((inclusion & INCLUDE_IMAGES) != 0) { try { - l.add(new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI, + l.add(new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI, Images.Thumbnails.INTERNAL_CONTENT_URI, sort, bucketId)); } catch (UnsupportedOperationException ex) { } @@ -3546,7 +3971,7 @@ public class ImageManager { if (haveSdCard && location != DataLocation.INTERNAL) { return new ImageList(ctx, cr, sStorageURI, sThumbURI, sort, bucketId); } else { - return new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI, + return new ImageList(ctx, cr, Images.Media.INTERNAL_CONTENT_URI, Images.Thumbnails.INTERNAL_CONTENT_URI, sort, bucketId); } } @@ -3577,7 +4002,7 @@ public class ImageManager { return false; } } - + static public boolean hasStorage() { return hasStorage(true); } @@ -3610,20 +4035,20 @@ public class ImageManager { } catch (UnsupportedOperationException ex) { return null; } - + } - + public static boolean isMediaScannerScanning(Context context) { boolean result = false; - Cursor cursor = query(context, MediaStore.getMediaScannerUri(), + Cursor cursor = query(context, MediaStore.getMediaScannerUri(), new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null); if (cursor != null) { if (cursor.getCount() == 1) { cursor.moveToFirst(); result = "external".equals(cursor.getString(0)); } - cursor.close(); - } + cursor.close(); + } if (VERBOSE) Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>>>>>> isMediaScannerScanning returning " + result); diff --git a/src/com/android/camera/ImageViewTouchBase.java b/src/com/android/camera/ImageViewTouchBase.java index 9993373..7cdf55e 100644 --- a/src/com/android/camera/ImageViewTouchBase.java +++ b/src/com/android/camera/ImageViewTouchBase.java @@ -13,6 +13,7 @@ import android.util.Config; import android.util.Log; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; +import android.view.KeyEvent; import android.widget.ImageView; abstract public class ImageViewTouchBase extends ImageView { @@ -91,6 +92,17 @@ abstract public class ImageViewTouchBase extends ImageView { } } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && getScale() > 1.0f) { + // If we're zoomed in, pressing Back jumps out to show the entire image, otherwise Back + // returns the user to the gallery. + zoomTo(1.0f); + return true; + } + return super.onKeyDown(keyCode, event); + } + protected Handler mHandler = new Handler(); protected int mLastXTouchPos; diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java index 033fc9c..9e4fb82 100644 --- a/src/com/android/camera/MenuHelper.java +++ b/src/com/android/camera/MenuHelper.java @@ -23,12 +23,14 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Handler; +import android.provider.MediaStore; import android.util.Config; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; import android.view.View; +import android.view.MenuItem.OnMenuItemClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; @@ -39,16 +41,18 @@ import android.widget.Toast; import java.util.ArrayList; +import com.android.camera.ImageManager.IImage; + public class MenuHelper { static private final String TAG = "MenuHelper"; - + static public final int GENERIC_ITEM = 1; static public final int IMAGE_SAVING_ITEM = 2; static public final int VIDEO_SAVING_ITEM = 3; static public final int IMAGE_MODE_ITEM = 4; static public final int VIDEO_MODE_ITEM = 5; static public final int MENU_ITEM_MAX = 5; - + static public final int INCLUDE_ALL = 0xFFFFFFFF; static public final int INCLUDE_VIEWPLAY_MENU = (1 << 0); static public final int INCLUDE_SHARE_MENU = (1 << 1); @@ -57,84 +61,52 @@ public class MenuHelper { static public final int INCLUDE_DELETE_MENU = (1 << 4); static public final int INCLUDE_ROTATE_MENU = (1 << 5); static public final int INCLUDE_DETAILS_MENU = (1 << 5); - + + static public final int MENU_SWITCH_CAMERA_MODE = 0; + static public final int MENU_CAPTURE_PICTURE = 1; + static public final int MENU_CAPTURE_VIDEO = 2; static public final int MENU_IMAGE_SHARE = 10; - static public final int MENU_IMAGE_SHARE_EMAIL = 11; - static public final int MENU_IMAGE_SHARE_MMS = 12; - static public final int MENU_IMAGE_SHARE_PICASA =13; static public final int MENU_IMAGE_SET = 14; static public final int MENU_IMAGE_SET_WALLPAPER = 15; static public final int MENU_IMAGE_SET_CONTACT = 16; static public final int MENU_IMAGE_SET_MYFAVE = 17; static public final int MENU_IMAGE_CROP = 18; static public final int MENU_IMAGE_ROTATE = 19; - static public final int MENU_IMAGE_ROTATE_LEFT = 20; + static public final int MENU_IMAGE_ROTATE_LEFT = 20; static public final int MENU_IMAGE_ROTATE_RIGHT = 21; static public final int MENU_IMAGE_TOSS = 22; static public final int MENU_VIDEO_PLAY = 23; static public final int MENU_VIDEO_SHARE = 24; - static public final int MENU_VIDEO_SHARE_MMS = 25; - static public final int MENU_VIDEO_SHARE_YOUTUBE = 26; static public final int MENU_VIDEO_TOSS = 27; - static public final int MENU_IMAGE_SHARE_PICASA_ALL =28; - + public interface MenuItemsResult { public void gettingReadyToOpen(Menu menu, ImageManager.IImage image); public void aboutToCall(MenuItem item, ImageManager.IImage image); } - + public interface MenuInvoker { public void run(MenuCallback r); } - + public interface MenuCallback { public void run(Uri uri, ImageManager.IImage image); } static MenuItemsResult addImageMenuItems( - Menu menu, + Menu menu, int inclusions, + final boolean isImage, final Activity activity, final Handler handler, final Runnable onDelete, final MenuInvoker onInvoke) { - final ArrayList<MenuItem> requiresWriteAccessItems = new ArrayList<MenuItem>(); - final ArrayList<MenuItem> requiresNoDrmAccessItems = new ArrayList<MenuItem>(); - if ((inclusions & INCLUDE_SHARE_MENU) != 0) { - if (Config.LOGV) - Log.v(TAG, ">>>>> add share"); - MenuItem item = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_SHARE, 10, - R.string.camera_share).setOnMenuItemClickListener( - new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - onInvoke.run(new MenuCallback() { - public void run(Uri u, ImageManager.IImage image) { - if (image == null) - return; - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType(image.getMimeType()); - intent.putExtra(Intent.EXTRA_STREAM, u); - try { - activity.startActivity(Intent.createChooser(intent, - activity.getText(R.string.sendImage))); - } catch (android.content.ActivityNotFoundException ex) { - Toast.makeText(activity, R.string.no_way_to_share_image, Toast.LENGTH_SHORT).show(); - } - } - }); - return true; - } - }); - item.setIcon(android.R.drawable.ic_menu_share); - requiresNoDrmAccessItems.add(item); - } + final ArrayList<MenuItem> requiresWriteAccessItems = new ArrayList<MenuItem>(); + final ArrayList<MenuItem> requiresNoDrmAccessItems = new ArrayList<MenuItem>(); - if ((inclusions & INCLUDE_ROTATE_MENU) != 0) { + if (isImage && ((inclusions & INCLUDE_ROTATE_MENU) != 0)) { SubMenu rotateSubmenu = menu.addSubMenu(IMAGE_SAVING_ITEM, MENU_IMAGE_ROTATE, 40, R.string.rotate).setIcon(android.R.drawable.ic_menu_rotate); - // Don't show the rotate submenu if the item at hand is read only + // Don't show the rotate submenu if the item at hand is read only // since the items within the submenu won't be shown anyway. This is // really a framework bug in that it shouldn't show the submenu if // the submenu has no visible items. @@ -158,7 +130,7 @@ public class MenuHelper { public void run(Uri u, ImageManager.IImage image) { if (image == null || image.isReadonly()) return; - + image.rotateImageBy(90); } }); @@ -167,21 +139,8 @@ public class MenuHelper { }).setAlphabeticShortcut('r')); } } - - if ((inclusions & INCLUDE_DELETE_MENU) != 0) { - MenuItem deleteItem = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_TOSS, 70, R.string.camera_toss); - requiresWriteAccessItems.add(deleteItem); - deleteItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - deletePhoto(activity, onDelete); - return true; - } - }) - .setAlphabeticShortcut('d') - .setIcon(android.R.drawable.ic_menu_delete); - } - - if ((inclusions & INCLUDE_CROP_MENU) != 0) { + + if (isImage && ((inclusions & INCLUDE_CROP_MENU) != 0)) { MenuItem autoCrop = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_CROP, 73, R.string.camera_crop).setOnMenuItemClickListener( new MenuItem.OnMenuItemClickListener() { @@ -190,7 +149,7 @@ public class MenuHelper { public void run(Uri u, ImageManager.IImage image) { if (u == null) return; - + Intent cropIntent = new Intent(); cropIntent.setClass(activity, CropImage.class); cropIntent.setData(u); @@ -203,8 +162,8 @@ public class MenuHelper { autoCrop.setIcon(android.R.drawable.ic_menu_crop); requiresWriteAccessItems.add(autoCrop); } - - if ((inclusions & INCLUDE_SET_MENU) != 0) { + + if (isImage && ((inclusions & INCLUDE_SET_MENU) != 0)) { MenuItem setMenu = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_SET, 75, R.string.camera_set); setMenu.setIcon(android.R.drawable.ic_menu_set_as); @@ -214,7 +173,7 @@ public class MenuHelper { public void run(Uri u, ImageManager.IImage image) { if (u == null || image == null) return; - + if (Config.LOGV) Log.v(TAG, "in callback u is " + u + "; mime type is " + image.getMimeType()); Intent intent = new Intent(Intent.ACTION_ATTACH_DATA); @@ -228,6 +187,56 @@ public class MenuHelper { }); } + if ((inclusions & INCLUDE_SHARE_MENU) != 0) { + if (Config.LOGV) + Log.v(TAG, ">>>>> add share"); + MenuItem item1 = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_SHARE, 10, + R.string.camera_share).setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + onInvoke.run(new MenuCallback() { + public void run(Uri u, ImageManager.IImage image) { + if (image == null) + return; + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + String mimeType = image.getMimeType(); + intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_STREAM, u); + boolean isImage = ImageManager.isImageMimeType(mimeType); + try { + activity.startActivity(Intent.createChooser(intent, + activity.getText( + isImage ? R.string.sendImage : R.string.sendVideo))); + } catch (android.content.ActivityNotFoundException ex) { + Toast.makeText(activity, + isImage ? R.string.no_way_to_share_image + : R.string.no_way_to_share_video, + Toast.LENGTH_SHORT).show(); + } + } + }); + return true; + } + }); + item1.setIcon(android.R.drawable.ic_menu_share); + MenuItem item = item1; + requiresNoDrmAccessItems.add(item); + } + + if ((inclusions & INCLUDE_DELETE_MENU) != 0) { + MenuItem deleteItem = menu.add(IMAGE_SAVING_ITEM, MENU_IMAGE_TOSS, 70, R.string.camera_toss); + requiresWriteAccessItems.add(deleteItem); + deleteItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + deleteImageImpl(activity, onDelete, isImage); + return true; + } + }) + .setAlphabeticShortcut('d') + .setIcon(android.R.drawable.ic_menu_delete); + } + if ((inclusions & INCLUDE_DETAILS_MENU) != 0) { MenuItem detailsMenu = menu.add(0, 0, 80, R.string.details).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -237,9 +246,9 @@ public class MenuHelper { return; AlertDialog.Builder builder = new AlertDialog.Builder(activity); - + final View d = View.inflate(activity, R.layout.detailsview, null); - + ImageView imageView = (ImageView) d.findViewById(R.id.details_thumbnail_image); imageView.setImageBitmap(image.miniThumbBitmap()); @@ -250,8 +259,8 @@ public class MenuHelper { String lengthString = ""; try { long length = data.available(); - lengthString = - android.content.Formatter.formatFileSize(activity, length); + lengthString = + android.text.format.Formatter.formatFileSize(activity, length); data.close(); } catch (java.io.IOException ex) { @@ -260,9 +269,14 @@ public class MenuHelper { ((TextView)d.findViewById(R.id.details_attrname_1)).setText(R.string.details_file_size); ((TextView)d.findViewById(R.id.details_attrvalu_1)).setText(lengthString); - String dimensionsString = String.valueOf(image.getWidth() + " X " + image.getHeight()); - ((TextView)d.findViewById(R.id.details_attrname_2)).setText(R.string.details_image_resolution); - ((TextView)d.findViewById(R.id.details_attrvalu_2)).setText(dimensionsString); + if (isImage) { + String dimensionsString = String.valueOf(image.getWidth() + " X " + image.getHeight()); + ((TextView)d.findViewById(R.id.details_attrname_2)).setText(R.string.details_image_resolution); + ((TextView)d.findViewById(R.id.details_attrvalu_2)).setText(dimensionsString); + } else { + d.findViewById(R.id.details_attrname_2).setVisibility(View.GONE); + d.findViewById(R.id.details_attrvalu_2).setVisibility(View.GONE); + } String dateString = ""; long dateTaken = image.getDateTaken(); @@ -270,19 +284,19 @@ public class MenuHelper { java.util.Date date = new java.util.Date(image.getDateTaken()); java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat(); dateString = dateFormat.format(date); - + ((TextView)d.findViewById(R.id.details_attrname_3)).setText(R.string.details_date_taken); ((TextView)d.findViewById(R.id.details_attrvalu_3)).setText(dateString); } else { d.findViewById(R.id.details_daterow).setVisibility(View.GONE); } - + builder.setIcon(android.R.drawable.ic_dialog_info) .setTitle(R.string.details_panel_title) .setView(d) .show(); - + } }); return true; @@ -290,7 +304,23 @@ public class MenuHelper { }); detailsMenu.setIcon(R.drawable.ic_menu_view_details); } - + + if ((!isImage) && ((inclusions & INCLUDE_VIEWPLAY_MENU) != 0)) { + menu.add(VIDEO_SAVING_ITEM, MENU_VIDEO_PLAY, 0, R.string.video_play) + .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + onInvoke.run(new MenuCallback() { + public void run(Uri uri, IImage image) { + Intent intent = new Intent(Intent.ACTION_VIEW, + image.fullSizeImageUri()); + activity.startActivity(intent); + }}); + return true; + } + }); + } + + return new MenuItemsResult() { public void gettingReadyToOpen(Menu menu, ImageManager.IImage image) { // protect against null here. this isn't strictly speaking required @@ -321,94 +351,25 @@ public class MenuHelper { }; } - static MenuItemsResult addVideoMenuItems( - Menu menu, - int inclusions, - final Activity activity, - final Handler handler, - final SelectedImageGetter mGetter, - final Runnable onDelete, - final Runnable preWork, - final Runnable postWork) { - - if ((inclusions & INCLUDE_VIEWPLAY_MENU) != 0) { - menu.add(VIDEO_SAVING_ITEM, MENU_VIDEO_PLAY, 0, R.string.video_play).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - if (preWork != null) - preWork.run(); - - Intent intent = new Intent(Intent.ACTION_VIEW, mGetter.getCurrentImageUri()); - activity.startActivity(intent); - - // don't do the postWork since we're launching another activity - return true; - } - }); - } - - if ((inclusions & INCLUDE_SHARE_MENU) != 0) { - MenuItem item = menu.add(VIDEO_SAVING_ITEM, 0, 0, R.string.camera_share).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - Uri u = mGetter.getCurrentImageUri(); - if (u == null) - return true; - - if (preWork != null) - preWork.run(); - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType(mGetter.getCurrentImage().getMimeType()); - intent.putExtra(Intent.EXTRA_STREAM, u); - try { - activity.startActivity(Intent.createChooser(intent, - activity.getText(R.string.sendVideo))); - } catch (android.content.ActivityNotFoundException ex) { - Toast.makeText(activity, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); - - if (postWork != null) - postWork.run(); - } - return true; - } - }); - item.setIcon(android.R.drawable.ic_menu_share); - } - - if ((inclusions & INCLUDE_DELETE_MENU) != 0) { - MenuItem deleteMenu = menu.add(VIDEO_SAVING_ITEM, MENU_VIDEO_TOSS, 0, R.string.camera_toss).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - if (preWork != null) - preWork.run(); - - activity.getContentResolver().delete(mGetter.getCurrentImageUri(), null, null); - - if (onDelete != null) - onDelete.run(); - - if (postWork != null) - postWork.run(); - - return true; - } - }); - deleteMenu.setIcon(android.R.drawable.ic_menu_delete); - deleteMenu.setAlphabeticShortcut('d'); - } + static void deletePhoto(Activity activity, Runnable onDelete) { + deleteImageImpl(activity, onDelete, true); + } - return null; + static void deleteImage(Activity activity, Runnable onDelete, IImage image) { + deleteImageImpl(activity, onDelete, ImageManager.isImage(image)); } - - static void deletePhoto(Activity activity, final Runnable onDelete) { + + private static void deleteImageImpl(Activity activity, final Runnable onDelete, boolean isPhoto) { boolean confirm = android.preference.PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("pref_gallery_confirm_delete_key", true); if (!confirm) { if (onDelete != null) onDelete.run(); } else { android.app.AlertDialog.Builder b = new android.app.AlertDialog.Builder(activity); - b.setIcon(R.drawable.delete_image); + b.setIcon(android.R.drawable.ic_dialog_alert); b.setTitle(R.string.confirm_delete_title); - b.setMessage(R.string.confirm_delete_message); + b.setMessage(isPhoto? R.string.confirm_delete_message + : R.string.confirm_delete_video_message); b.setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { public void onClick(android.content.DialogInterface v, int x) { if (onDelete != null) @@ -423,7 +384,62 @@ public class MenuHelper { b.create().show(); } } - + + static void addSwitchModeMenuItem(Menu menu, final Activity activity, + final boolean switchToVideo) { + int group = switchToVideo ? MenuHelper.IMAGE_MODE_ITEM : MenuHelper.VIDEO_MODE_ITEM; + int labelId = switchToVideo ? R.string.switch_to_video_lable + : R.string.switch_to_camera_lable; + int iconId = switchToVideo ? R.drawable.ic_menu_camera_video_view + : R.drawable.ic_menu_camera; + MenuItem item = menu.add(group, MENU_SWITCH_CAMERA_MODE, 0, + labelId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + String action = switchToVideo ? MediaStore.INTENT_ACTION_VIDEO_CAMERA + : MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA; + Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + activity.finish(); + activity.startActivity(intent); + return true; + } + }); + item.setIcon(iconId); + } + + static void addCaptureMenuItems(Menu menu, final Activity activity) { + + menu.add(0, MENU_CAPTURE_PICTURE, 1, R.string.capture_picture) + .setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + try { + activity.startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + // Ignore exception + } + return true; + } + }) + .setIcon(R.drawable.ic_menu_camera); + + menu.add(0, MENU_CAPTURE_VIDEO, 2, R.string.capture_video) + .setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); + try { + activity.startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + // Ignore exception + } + return true; + } + }) + .setIcon(R.drawable.ic_menu_camera_video_view); + } static MenuItem addFlipOrientation(Menu menu, final Activity activity, final SharedPreferences prefs) { // position 41 after rotate return menu @@ -457,65 +473,5 @@ public class MenuHelper { ? android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER : req); } - - static public class YouTubeUploadInfoDialog extends Dialog { - private CheckBox mPrivate; - private ImageManager.VideoObject mVideo; - private EditText mTitle; - private EditText mTags; - private EditText mDescription; - private Spinner mCategory; - private Button mUpload; - - public YouTubeUploadInfoDialog(final Activity activity, - final ArrayList<String> categoriesShort, - final ArrayList<String> categoriesLong, - ImageManager.VideoObject video, - final Runnable postRunnable) { - super(activity, android.R.style.Theme_Dialog); - mVideo = video; - setContentView(R.layout.youtube_upload_info); - setTitle(R.string.upload_dialog_title); - - mPrivate = (CheckBox)findViewById(R.id.public_or_private); - if (!mPrivate.isChecked()) { - mPrivate.setChecked(true); - } - - mTitle = (EditText)findViewById(R.id.video_title); - mTags = (EditText)findViewById(R.id.video_tags); - mDescription = (EditText)findViewById(R.id.video_description); - mCategory = (Spinner)findViewById(R.id.category); - - if (Config.LOGV) - Log.v(TAG, "setting categories in adapter"); - android.widget.ArrayAdapter<String> categories = new android.widget.ArrayAdapter<String>(activity, android.R.layout.simple_spinner_item, categoriesLong); - categories.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mCategory.setAdapter(categories); - - if (mVideo != null) { - mTitle.setText(mVideo.getTitle()); - mTags.setText(mVideo.getTags()); - mDescription.setText(mVideo.getDescription()); - } - - mUpload = (Button)findViewById(R.id.do_upload); - mUpload.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) - { - if (mVideo != null) { - mVideo.setName(mTitle.getText().toString()); - mVideo.setDescription(mDescription.getText().toString()); - mVideo.setTags(mTags.getText().toString()); - } - - YouTubeUploadInfoDialog.this.dismiss(); - UploadAction.uploadImage(activity, mVideo); - if (postRunnable != null) - postRunnable.run(); - } - }); - } - } } diff --git a/src/com/android/camera/MovieView.java b/src/com/android/camera/MovieView.java new file mode 100644 index 0000000..58e80df --- /dev/null +++ b/src/com/android/camera/MovieView.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 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.camera; + + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.provider.MediaStore; +import android.view.View; +import android.widget.MediaController; +import android.widget.VideoView; + +public class MovieView extends Activity implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener +{ + private static final String TAG = "MovieView"; + // Copied from MediaPlaybackService in the Music Player app. Should be public, but isn't. + private static final String SERVICECMD = "com.android.music.musicservicecommand"; + private static final String CMDNAME = "command"; + private static final String CMDPAUSE = "pause"; + + private VideoView mVideoView; + private View mProgressView; + private boolean mFinishOnCompletion; + public MovieView() + { + } + + @Override + public void onCreate(Bundle icicle) + { + super.onCreate(icicle); + + setContentView(R.layout.movie_view); + + mVideoView = (VideoView) findViewById(R.id.surface_view); + mProgressView = findViewById(R.id.progress_indicator); + Intent intent = getIntent(); + if (intent.hasExtra(MediaStore.EXTRA_SCREEN_ORIENTATION)) { + int orientation = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, + ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + if (orientation != getRequestedOrientation()) { + setRequestedOrientation(orientation); + } + } + mFinishOnCompletion = intent.getBooleanExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true); + Uri uri = intent.getData(); + + // For streams that we expect to be slow to start up, show a + // progress spinner until playback starts. + String scheme = uri.getScheme(); + if ("http".equalsIgnoreCase(scheme) || + "rtsp".equalsIgnoreCase(scheme)) { + mHandler.postDelayed(mPlayingChecker, 250); + } else { + mProgressView.setVisibility(View.GONE); + } + + mVideoView.setOnErrorListener(this); + mVideoView.setOnCompletionListener(this); + mVideoView.setVideoURI(uri); + mVideoView.setMediaController(new MediaController(this)); + mVideoView.requestFocus(); // make the video view handle keys for seeking and pausing + + Intent i = new Intent(SERVICECMD); + i.putExtra(CMDNAME, CMDPAUSE); + sendBroadcast(i); + + mVideoView.start(); + } + + @Override + public void onPause() { + mHandler.removeCallbacksAndMessages(null); + super.onPause(); + } + + Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + } + }; + + Runnable mPlayingChecker = new Runnable() { + public void run() { + if (mVideoView.isPlaying()) { + mProgressView.setVisibility(View.GONE); + } else { + mHandler.postDelayed(mPlayingChecker, 250); + } + } + }; + + public boolean onError(MediaPlayer player, int arg1, int arg2) { + mHandler.removeCallbacksAndMessages(null); + mProgressView.setVisibility(View.GONE); + return false; + } + + public void onCompletion(MediaPlayer mp) { + if (mFinishOnCompletion) { + finish(); + } + } +} diff --git a/src/com/android/camera/PwaUpload.java b/src/com/android/camera/PwaUpload.java deleted file mode 100644 index 8df08df..0000000 --- a/src/com/android/camera/PwaUpload.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2006 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.camera; - -import android.content.Intent; -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; -import android.net.Uri; - -/** - * - */ -public class PwaUpload extends Activity -{ - private static final String TAG = "camera"; - - @Override public void onCreate(Bundle icicle) { - super.onCreate(icicle); - ImageManager.IImageList imageList = ImageManager.instance().allImages( - this, - getContentResolver(), - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_IMAGES|ImageManager.INCLUDE_VIDEOS, - ImageManager.SORT_ASCENDING); - Uri uri = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM); - if (android.util.Config.LOGV) - Log.v(TAG, "uri is " + uri); - ImageManager.IImage imageObj = imageList.getImageForUri(uri); - - if (android.util.Config.LOGV) - Log.v(TAG, "imageObj is " + imageObj); - if (imageObj != null) { - UploadAction.uploadImage(this, imageObj); - } - finish(); - } - - @Override public void onResume() { - super.onResume(); - } -} diff --git a/src/com/android/camera/SlideShow.java b/src/com/android/camera/SlideShow.java index ee6c7be..2be99ac 100644 --- a/src/com/android/camera/SlideShow.java +++ b/src/com/android/camera/SlideShow.java @@ -60,21 +60,21 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory private int mCurrentPosition = 0; private ImageView mSwitcher; private boolean mPosted = false; - + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); Window wp = getWindow(); wp.setFlags(FLAG_KEEP_SCREEN_ON, FLAG_KEEP_SCREEN_ON); - + setContentView(R.layout.slide_show); - + mSwitcher = (ImageView)findViewById(R.id.imageview); if (android.util.Config.LOGV) Log.v(TAG, "mSwitcher " + mSwitcher); } - + @Override protected void onResume() { @@ -86,13 +86,13 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory } loadImage(); } - + @Override protected void onPause() { super.onPause(); cancelPost(); } - + static public class ImageViewTouch extends ImageView { class xy { public xy(float xIn, float yIn) { @@ -108,26 +108,26 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory float x,y; long timeAdded; } - + SlideShow mSlideShow; Paint mPaints[] = new Paint[1]; ArrayList<xy> mPoints = new ArrayList<xy>(); boolean mDown; - + public ImageViewTouch(Context context) { super(context); mSlideShow = (SlideShow) context; setScaleType(ImageView.ScaleType.CENTER); setupPaint(); } - + public ImageViewTouch(Context context, AttributeSet attrs) { super(context, attrs); mSlideShow = (SlideShow) context; setScaleType(ImageView.ScaleType.CENTER); setupPaint(); } - + private void setupPaint() { for (int i = 0; i < mPaints.length; i++) { Paint p = new Paint(); @@ -138,7 +138,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory mPaints[i] = p; } } - + private void addEvent(MotionEvent event) { long now = System.currentTimeMillis(); mPoints.add(new xy(event)); @@ -151,7 +151,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory mPoints.remove(0); } } - + public boolean onTouchEvent(MotionEvent event) { addEvent(event); switch (event.getAction()) { @@ -182,7 +182,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory long delta = now - ev.timeAdded; if (delta > sLag) continue; - + int alpha2 = Math.max(0, 255 - (255 * (int)delta / sLag)); if (alpha2 == 0) continue; @@ -195,7 +195,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory } } - + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -203,14 +203,14 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory cancelPost(); loadPreviousImage(); return true; - + case KeyEvent.KEYCODE_DPAD_RIGHT: cancelPost(); loadNextImage(); return true; - + case KeyEvent.KEYCODE_DPAD_CENTER: - if (mPosted) + if (mPosted) cancelPost(); else loadNextImage(); @@ -218,12 +218,12 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory } return super.onKeyDown(keyCode, event); } - + private void cancelPost() { mHandler.removeCallbacks(mNextImageRunnable); mPosted = false; } - + private void post() { mHandler.postDelayed(mNextImageRunnable, sNextImageInterval); mPosted = true; @@ -233,7 +233,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory ImageManager.IImage image = mImageList.getImageAt(mCurrentPosition); if (image == null) return; - + Bitmap bitmap = image.thumbBitmap(); if (bitmap == null) return; @@ -241,7 +241,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory mSwitcher.setImageDrawable(new BitmapDrawable(bitmap)); post(); } - + private Runnable mNextImageRunnable = new Runnable() { public void run() { if (android.util.Config.LOGV) @@ -249,13 +249,13 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory loadNextImage(); } }; - + private void loadNextImage() { if (++mCurrentPosition >= mImageList.getCount()) mCurrentPosition = 0; loadImage(); } - + private void loadPreviousImage() { if (mCurrentPosition == 0) mCurrentPosition = mImageList.getCount() - 1; @@ -272,7 +272,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory i.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); return i; } - + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -286,22 +286,22 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory public void checkThumbnails(ThumbCheckCallback cb) { // TODO Auto-generated method stub - + } public void commitChanges() { // TODO Auto-generated method stub - + } public void removeOnChangeListener(OnChange changeCallback) { // TODO Auto-generated method stub - + } public void setOnChangeListener(OnChange changeCallback, Handler h) { // TODO Auto-generated method stub - + } private ArrayList<FileImage> mImages = new ArrayList<FileImage>(); @@ -320,7 +320,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory public long imageId() { return mId; } - + public String getDataPath() { return mPath; } @@ -332,7 +332,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory public IGetBitmap_cancelable fullSizeBitmap_cancelable(int targetWidthOrHeight) { return null; } - + public Bitmap thumbBitmap() { Bitmap b = fullSizeBitmap(320); Matrix m = new Matrix(); @@ -341,7 +341,7 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory Bitmap scaledBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true); return scaledBitmap; } - + public Bitmap miniThumbBitmap() { return thumbBitmap(); } @@ -416,7 +416,11 @@ public class SlideShow extends Activity implements ViewSwitcher.ViewFactory public int getCount() { return mImages.size(); } - + + public boolean isEmpty() { + return mImages.isEmpty(); + } + public void deactivate() { // nothing to do here } diff --git a/src/com/android/camera/UploadAction.java b/src/com/android/camera/UploadAction.java deleted file mode 100644 index 41cc351..0000000 --- a/src/com/android/camera/UploadAction.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2007 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.camera; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.util.Config; -import android.util.Log; - -public class UploadAction { - static private final String TAG = "UploadAction"; - - static public void uploadImage(Activity activity, ImageManager.IImage image) { - Bundle args = new Bundle(); - if (image != null) - args.putString("imageuri", image.fullSizeImageUri().toString()); - activity.startService(new Intent(activity, UploadService.class).putExtras(args)); - } -} diff --git a/src/com/android/camera/UploadService.java b/src/com/android/camera/UploadService.java deleted file mode 100644 index 9c7d2b0..0000000 --- a/src/com/android/camera/UploadService.java +++ /dev/null @@ -1,1181 +0,0 @@ -/* - * Copyright (C) 2006 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.camera; - -import com.android.camera.ImageManager.IImage; -import com.android.internal.http.multipart.Part; -import com.android.internal.http.multipart.MultipartEntity; -import com.android.internal.http.multipart.PartBase; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Service; -import android.content.ComponentName; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcel; -import android.preference.PreferenceManager; -import android.sax.Element; -import android.sax.ElementListener; -import android.sax.EndTextElementListener; -import android.sax.RootElement; -import android.util.Config; -import android.util.Log; -import android.util.Xml; - -import com.google.android.googleapps.GoogleLoginCredentialsResult; -import com.google.android.googlelogin.GoogleLoginServiceBlockingHelper; -import com.google.android.googlelogin.GoogleLoginServiceConstants; -import com.google.android.googlelogin.GoogleLoginServiceNotFoundException; - -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; - -import android.net.http.AndroidHttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpGet; -import com.android.internal.http.multipart.StringPart; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.entity.StringEntity; -import org.apache.http.message.BasicHeader; -import org.apache.http.params.HttpParams; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EncodingUtils; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.HashMap; - -public class UploadService extends Service implements Runnable { - private static final String TAG = "UploadService"; - - static final boolean DEBUG = false; - private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; - - private GoogleLoginServiceBlockingHelper mGls; - static public final int MSG_STATUS = 3; - static public final int EVENT_UPLOAD_ERROR = 400; - - static public final String sPicasaService = "lh2"; - static public final String sYouTubeService = "youtube"; - static public final String sYouTubeUserService = "YouTubeUser"; - - static public final String sUploadAlbumName = "android_upload"; - HashMap<String, Album> mAlbums; - ArrayList<String> mAndroidUploadAlbumPhotos = null; - HashMap<String, String> mGDataAuthTokenMap = new HashMap<String, String>(); - - int mStartId; - Thread mThread; - - android.os.Handler mHandler = new android.os.Handler() { - - }; - - ArrayList<Runnable> mStatusListeners = new ArrayList<Runnable>(); - - ArrayList<Uri> mUploadList = new ArrayList<Uri>(); - - ImageManager.IImageList mImageList = null; - - String mPicasaUsername; - String mPicasaAuthToken; - String mYouTubeUsername; - String mYouTubeAuthToken; - - AndroidHttpClient mClient = AndroidHttpClient.newInstance("Android-Camera/0.1"); - - private static final ComponentName sLogin = new ComponentName( - "com.google.android.googleapps", - "com.google.android.googleapps.GoogleLoginService"); - - public UploadService() { - if (LOCAL_LOGV) - Log.v(TAG, "UploadService Constructor !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - } - - private void computeAuthToken() { - if (LOCAL_LOGV) Log.v(TAG, "computeAuthToken()"); - if (mPicasaAuthToken != null) return; - - try { - String account = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE); - GoogleLoginCredentialsResult result = - mGls.getCredentials(account, sPicasaService, true); - mPicasaAuthToken = result.getCredentialsString(); - mPicasaUsername = result.getAccount(); - if (Config.LOGV) - Log.v(TAG, "mPicasaUsername is " + mPicasaUsername); - } catch (GoogleLoginServiceNotFoundException e) { - Log.e(TAG, "Could not get auth token", e); - } - } - - private void computeYouTubeAuthToken() { - if (LOCAL_LOGV) Log.v(TAG, "computeYouTubeAuthToken()"); - if (mYouTubeAuthToken != null) return; - - try { - String account = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE); - GoogleLoginCredentialsResult result = - mGls.getCredentials(account, sYouTubeService, true); - mYouTubeAuthToken = result.getCredentialsString(); - mYouTubeUsername = result.getAccount(); - if (mYouTubeAuthToken.equals("NoLinkedYouTubeAccount")) { - // we successfully logged in to the google account, but it - // is not linked to a YouTube username. - if (Config.LOGV) - Log.v(TAG, "account " + mYouTubeUsername + " is not linked to a youtube account"); - mYouTubeAuthToken = null; - return; - } - - mYouTubeUsername = mGls.peekCredentials(mYouTubeUsername, sYouTubeUserService); - // now mYouTubeUsername is the YouTube username linked to the - // google account, which is probably what we want to display. - - if (Config.LOGV) - Log.v(TAG, "3 mYouTubeUsername: " + mYouTubeUsername); - } catch (GoogleLoginServiceNotFoundException e) { - Log.e(TAG, "Could not get auth token", e); - } - } - - NotificationManager mNotificationManager; - - @Override - public void onCreate() { - - try { - mGls = new GoogleLoginServiceBlockingHelper(this); - } catch (GoogleLoginServiceNotFoundException e) { - Log.e(TAG, "Could not find google login service, stopping service"); - stopSelf(); - } - - if (mThread == null) { - mThread = new Thread(this); - mThread.start(); - } - mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); - - IntentFilter intentFilter = new IntentFilter("com.android.camera.NEW_PICTURE"); - b = new android.content.BroadcastReceiver() { - public void onReceive(android.content.Context ctx, Intent intent) { - android.content.SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); - if (prefs.getBoolean("pref_camera_autoupload_key", false)) { - if (Config.LOGV) - Log.v(TAG, ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> auto upload " + intent.getData()); - } - } - }; - registerReceiver(b, intentFilter); - } - - android.content.BroadcastReceiver b = null; - - @Override - public void onDestroy() { - mGls.close(); - if (b != null) { - unregisterReceiver(b); - } - } - - @Override - public void onStart(Intent intent, int startId) { - if (LOCAL_LOGV) - Log.v(TAG, "UploadService.onStart; this is " + hashCode()); - - if (mImageList == null) { - mImageList = ImageManager.instance().allImages( - this, - getContentResolver(), - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS, - ImageManager.SORT_ASCENDING); - mImageList.setOnChangeListener(new ImageManager.IImageList.OnChange() { - public void onChange(ImageManager.IImageList list) { - /* - Log.v(TAG, "onChange <<<<<<<<<<<<<<<<<<<<<<<<<"); - for (int i = 0; i < list.getCount(); i++) { - ImageManager.IImage img = list.getImageAt(i); - Log.v(TAG, "pos " + i + " " + img.fullSizeImageUri()); - String picasaId = img.getPicasaId(); - if (picasaId == null || picasaId.length() == 0) { - synchronized (mUploadList) { - Uri uri = img.fullSizeImageUri(); - if (mUploadList.contains(uri)) { - mUploadList.add(img.fullSizeImageUri()); - mUploadList.notify(); - } - } - } - } - */ - } - }, mHandler); - } - - if (LOCAL_LOGV) - Log.v(TAG, "got image list with count " + mImageList.getCount()); - - synchronized (mUploadList) { - mStartId = startId; - String uriString = intent.getStringExtra("imageuri"); - - if (LOCAL_LOGV) - Log.v(TAG, "starting UploadService; startId = " + startId + " start uri: " + uriString); - - if (uriString != null) { - Uri uri = Uri.parse(uriString); - IImage image = mImageList.getImageForUri(uri); - if (!mUploadList.contains(uri)) { - if (LOCAL_LOGV) - Log.v(TAG, "queing upload of " + image.fullSizeImageUri()); - mUploadList.add(uri); - } - } else { - // for now upload all applies to images only, not videos - for (int i = 0; i < mImageList.getCount(); i++) { - IImage image = mImageList.getImageAt(i); - if (image instanceof ImageManager.Image) { - Uri uri = image.fullSizeImageUri(); - if (!mUploadList.contains(uri)) { - if (LOCAL_LOGV) - Log.v(TAG, "queing upload of " + image.fullSizeImageUri()); - mUploadList.add(uri); - } - } - } - } - updateNotification(); - } - - synchronized(mUploadList) { - mUploadList.notify(); - } - } - - void updateNotification() { - int videosCount = 0, imagesCount = 0; - for (int i = 0;i < mUploadList.size(); i++) { - // TODO yes this is a hack - Uri uri = mUploadList.get(i); - if (uri.toString().contains("video")) - videosCount += 1; - else - imagesCount += 1; - } - updateNotification(imagesCount, videosCount); - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - // This is the object that recieves interactions from clients. - private final IBinder mBinder = new Binder() { - protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { - return true; - } - }; - - private void updateNotification(int pendingImagesCount, int pendingVideosCount) { - final int mVideoUploadId = 1; - final int mImageUploadId = 2; - if (pendingImagesCount == 0) { - if (mNotificationManager != null) - mNotificationManager.cancel(mImageUploadId); - } else { - String detailedMsg = String.format(getResources().getString(R.string.uploadingNPhotos), pendingImagesCount); - Notification n = new Notification( - this, - android.R.drawable.stat_sys_upload, - getResources().getString(R.string.uploading_photos), - System.currentTimeMillis(), - getResources().getString(R.string.uploading_photos_2), - detailedMsg, - null); - mNotificationManager.notify(mImageUploadId, n); - } - if (pendingVideosCount == 0) { - if (mNotificationManager != null) - mNotificationManager.cancel(mVideoUploadId); - } else { - String detailedMsg = String.format(getResources().getString(R.string.uploadingNVideos), pendingImagesCount); - Notification n = new Notification( - this, - android.R.drawable.stat_sys_upload, - getResources().getString(R.string.uploading_videos), - System.currentTimeMillis(), - getResources().getString(R.string.uploading_videos_2), - detailedMsg, - null); - mNotificationManager.notify(mVideoUploadId, n); - } - } - - public void run() { - try { - if (Config.LOGV) - Log.v(TAG, "running upload thread..."); - while (true) { - IImage image = null; - synchronized (mUploadList) { - if (LOCAL_LOGV) - Log.v(TAG, "mUploadList.size() is " + mUploadList.size()); - if (mUploadList.size() == 0) { - try { - updateNotification(0, 0); - if (Config.LOGV) - Log.v(TAG, "waiting..."); - mUploadList.wait(60000); - if (Config.LOGV) - Log.v(TAG, "done waiting..."); - } catch (InterruptedException ex) { - } - if (mUploadList.size() == 0) { -// if (LOCAL_LOGV) Log.v(TAG, "exiting run, stoping service"); -// stopSelf(mStartId); -// break; - continue; - } - } - Uri uri = mUploadList.get(0); - image = mImageList.getImageForUri(uri); - if (Config.LOGV) - Log.v(TAG, "got uri " + uri + " " + image); - } - - boolean success = false; - if (image != null) { - updateNotification(); - - long t1 = System.currentTimeMillis(); - success = uploadItem(image); - long t2 = System.currentTimeMillis(); - if (LOCAL_LOGV) Log.v(TAG, "upload took " + (t2-t1) + "; success = " + success); - } - - synchronized (mUploadList) { - mUploadList.remove(0); - if (!success && image != null) { - mUploadList.add(image.fullSizeImageUri()); - } - } - if (!success) { - int retryDelay = 30000; - if (LOCAL_LOGV) - Log.v(TAG, "failed to upload " + image.fullSizeImageUri() + " trying again in " + retryDelay + " ms"); - try { - synchronized (mUploadList) { - long t1x = System.currentTimeMillis(); - mUploadList.wait(retryDelay); - long t2x = System.currentTimeMillis(); - if (Config.LOGV) - Log.v(TAG, "retry waited " + (t2x-t1x)); - } - } catch (InterruptedException ex) { - if (Config.LOGV) - Log.v(TAG, "ping, was waiting but now retry again"); - }; - } - } - } catch (Exception ex) { - Log.e(TAG, "got exception in upload thread", ex); - } - finally { - if (LOCAL_LOGV) - Log.v(TAG, "finished task"); - } - } - - private String getLatLongString(IImage image) { - if (image.hasLatLong()) { - return "<georss:where><gml:Point><gml:pos>" - + image.getLatitude() - + " " - + image.getLongitude() - + "</gml:pos></gml:Point></georss:where>"; - } else { - return ""; - } - } - - private String uploadAlbumName() { - android.content.SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - String s = prefs.getString("pref_camera_upload_albumname_key", sUploadAlbumName); - return s; - } - - private boolean uploadItem(IImage image) { - if (LOCAL_LOGV) - Log.v(TAG, "starting work on " + image); - - if (image instanceof ImageManager.VideoObject) { - if (LOCAL_LOGV) - Log.v(TAG, "Uploading video"); - computeYouTubeAuthToken(); - return (new VideoUploadTask(image)).upload(); - } else { - if (LOCAL_LOGV) - Log.v(TAG, "Uploading photo"); - - computeAuthToken(); - // handle photos - if (mAlbums == null) - mAlbums = getAlbums(); - - String albumName = uploadAlbumName(); - if (mAlbums == null || !mAlbums.containsKey(albumName)) { - Album a = createAlbum(albumName, uploadAlbumName()); - if (a == null) { - return false; - } - if (LOCAL_LOGV) - Log.v(TAG, "made new album: " + a.getAlbumName() + "; " + a.getAlbumId()); - mAlbums.put(a.getAlbumName(), a); - } - - if (mAndroidUploadAlbumPhotos == null) - mAndroidUploadAlbumPhotos = getAlbumContents(albumName); - - if (mAndroidUploadAlbumPhotos != null) { - String previousUploadId = image.getPicasaId(); - if (previousUploadId != null) { - if (mAndroidUploadAlbumPhotos.contains(previousUploadId)) { - if (Config.LOGV) - Log.v(TAG, "already have id " + previousUploadId); - return true; - } - } - } - Album album = mAlbums.get(albumName); - return (new ImageUploadTask(image)).upload(album); - } - } - -// void broadcastError(int error) { -// HashMap map = new HashMap(); -// map.put("error", new Integer(error)); -// -// Message send = Message.obtain(); -// send.what = EVENT_UPLOAD_ERROR; -// send.setData(map); -// -// if (mBroadcaster == null) { -// mBroadcaster = new Broadcaster(); -// } -// mBroadcaster.broadcast(send); -// } - - class Album { - String mAlbumName; - - String mAlbumId; - - public Album() { - } - - public void setAlbumName(String albumName) { - mAlbumName = albumName; - } - - public void setAlbumId(String albumId) { - mAlbumId = albumId; - } - - public String getAlbumName() { - return mAlbumName; - } - - public String getAlbumId() { - return mAlbumId; - } - } - - static private String stringFromResponse(HttpResponse response) { - try { - HttpEntity entity = response.getEntity(); - InputStream inputStream = entity.getContent(); - StringWriter s = new StringWriter(); - while (true) { - int c = inputStream.read(); - if (c == -1) - break; - s.write((char)c); - } - inputStream.close(); - String retval = s.toString(); - if (Config.LOGV) - Log.v(TAG, "got resposne " + retval); - return retval; - } catch (Exception ex) { - return null; - } - } - - abstract class UploadTask { - IImage mImageObj; - - public UploadTask(IImage image) { - mImageObj = image; - } - - public class UploadResponse { - private HttpResponse mStatus; - private String mBody; - - public UploadResponse(HttpResponse status) { - mStatus = status; - mBody = stringFromResponse(status); - } - - public int getStatus() { - return mStatus.getStatusLine().getStatusCode(); - } - - public String getResponse() { - return mBody; - } - } - - class StreamPart extends PartBase { - InputStream mInputStream; - long mLength; - - StreamPart(String name, InputStream inputStream, String contentType) { - super(name, - contentType == null ? "application/octet-stream" : contentType, - "ISO-8859-1", - "binary" - ); - mInputStream = inputStream; - try { - mLength = inputStream.available(); - } catch (IOException ex) { - - } - } - - @Override - protected long lengthOfData() throws IOException { - return mLength; - } - - @Override - protected void sendData(OutputStream out) throws IOException { - byte [] buffer = new byte[4096]; - while (true) { - int got = mInputStream.read(buffer); - if (got == -1) - break; - out.write(buffer, 0, got); - } - mInputStream.close(); - } - - @Override - protected void sendDispositionHeader(OutputStream out) throws IOException { - } - - @Override - protected void sendContentTypeHeader(OutputStream out) throws IOException { - String contentType = getContentType(); - if (contentType != null) { - out.write(CONTENT_TYPE_BYTES); - out.write(EncodingUtils.getAsciiBytes(contentType)); - String charSet = getCharSet(); - if (charSet != null) { - out.write(CHARSET_BYTES); - out.write(EncodingUtils.getAsciiBytes(charSet)); - } - } - } - } - - public class StringPartX extends StringPart { - public StringPartX(String name, String value, String charset) { - super(name, value, charset); - setContentType("application/atom+xml"); - } - - @Override - protected void sendDispositionHeader(OutputStream out) throws IOException { - } - - @Override - protected void sendContentTypeHeader(OutputStream out) throws IOException { - String contentType = getContentType(); - if (contentType != null) { - out.write(CONTENT_TYPE_BYTES); - out.write(EncodingUtils.getAsciiBytes(contentType)); - String charSet = getCharSet(); - if (charSet != null) { - out.write(CHARSET_BYTES); - out.write(EncodingUtils.getAsciiBytes(charSet)); - } - } - } - } - - public class MultipartEntityX extends MultipartEntity { - public MultipartEntityX(Part[] parts, HttpParams params) { - super(parts, params); - } - - @Override - public Header getContentType() { - StringBuilder buffer = new StringBuilder(); - buffer.append("multipart/related; boundary="); - buffer.append(EncodingUtils.getAsciiString(getMultipartBoundary())); - return new BasicHeader(HTTP.CONTENT_TYPE, buffer.toString()); - } - - } - - protected UploadResponse doUpload(String uploadUrl, - String mimeType, - String data, - IImage imageObj, - String authToken, - String title, - String filename, - boolean youTubeAuthenticate) { - if (authToken == null) - return null; - - FileInputStream inputStream = (FileInputStream)mImageObj.fullSizeImageData(); - try { - HttpPost post = new HttpPost(uploadUrl); - post.addHeader(new BasicHeader("Authorization", "GoogleLogin auth=" + authToken)); - if (youTubeAuthenticate) { - // TODO: remove hardwired key? - This is our official YouTube issued developer key to Android. - String youTubeDeveloperKey = "key=AI39si5Cr35CiD1IgDqD9Ua6N4dSbY-oibnLUPITmBN_rFW6qRz-hd8sTqNzRf1gzNwSYZbDuS31Txa4iKyjAV77507O4tq7JA"; - post.addHeader("X-GData-Key", youTubeDeveloperKey); - post.addHeader("Slug", filename); - } - - Part p1 = new StringPartX("param_name", data, null); - Part p2 = new StreamPart("field_uploadfile", inputStream, mimeType); - - MultipartEntity mpe = new MultipartEntityX(new Part[] { p1, p2 }, post.getParams()); - post.setEntity(mpe); - HttpResponse status = mClient.execute(post); - if (LOCAL_LOGV) Log.v(TAG, "doUpload response is " + status.getStatusLine()); - return new UploadResponse(status); - } catch (java.io.IOException ex) { - if (LOCAL_LOGV) Log.v(TAG, "IOException in doUpload", ex); - return null; - } - } - - class ResponseHandler implements ElementListener { - private static final String ATOM_NAMESPACE - = "http://www.w3.org/2005/Atom"; - private static final String PICASSA_NAMESPACE - = "http://schemas.google.com/photos/2007"; - - private ContentHandler mHandler = null; - private String mId = null; - - public ResponseHandler() { - RootElement root = new RootElement(ATOM_NAMESPACE, "entry"); - Element entry = root; - entry.setElementListener(this); - - entry.getChild(PICASSA_NAMESPACE, "id") - .setEndTextElementListener(new EndTextElementListener() { - public void end(String body) { - mId = body; - } - }); - - mHandler = root.getContentHandler(); - } - - public void start(Attributes attributes) { - } - - public void end() { - } - - ContentHandler getContentHandler() { - return mHandler; - } - - public String getId() { - return mId; - } - } - } - - private class VideoUploadTask extends UploadTask { - public VideoUploadTask(IImage image) { - super(image); - } - protected String getYouTubeBaseUrl() { - return "http://gdata.youtube.com"; - } - - public boolean upload() { - String uploadUrl = "http://uploads.gdata.youtube.com" - + "/feeds/users/" - + mYouTubeUsername - + "/uploads?client=ytapi-google-android"; - - String title = mImageObj.getTitle(); - String isPrivate = ""; - String keywords = ""; - String category = ""; - if (mImageObj instanceof ImageManager.VideoObject) { - ImageManager.VideoObject video = (ImageManager.VideoObject)mImageObj; - if (mImageObj.getIsPrivate()) { - isPrivate = "<yt:private/>"; - } - keywords = video.getTags(); - if (keywords == null || keywords.trim().length() == 0) { - // there must be a keyword or YouTube will reject the video - keywords = getResources().getString(R.string.upload_default_tags_text); - } - // TODO: use the real category when we have the category spinner in details -// category = video.getCategory(); - category = ""; - if (category == null || category.trim().length() == 0) { - // there must be a description or YouTube will get an internal error and return 500 - category = getResources().getString(R.string.upload_default_category_text); - } - } - String description = mImageObj.getDescription(); - if (description == null || description.trim().length() == 0) { - // there must be a description or YouTube will get an internal error and return 500 - description = getResources().getString(R.string.upload_default_description_text); - } - String data = "<?xml version='1.0'?>\n" - + "<entry xmlns='http://www.w3.org/2005/Atom'\n" - + " xmlns:media='http://search.yahoo.com/mrss/'\n" - + " xmlns:yt='http://gdata.youtube.com/schemas/2007'>\n" - + " <media:group>\n" - + " <media:title type='plain'>" + title + "</media:title>\n" // TODO: need user entered title - + " <media:description type='plain'>" + description + "</media:description>\n" - + isPrivate - + " <media:category scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>\n" - + category - + " </media:category>\n" - + " <media:keywords>" + keywords + "</media:keywords>\n" - + " </media:group>\n" - + "</entry>"; - - if (LOCAL_LOGV) Log.v("youtube", "uploadUrl: " + uploadUrl); - if (LOCAL_LOGV) Log.v("youtube", "GData: " + data); - - UploadResponse result = doUpload(uploadUrl, - "video/3gpp2", - data, - null, - mYouTubeAuthToken, - title, - mImageObj.fullSizeImageUri().getLastPathSegment(), - true); - - boolean success = false; - if (result != null) { - switch (result.getStatus()) { - case 401: - if (result.getResponse().contains("Token expired")) { - // When we tried to upload a video to YouTube, the youtube server told us - // our auth token was expired. Get a new one and try again. - try { - mGls.invalidateAuthToken(mYouTubeAuthToken); - } catch (GoogleLoginServiceNotFoundException e) { - Log.e(TAG, "Could not invalidate youtube auth token", e); - } - mYouTubeAuthToken = null; // Forces computeYouTubeAuthToken to get a new token. - computeYouTubeAuthToken(); - } - break; - - case 200: - case 201: - case 202: - case 203: - case 204: - case 205: - case 206: - success = true; - break; - - } - } - return success; - } - } - - private class ImageUploadTask extends UploadTask { - public ImageUploadTask(IImage image) { - super(image); - } - - public boolean upload(Album album) { - String uploadUrl = getServiceBaseUrl() - + mPicasaUsername - + "/album/" - + album.getAlbumId(); - - String name = mImageObj.getTitle(); - String description = mImageObj.getDescription(); - String data = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:georss='http://www.georss.org/georss' xmlns:gml='http://www.opengis.net/gml'><title>" - + name - + "</title>" - + "<summary>" - + (description != null ? description : "") - + "</summary>" - + getLatLongString(mImageObj) - + "<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/photos/2007#photo'/></entry>\n"; - - if (LOCAL_LOGV) - Log.v(TAG, "xml for image is " + data); - UploadResponse response = doUpload(uploadUrl, - "image/jpeg", - data, - mImageObj, - mPicasaAuthToken, - name, - name, - false); - - if (response != null) { - int status = response.getStatus(); - if (status == HttpStatus.SC_UNAUTHORIZED || - status == HttpStatus.SC_FORBIDDEN || - status == HttpStatus.SC_INTERNAL_SERVER_ERROR) { - try { - mGls.invalidateAuthToken(mPicasaAuthToken); - } catch (GoogleLoginServiceNotFoundException e) { - Log.e(TAG, "Could not invalidate picasa auth token", e); - } - mPicasaAuthToken = null; - } else { - ResponseHandler h = new ResponseHandler(); - try { - Xml.parse(response.getResponse(), h.getContentHandler()); - String id = h.getId(); - if (id != null && mImageObj != null) { - mImageObj.setPicasaId(id); - mAndroidUploadAlbumPhotos.add(id); - return true; - } - } catch (org.xml.sax.SAXException ex) { - Log.e(TAG, "SAXException in doUpload " + ex.toString()); - } - } - } - return false; - } - } - - private Album createAlbum(String name, String summary) { - String authToken = mPicasaAuthToken; - if (authToken == null) - return null; - - try { - String url = getServiceBaseUrl() + mPicasaUsername; - HttpPost post = new HttpPost(url); - String entryString = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:media='http://search.yahoo.com/mrss/' xmlns:gphoto='http://schemas.google.com/photos/2007'>" - + "<title type='text'>" - + name - + "</title>" - + "<summary>" - + summary - + "</summary>" - + "<gphoto:access>private</gphoto:access>" - + "<gphoto:commentingEnabled>true</gphoto:commentingEnabled>" - + "<gphoto:timestamp>" - + String.valueOf(System.currentTimeMillis()) - + "</gphoto:timestamp>" - + "<category scheme=\"http://schemas.google.com/g/2005#kind\" term=\"http://schemas.google.com/photos/2007#album\"/>" - + "</entry>\n"; - - StringEntity entity = new StringEntity(entryString); - entity.setContentType(new BasicHeader("Content-Type", "application/atom+xml")); - post.setEntity(entity); - post.addHeader(new BasicHeader("Authorization", "GoogleLogin auth=" + authToken)); - HttpResponse status = mClient.execute(post); - if (LOCAL_LOGV) - Log.v(TAG, "status is " + status.getStatusLine()); - if (status.getStatusLine().getStatusCode() < 200 || status.getStatusLine().getStatusCode() >= 300) { - return null; - } - Album album = new Album(); - Xml.parse(stringFromResponse(status), new PicasaAlbumHandler(album).getContentHandler()); - return album; - } catch (java.io.UnsupportedEncodingException ex) { - Log.e(TAG, "gak, UnsupportedEncodingException " + ex.toString()); - } catch (java.io.IOException ex) { - Log.e(TAG, "IOException " + ex.toString()); - } catch (org.xml.sax.SAXException ex) { - Log.e(TAG, "XmlPullParserException " + ex.toString()); - } - return null; - } - - public static String streamToString(InputStream stream, int maxChars, boolean reset) - throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(stream), 8192); - StringBuilder sb = new StringBuilder(); - String line = null; - - while ((line = reader.readLine()) != null - && (maxChars == -1 || sb.length() < maxChars)) { - sb.append(line); - } - reader.close(); - if (reset) stream.reset(); - return sb.toString(); - } - - InputStream get(String url) { - try { - if (LOCAL_LOGV) Log.v(TAG, "url is " + url); - - for (int i = 0; i < 2; ++i) { - HttpGet get = new HttpGet(url); - get.setHeader(new BasicHeader("Authorization", - "GoogleLogin auth=" + mPicasaAuthToken)); - - HttpResponse response = mClient.execute(get); - if (LOCAL_LOGV) Log.v(TAG, "response is " + response.getStatusLine()); - switch (response.getStatusLine().getStatusCode()) { - case HttpStatus.SC_UNAUTHORIZED: - case HttpStatus.SC_FORBIDDEN: - case HttpStatus.SC_INTERNAL_SERVER_ERROR: // http://b/1151576 - try { - mGls.invalidateAuthToken(mPicasaAuthToken); - } catch (GoogleLoginServiceNotFoundException e) { - Log.e(TAG, "Could not invalidate picasa auth token", e); - } - mPicasaAuthToken = null; - computeAuthToken(); - if (mPicasaAuthToken != null) { - // retry fetch after getting new token - continue; - } - break; - } - - InputStream inputStream = response.getEntity().getContent(); - return inputStream; - } - return null; - } catch (java.io.IOException ex) { - Log.e(TAG, "IOException"); - } - return null; - } - - private HashMap<String, Album> getAlbums() { - if (LOCAL_LOGV) - Log.v(TAG, "getAlbums"); - - PicasaAlbumHandler h = new PicasaAlbumHandler(); - try { - String url = getServiceBaseUrl() + mPicasaUsername + "?kind=album"; - InputStream inputStream = get(url); - if (inputStream == null) { - if (Config.LOGV) - Log.v(TAG, "can't get " + url + "; bail from getAlbums()"); - mPicasaAuthToken = null; - return null; - } - - Xml.parse(inputStream, Xml.findEncodingByName("UTF-8"), h.getContentHandler()); - if (LOCAL_LOGV) - Log.v(TAG, "done getting albums"); - inputStream.close(); - } catch (IOException e) { - Log.e(TAG, "got exception " + e.toString()); - e.printStackTrace(); - } catch (SAXException e) { - Log.e(TAG, "got exception " + e.toString()); - e.printStackTrace(); - } - if (LOCAL_LOGV) { - java.util.Iterator it = h.getAlbums().keySet().iterator(); - while (it.hasNext()) { - if (Config.LOGV) - Log.v(TAG, "album: " + (String) it.next()); - } - } - return h.getAlbums(); - } - - ArrayList<String> getAlbumContents(String albumName) { - String url = getServiceBaseUrl() + mPicasaUsername + "/album/" + albumName + "?kind=photo&max-results=10000"; - try { - InputStream inputStream = get(url); - if (inputStream == null) - return null; - - AlbumContentsHandler ah = new AlbumContentsHandler(); - Xml.parse(inputStream, Xml.findEncodingByName("UTF-8"), ah.getContentHandler()); - ArrayList<String> photos = ah.getPhotos(); - inputStream.close(); - return photos; - } catch (IOException e) { - Log.e(TAG, "got IOException " + e.toString()); - e.printStackTrace(); - } catch (SAXException e) { - Log.e(TAG, "got SAXException " + e.toString()); - e.printStackTrace(); - } - return null; - } - - class AlbumContentsHandler implements ElementListener { - private static final String ATOM_NAMESPACE - = "http://www.w3.org/2005/Atom"; - private static final String PICASA_NAMESPACE - = "http://schemas.google.com/photos/2007"; - - private ContentHandler mHandler = null; - private ArrayList<String> mPhotos = new ArrayList<String>(); - - public AlbumContentsHandler() { - RootElement root = new RootElement(ATOM_NAMESPACE, "feed"); - Element entry = root.getChild(ATOM_NAMESPACE, "entry"); - - entry.setElementListener(this); - - entry.getChild(PICASA_NAMESPACE, "id") - .setEndTextElementListener(new EndTextElementListener() { - public void end(String body) { - mPhotos.add(body); - } - }); - - mHandler = root.getContentHandler(); - } - - public void start(Attributes attributes) { - } - - public void end() { - } - - ContentHandler getContentHandler() { - return mHandler; - } - - public ArrayList<String> getPhotos() { - return mPhotos; - } - } - - private String getServiceBaseUrl() { - return "http://picasaweb.google.com/data/feed/api/user/"; - } - - - class PicasaAlbumHandler implements ElementListener { - private Album mAlbum; - private HashMap<String, Album> mAlbums = new HashMap<String, Album>(); - private boolean mJustOne; - private static final String ATOM_NAMESPACE - = "http://www.w3.org/2005/Atom"; - private static final String PICASSA_NAMESPACE - = "http://schemas.google.com/photos/2007"; - private ContentHandler handler = null; - - public PicasaAlbumHandler() { - mJustOne = false; - init(); - } - - public HashMap<String, Album> getAlbums() { - return mAlbums; - } - - public PicasaAlbumHandler(Album album) { - mJustOne = true; - mAlbum = album; - init(); - } - - private void init() { - Element entry; - RootElement root; - if (mJustOne) { - root = new RootElement(ATOM_NAMESPACE, "entry"); - entry = root; - } else { - root = new RootElement(ATOM_NAMESPACE, "feed"); - entry = root.getChild(ATOM_NAMESPACE, "entry"); - } - entry.setElementListener(this); - - entry.getChild(ATOM_NAMESPACE, "title") - .setEndTextElementListener(new EndTextElementListener() { - public void end(String body) { - mAlbum.setAlbumName(body); - } - }); - - entry.getChild(PICASSA_NAMESPACE, "name") - .setEndTextElementListener(new EndTextElementListener() { - public void end(String body) { - mAlbum.setAlbumId(body); - } - }); - - this.handler = root.getContentHandler(); - } - - public void start(Attributes attributes) { - if (!mJustOne) { - mAlbum = new Album(); - } - } - - public void end() { - if (!mJustOne) { - mAlbums.put(mAlbum.getAlbumName(), mAlbum); - mAlbum = null; - } - } - - ContentHandler getContentHandler() { - return handler; - } - } -} diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java new file mode 100644 index 0000000..7fcab9a --- /dev/null +++ b/src/com/android/camera/VideoCamera.java @@ -0,0 +1,810 @@ +/* + * Copyright (C) 2007 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.camera; + +import java.io.File; +import java.util.ArrayList; +import java.io.IOException; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.ColorDrawable; +import android.location.LocationManager; +import android.media.MediaMetadataRetriever; +import android.media.MediaRecorder; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.os.StatFs; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Video; +import android.text.format.DateFormat; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.animation.Animation; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.RelativeLayout.LayoutParams; + +public class VideoCamera extends Activity implements View.OnClickListener, SurfaceHolder.Callback { + + private static final String TAG = "videocamera"; + + private static final boolean DEBUG = true; + private static final boolean DEBUG_SUPPRESS_AUDIO_RECORDING = DEBUG && true; + private static final boolean DEBUG_DO_NOT_REUSE_MEDIA_RECORDER = DEBUG && true; + + private static final int KEEP = 2; + private static final int CLEAR_SCREEN_DELAY = 4; + private static final int UPDATE_RECORD_TIME = 5; + private static final int RESTART_PREVIEW = 6; + + private static final int SCREEN_DELAY = 2 * 60 * 1000; + private static final int POST_PICTURE_ALERT_TIMEOUT = 6 * 1000; + + private static final int NO_STORAGE_ERROR = -1; + private static final int CANNOT_STAT_ERROR = -2; + + public static final int MENU_SWITCH_TO_VIDEO = 0; + public static final int MENU_SWITCH_TO_CAMERA = 1; + public static final int MENU_SETTINGS = 6; + public static final int MENU_GALLERY_PHOTOS = 7; + public static final int MENU_GALLERY_VIDEOS = 8; + public static final int MENU_SAVE_SELECT_PHOTOS = 30; + public static final int MENU_SAVE_NEW_PHOTO = 31; + public static final int MENU_SAVE_SELECTVIDEO = 32; + public static final int MENU_SAVE_TAKE_NEW_VIDEO = 33; + public static final int MENU_SAVE_GALLERY_PHOTO = 34; + public static final int MENU_SAVE_GALLERY_VIDEO_PHOTO = 35; + public static final int MENU_SAVE_CAMERA_DONE = 36; + public static final int MENU_SAVE_CAMERA_VIDEO_DONE = 37; + + Toast mToast; + SharedPreferences mPreferences; + + private static final float VIDEO_ASPECT_RATIO = 176.0f / 144.0f; + VideoPreview mVideoPreview; + SurfaceHolder mSurfaceHolder = null; + ImageView mBlackout = null; + ImageView mVideoFrame; + Bitmap mVideoFrameBitmap; + + private MediaRecorder mMediaRecorder; + private boolean mMediaRecorderRecording = false; + private long mRecordingStartTime; + private String mCurrentVideoFilename; + private Uri mCurrentVideoUri; + + boolean mPausing = false; + + static ContentResolver mContentResolver; + boolean mDidRegister = false; + + int mCurrentZoomIndex = 0; + + private ImageView mModeIndicatorView; + private ImageView mRecordingIndicatorView; + private TextView mRecordingTimeView; + + ArrayList<MenuItem> mGalleryItems = new ArrayList<MenuItem>(); + + View mPostPictureAlert; + LocationManager mLocationManager = null; + + private int mPicturesRemaining; + + private Handler mHandler = new MainHandler(); + + private void cancelSavingNotification() { + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + } + + /** This Handler is used to post message back onto the main thread of the application */ + private class MainHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case KEEP: { + keep(); + + if (msg.obj != null) { + mHandler.post((Runnable)msg.obj); + } + break; + } + + case CLEAR_SCREEN_DELAY: { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + break; + } + + case UPDATE_RECORD_TIME: { + if (mMediaRecorderRecording) { + long now = SystemClock.uptimeMillis(); + long delta = now - mRecordingStartTime; + long seconds = delta / 1000; + long minutes = seconds / 60; + long remainderSeconds = seconds - (minutes * 60); + + String secondsString = Long.toString(remainderSeconds); + if (secondsString.length() < 2) { + secondsString = "0" + secondsString; + } + String minutesString = Long.toString(minutes); + if (minutesString.length() < 2) { + minutesString = "0" + minutesString; + } + String text = minutesString + ":" + secondsString; + mRecordingTimeView.setText(text); + mHandler.sendEmptyMessageDelayed(UPDATE_RECORD_TIME, 1000); + } + break; + } + + case RESTART_PREVIEW: + hideVideoFrameAndStartPreview(); + break; + + default: + Log.v(TAG, "Unhandled message: " + msg.what); + break; + } + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { + // SD card available + // TODO put up a "please wait" message + // TODO also listen for the media scanner finished message + showStorageToast(); + } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { + // SD card unavailable + showStorageToast(); + } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { + Toast.makeText(VideoCamera.this, getResources().getString(R.string.wait), 5000); + } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { + showStorageToast(); + } + } + }; + + static private String createName(long dateTaken) { + return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString(); + } + + private void postAfterKeep(final Runnable r) { + Message msg = mHandler.obtainMessage(KEEP); + msg.obj = r; + msg.sendToTarget(); + } + + /** Called with the activity is first created. */ + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + + mPreferences = PreferenceManager.getDefaultSharedPreferences(this); + mContentResolver = getContentResolver(); + + //setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); + requestWindowFeature(Window.FEATURE_PROGRESS); + + Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + setContentView(R.layout.video_camera); + + mVideoPreview = (VideoPreview) findViewById(R.id.camera_preview); + mVideoPreview.setAspectRatio(VIDEO_ASPECT_RATIO); + + // don't set mSurfaceHolder here. We have it set ONLY within + // surfaceCreated / surfaceDestroyed, other parts of the code + // assume that when it is set, the surface is also set. + SurfaceHolder holder = mVideoPreview.getHolder(); + holder.addCallback(this); + holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + + mBlackout = (ImageView) findViewById(R.id.blackout); + mBlackout.setBackgroundDrawable(new ColorDrawable(0xFF000000)); + + mPostPictureAlert = findViewById(R.id.post_picture_panel); + View b; + + b = findViewById(R.id.play); + b.setOnClickListener(this); + + b = findViewById(R.id.share); + b.setOnClickListener(this); + + b = findViewById(R.id.discard); + b.setOnClickListener(this); + + mModeIndicatorView = (ImageView) findViewById(R.id.mode_indicator); + mRecordingIndicatorView = (ImageView) findViewById(R.id.recording_indicator); + mRecordingTimeView = (TextView) findViewById(R.id.recording_time); + mVideoFrame = (ImageView) findViewById(R.id.video_frame); + } + + @Override + public void onStart() { + super.onStart(); + + final View hintView = findViewById(R.id.hint_toast); + if (hintView != null) + hintView.setVisibility(View.GONE); + + Thread t = new Thread(new Runnable() { + public void run() { + final boolean storageOK = calculatePicturesRemaining() > 0; + if (hintView == null) + return; + + if (storageOK) { + mHandler.post(new Runnable() { + public void run() { + hintView.setVisibility(View.VISIBLE); + } + }); + mHandler.postDelayed(new Runnable() { + public void run() { + Animation a = new android.view.animation.AlphaAnimation(1F, 0F); + a.setDuration(500); + a.startNow(); + hintView.setAnimation(a); + hintView.setVisibility(View.GONE); + } + }, 3000); + } else { + mHandler.post(new Runnable() { + public void run() { + hintView.setVisibility(View.GONE); + showStorageToast(); + } + }); + } + } + }); + t.start(); + } + + public void onClick(View v) { + switch (v.getId()) { + case R.id.discard: { + File f = new File(mCurrentVideoFilename); + f.delete(); + mContentResolver.delete(mCurrentVideoUri, null, null); + + hideVideoFrameAndStartPreview(); + break; + } + + case R.id.share: { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("video/3gpp"); + intent.putExtra(Intent.EXTRA_STREAM, mCurrentVideoUri); + try { + startActivity(Intent.createChooser(intent, getText(R.string.sendVideo))); + } catch (android.content.ActivityNotFoundException ex) { + Toast.makeText(VideoCamera.this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); + } + + break; + } + + case R.id.play: { + Intent intent = new Intent(Intent.ACTION_VIEW, mCurrentVideoUri); + try { + startActivity(intent); + } catch (android.content.ActivityNotFoundException ex) { + Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex); + } + break; + } + } + } + + private void showStorageToast() { + String noStorageText = null; + int remaining = calculatePicturesRemaining(); + + if (remaining == NO_STORAGE_ERROR) { + noStorageText = getString(R.string.no_storage); + } else if (remaining < 1) { + noStorageText = getString(R.string.not_enough_space); + } + + if (noStorageText != null) { + Toast.makeText(this, noStorageText, 5000).show(); + } + } + + @Override + public void onResume() { + super.onResume(); + mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); + + mPausing = false; + + // install an intent filter to receive SD card related events. + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); + intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); + intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); + intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); + intentFilter.addDataScheme("file"); + registerReceiver(mReceiver, intentFilter); + mDidRegister = true; + + mBlackout.setVisibility(View.INVISIBLE); + if (mVideoFrameBitmap == null) { + initializeVideo(); + } else { + showPostRecordingAlert(); + } + } + + @Override + public void onStop() { + Log.v(TAG, "onStop"); + stopVideoRecording(); + keep(); + mHandler.removeMessages(CLEAR_SCREEN_DELAY); + super.onStop(); + } + + @Override + protected void onPause() { + Log.v(TAG, "onPause"); + stopVideoRecording(); + keep(); + hidePostPictureAlert(); + + mPausing = true; + + if (mDidRegister) { + unregisterReceiver(mReceiver); + mDidRegister = false; + } + + super.onPause(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + if (mMediaRecorderRecording) { + Log.v(TAG, "onKeyBack"); + stopVideoRecordingAndDisplayDialog(); + return true; + } else if(isPostRecordingAlertVisible()) { + hideVideoFrameAndStartPreview(); + return true; + } + break; + case KeyEvent.KEYCODE_FOCUS: + return true; + case KeyEvent.KEYCODE_CAMERA: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (event.getRepeatCount() == 0) { + if (!mMediaRecorderRecording) { + startVideoRecording(); + } else { + stopVideoRecordingAndDisplayDialog(); + } + return true; + } + return true; + case KeyEvent.KEYCODE_MENU: + if (mMediaRecorderRecording) { + stopVideoRecordingAndDisplayDialog(); + return true; + } + hideVideoFrameAndStartPreview(); + break; + } + + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + cancelRestartPreviewTimeout(); + return super.onTrackballEvent(event); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + stopVideoRecording(); + initializeVideo(); + } + + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceHolder = holder; + } + + public void surfaceDestroyed(SurfaceHolder holder) { + mSurfaceHolder = null; + } + + void gotoGallery() { + Uri target = Video.Media.INTERNAL_CONTENT_URI; + Intent intent = new Intent(Intent.ACTION_VIEW, target); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Could not start gallery activity", e); + } + } + + void keep() { + cancelSavingNotification(); + }; + + void toss() { + cancelSavingNotification(); + }; + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + for (int i = 1; i <= MenuHelper.MENU_ITEM_MAX; i++) { + if (i != MenuHelper.GENERIC_ITEM) { + menu.setGroupVisible(i, false); + } + } + + menu.setGroupVisible(MenuHelper.VIDEO_MODE_ITEM, true); + + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + addBaseMenuItems(menu); + MenuHelper.addImageMenuItems( + menu, + MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_ROTATE_MENU, + false, + VideoCamera.this, + mHandler, + + // Handler for deletion + new Runnable() { + public void run() { + // What do we do here? + // mContentResolver.delete(uri, null, null); + } + }, + new MenuHelper.MenuInvoker() { + public void run(final MenuHelper.MenuCallback cb) { + } + }); + + MenuItem gallery = menu.add(MenuHelper.IMAGE_SAVING_ITEM, MENU_SAVE_GALLERY_PHOTO, 0, + R.string.camera_gallery_photos_text).setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + postAfterKeep(new Runnable() { + public void run() { + gotoGallery(); + } + }); + return true; + } + }); + gallery.setIcon(android.R.drawable.ic_menu_gallery); + return true; + } + + private int calculatePicturesRemaining() { + try { + if (!ImageManager.hasStorage()) { + mPicturesRemaining = NO_STORAGE_ERROR; + } else { + String storageDirectory = Environment.getExternalStorageDirectory().toString(); + StatFs stat = new StatFs(storageDirectory); + float remaining = ((float)stat.getAvailableBlocks() * (float)stat.getBlockSize()) / 400000F; + mPicturesRemaining = (int)remaining; + } + } catch (Exception ex) { + // if we can't stat the filesystem then we don't know how many + // pictures are remaining. it might be zero but just leave it + // blank since we really don't know. + mPicturesRemaining = CANNOT_STAT_ERROR; + } + return mPicturesRemaining; + } + + private void initializeVideo() { + Log.v(TAG, "initializeVideo"); + releaseMediaRecorder(); + + if (mSurfaceHolder == null) { + Log.v(TAG, "SurfaceHolder is null"); + return; + } + + mMediaRecorder = new MediaRecorder(); + + if (DEBUG_SUPPRESS_AUDIO_RECORDING) { + Log.v(TAG, "DEBUG_SUPPRESS_AUDIO_RECORDING is true."); + } else { + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + } + mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); + mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + Log.v(TAG, "before setOutputFile"); + createVideoPath(); + mMediaRecorder.setOutputFile(mCurrentVideoFilename); + Boolean videoQualityLow = getIntPreference("pref_camera_videoquality_key") == 0; + + // Use the same frame rate for both, since internally + // if the frame rate is too large, it can cause camera to become + // unstable. We need to fix the MediaRecorder to disable the support + // of setting frame rate for now. + mMediaRecorder.setVideoFrameRate(20); + if (videoQualityLow) { + mMediaRecorder.setVideoSize(176,144); + } else { + mMediaRecorder.setVideoSize(352,288); + } + mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263); + if (!DEBUG_SUPPRESS_AUDIO_RECORDING) { + mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + } + Log.v(TAG, "before setPreviewDisplay"); + mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); + try { + mMediaRecorder.prepare(); + } catch (IOException exception) { + Log.e(TAG, "prepare failed for " + mCurrentVideoFilename); + releaseMediaRecorder(); + // TODO: add more exception handling logic here + return; + } + mMediaRecorderRecording = false; + } + + private void releaseMediaRecorder() { + Log.v(TAG, "Releasing media recorder."); + if (mMediaRecorder != null) { + mMediaRecorder.reset(); + mMediaRecorder.release(); + mMediaRecorder = null; + } + } + + private void restartPreview() { + if (DEBUG_DO_NOT_REUSE_MEDIA_RECORDER) { + Log.v(TAG, "DEBUG_DO_NOT_REUSE_MEDIA_RECORDER recreating mMediaRecorder."); + initializeVideo(); + } else { + try { + mMediaRecorder.prepare(); + } catch (IOException exception) { + Log.e(TAG, "prepare failed for " + mCurrentVideoFilename); + releaseMediaRecorder(); + // TODO: add more exception handling logic here + } + } + } + + private int getIntPreference(String key) { + String s = mPreferences.getString(key, "0"); + return Integer.parseInt(s); + } + + private void createVideoPath() { + long dateTaken = System.currentTimeMillis(); + String title = createName(dateTaken); + String displayName = title + ".3gp"; // Used when emailing. + String filename = ImageManager.CAMERA_IMAGE_BUCKET_NAME + "/" + + Long.toString(dateTaken) + ".3gp"; + ContentValues values = new ContentValues(7); + values.put(Video.Media.TITLE, title); + values.put(Video.Media.DISPLAY_NAME, displayName); + values.put(Video.Media.DESCRIPTION, ""); + values.put(Video.Media.DATE_TAKEN, dateTaken); + values.put(Video.Media.MIME_TYPE, "video/3gpp"); + values.put(Video.Media.DATA, filename); + Uri videoTable = Uri.parse("content://media/external/video/media"); + Uri item = mContentResolver.insert(videoTable, values); + mCurrentVideoFilename = filename; + mCurrentVideoUri = item; + } + + private void addBaseMenuItems(Menu menu) { + MenuHelper.addSwitchModeMenuItem(menu, this, false); + { + MenuItem gallery = menu.add(MenuHelper.IMAGE_MODE_ITEM, MENU_GALLERY_PHOTOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + gotoGallery(); + return true; + } + }); + gallery.setIcon(android.R.drawable.ic_menu_gallery); + mGalleryItems.add(gallery); + } + { + MenuItem gallery = menu.add(MenuHelper.VIDEO_MODE_ITEM, MENU_GALLERY_VIDEOS, 0, R.string.camera_gallery_photos_text).setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + gotoGallery(); + return true; + } + }); + gallery.setIcon(android.R.drawable.ic_menu_gallery); + mGalleryItems.add(gallery); + } + + MenuItem item = menu.add(MenuHelper.GENERIC_ITEM, MENU_SETTINGS, 0, R.string.settings).setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(); + intent.setClass(VideoCamera.this, CameraSettings.class); + startActivity(intent); + return true; + } + }); + item.setIcon(android.R.drawable.ic_menu_preferences); + } + + private void startVideoRecording() { + Log.v(TAG, "startVideoRecording"); + if (!mMediaRecorderRecording) { + + // Check mMediaRecorder to see whether it is initialized or not. + if (mMediaRecorder == null) { + initializeVideo(); + } + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + try { + mMediaRecorder.start(); // Recording is now started + } catch (RuntimeException e) { + Log.e(TAG, "Could not start media recorder. ", e); + return; + } + mMediaRecorderRecording = true; + mRecordingStartTime = SystemClock.uptimeMillis(); + mModeIndicatorView.setVisibility(View.GONE); + mRecordingIndicatorView.setVisibility(View.VISIBLE); + mRecordingTimeView.setText(""); + mRecordingTimeView.setVisibility(View.VISIBLE); + mHandler.sendEmptyMessage(UPDATE_RECORD_TIME); + } + } + + private void stopVideoRecordingAndDisplayDialog() { + Log.v(TAG, "stopVideoRecordingAndDisplayDialog"); + if (mMediaRecorderRecording) { + stopVideoRecording(); + acquireAndShowVideoFrame(); + showPostRecordingAlert(); + } + } + + private void showPostRecordingAlert() { + cancelRestartPreviewTimeout(); + mPostPictureAlert.setVisibility(View.VISIBLE); + mHandler.sendEmptyMessageDelayed(RESTART_PREVIEW, POST_PICTURE_ALERT_TIMEOUT); + } + + private void hidePostPictureAlert() { + cancelRestartPreviewTimeout(); + mPostPictureAlert.setVisibility(View.INVISIBLE); + } + + private void cancelRestartPreviewTimeout() { + mHandler.removeMessages(RESTART_PREVIEW); + } + + private boolean isPostRecordingAlertVisible() { + return mPostPictureAlert.getVisibility() == View.VISIBLE; + } + + private void stopVideoRecording() { + Log.v(TAG, "stopVideoRecording"); + if (mMediaRecorderRecording || mMediaRecorder != null) { + if (mMediaRecorderRecording) { + mMediaRecorder.stop(); + } + releaseMediaRecorder(); + mMediaRecorderRecording = false; + mModeIndicatorView.setVisibility(View.VISIBLE); + mRecordingIndicatorView.setVisibility(View.GONE); + mRecordingTimeView.setVisibility(View.GONE); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + private void hideVideoFrameAndStartPreview() { + hidePostPictureAlert(); + hideVideoFrame(); + restartPreview(); + } + + private void acquireAndShowVideoFrame() { + recycleVideoFrameBitmap(); + mVideoFrameBitmap = createVideoThumbnail(mCurrentVideoFilename); + mVideoFrame.setImageBitmap(mVideoFrameBitmap); + mVideoFrame.setVisibility(View.VISIBLE); + } + + private void hideVideoFrame() { + recycleVideoFrameBitmap(); + mVideoFrame.setVisibility(View.GONE); + } + + private void recycleVideoFrameBitmap() { + if (mVideoFrameBitmap != null) { + mVideoFrame.setImageDrawable(null); + mVideoFrameBitmap.recycle(); + mVideoFrameBitmap = null; + } + } + + private Bitmap createVideoThumbnail(String filePath) { + Bitmap bitmap = null; + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY); + retriever.setDataSource(filePath); + bitmap = retriever.captureFrame(); + } finally { + retriever.release(); + } + return bitmap; + } + +} + diff --git a/src/com/android/camera/VideoPreview.java b/src/com/android/camera/VideoPreview.java new file mode 100644 index 0000000..aed1e89 --- /dev/null +++ b/src/com/android/camera/VideoPreview.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 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.camera; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View.MeasureSpec; + +class VideoPreview extends SurfaceView { + private float mAspectRatio; + private int mHorizontalTileSize = 1; + private int mVerticalTileSize = 1; + + /** + * Setting the aspect ratio to this value means to not enforce an aspect ratio. + */ + public static float DONT_CARE = 0.0f; + + public VideoPreview(Context context) { + super(context); + } + + public VideoPreview(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public VideoPreview(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setTileSize(int horizontalTileSize, int verticalTileSize) { + if ((mHorizontalTileSize != horizontalTileSize) + || (mVerticalTileSize != verticalTileSize)) { + mHorizontalTileSize = horizontalTileSize; + mVerticalTileSize = verticalTileSize; + requestLayout(); + invalidate(); + } + } + + public void setAspectRatio(int width, int height) { + setAspectRatio(((float) width) / ((float) height)); + } + + public void setAspectRatio(float aspectRatio) { + if (mAspectRatio != aspectRatio) { + mAspectRatio = aspectRatio; + requestLayout(); + invalidate(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mAspectRatio != DONT_CARE) { + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + + int width = widthSpecSize; + int height = heightSpecSize; + + if (width > 0 && height > 0) { + float defaultRatio = ((float) width) / ((float) height); + if (defaultRatio < mAspectRatio) { + // Need to reduce height + height = (int) (width / mAspectRatio); + } else if (defaultRatio > mAspectRatio) { + width = (int) (height * mAspectRatio); + } + width = roundUpToTile(width, mHorizontalTileSize, widthSpecSize); + height = roundUpToTile(height, mVerticalTileSize, heightSpecSize); + Log.i("VideoPreview", "ar " + mAspectRatio + " setting size: " + width + 'x' + height); + setMeasuredDimension(width, height); + return; + } + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private int roundUpToTile(int dimension, int tileSize, int maxDimension) { + return Math.min(((dimension + tileSize - 1) / tileSize) * tileSize, maxDimension); + } +} diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java index 4b9eb58..9579f19 100644 --- a/src/com/android/camera/ViewImage.java +++ b/src/com/android/camera/ViewImage.java @@ -102,8 +102,8 @@ public class ViewImage extends Activity private Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F); private Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F); private Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F); - - + + static final int sPadding = 20; static final int sHysteresis = sPadding * 2; static final int sBaseScrollDuration = 1000; // ms @@ -127,17 +127,17 @@ public class ViewImage extends Activity Runnable mDismissOnScreenControlsRunnable; ZoomControls mZoomControls; - + public ViewImage() { } - + private void updateNextPrevControls() { boolean showPrev = mCurrentPosition > 0; boolean showNext = mCurrentPosition < mAllImages.getCount() - 1; - + boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE; boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE; - + if (showPrev && !prevIsVisible) { Animation a = mShowPrevImageViewAnimation; a.setDuration(500); @@ -205,7 +205,7 @@ public class ViewImage extends Activity mDismissOnScreenControlsRunnable = new Runnable() { public void run() { mZoomControls.hide(); - + if (mNextImageView.getVisibility() == View.VISIBLE) { Animation a = mHideNextImageViewAnimation; a.setDuration(500); @@ -213,7 +213,7 @@ public class ViewImage extends Activity mNextImageView.setAnimation(a); mNextImageView.setVisibility(View.INVISIBLE); } - + if (mPrevImageView.getVisibility() == View.VISIBLE) { Animation a = mHidePrevImageViewAnimation; a.setDuration(500); @@ -252,14 +252,14 @@ public class ViewImage extends Activity static public class ImageViewTouch extends ImageViewTouchBase { private ViewImage mViewImage; - + private static int TOUCH_STATE_REST = 0; private static int TOUCH_STATE_LEFT_PRESS = 1; - private static int TOUCH_STATE_RIGHT_PRESS = 2; - private static int TOUCH_STATE_PANNING = 3; - + private static int TOUCH_STATE_RIGHT_PRESS = 2; + private static int TOUCH_STATE_PANNING = 3; + private static int TOUCH_AREA_WIDTH = 60; - + private int mTouchState = TOUCH_STATE_REST; public ImageViewTouch(Context context) { @@ -300,7 +300,7 @@ public class ViewImage extends Activity mLastYTouchPos = y; mTouchState = TOUCH_STATE_REST; break; - case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_MOVE: if (x < TOUCH_AREA_WIDTH) { if (mTouchState == TOUCH_STATE_REST) { mTouchState = TOUCH_STATE_LEFT_PRESS; @@ -325,10 +325,10 @@ public class ViewImage extends Activity mTouchState = TOUCH_STATE_PANNING; viewImage.mPrevImageView.setPressed(false); viewImage.mNextImageView.setPressed(false); - + int deltaX; int deltaY; - + if (mLastXTouchPos == -1) { deltaX = 0; deltaY = 0; @@ -342,7 +342,7 @@ public class ViewImage extends Activity if (mBitmapDisplayed == null) return true; - + if (deltaX != 0) { // Second. Pan to whatever degree is possible. if (getScale() > 1F) { @@ -357,7 +357,7 @@ public class ViewImage extends Activity int nextImagePos = -1; if (mTouchState == TOUCH_STATE_LEFT_PRESS && x < TOUCH_AREA_WIDTH) { nextImagePos = viewImage.mCurrentPosition - 1; - } else if (mTouchState == TOUCH_STATE_RIGHT_PRESS && + } else if (mTouchState == TOUCH_STATE_RIGHT_PRESS && x > viewWidth - TOUCH_AREA_WIDTH) { nextImagePos = viewImage.mCurrentPosition + 1; } @@ -546,7 +546,7 @@ public class ViewImage extends Activity private void animateScrollTo(int xNew, int yNew) { mScroller.startScrollTo(xNew, yNew); } - + @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -564,9 +564,8 @@ public class ViewImage extends Activity }); item.setIcon(android.R.drawable.ic_menu_slideshow); } - + mFlipItem = MenuHelper.addFlipOrientation(menu, ViewImage.this, mPrefs); - mFlipItem.setIcon(android.R.drawable.ic_menu_always_landscape_portrait); final SelectedImageGetter selectedImageGetter = new SelectedImageGetter() { public ImageManager.IImage getCurrentImage() { @@ -581,6 +580,7 @@ public class ViewImage extends Activity mImageMenuRunnable = MenuHelper.addImageMenuItems( menu, MenuHelper.INCLUDE_ALL, + true, ViewImage.this, mHandler, mDeletePhotoRunnable, @@ -641,7 +641,7 @@ public class ViewImage extends Activity setImage(mCurrentPosition); } }; - + @Override public boolean onPrepareOptionsMenu(Menu menu) { @@ -663,7 +663,7 @@ public class ViewImage extends Activity private boolean isCurrentImageShareable() { IImage image = mAllImages.getImageAt(mCurrentPosition); if (image != null){ - Uri uri = image.fullSizeImageUri(); + Uri uri = image.fullSizeImageUri(); String fullUri = uri.toString(); return fullUri.startsWith(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString()) || fullUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()); @@ -674,10 +674,12 @@ public class ViewImage extends Activity @Override public void onConfigurationChanged(android.content.res.Configuration newConfig) { super.onConfigurationChanged(newConfig); - for (ImageViewTouchBase iv: mImageViews) { - iv.setImageBitmapResetBase(null, false, true); + if (newConfig.orientation != getResources().getConfiguration().orientation) { + for (ImageViewTouchBase iv: mImageViews) { + iv.setImageBitmapResetBase(null, false, true); + } + MenuHelper.requestOrientation(this, mPrefs); } - MenuHelper.requestOrientation(this, mPrefs); } @Override @@ -839,7 +841,7 @@ public class ViewImage extends Activity if (mLoad != null) { long t1; if (Config.LOGV) t1 = System.currentTimeMillis(); - + Bitmap b = null; try { b = mLoad.get(); @@ -941,7 +943,7 @@ public class ViewImage extends Activity for (ImageViewTouchBase ivtb : mImageViews) ivtb.dump(); } - + if (!mFirst) { if (left) { mImageViews[2].copyFrom(mImageViews[1]); @@ -977,7 +979,7 @@ public class ViewImage extends Activity } else { mScroller.scrollTo(to, 0); } - + ImageGetterCallback cb = new ImageGetterCallback() { public void completed(boolean wasCanceled) { mImageViews[1].setFocusableInTouchMode(true); @@ -1064,7 +1066,7 @@ public class ViewImage extends Activity mSlideShowImageViews[i].setImageBitmapResetBase(null, true, true); mSlideShowImageViews[i].setVisibility(View.INVISIBLE); } - + Uri uri = getIntent().getData(); if (Config.LOGV) @@ -1132,7 +1134,7 @@ public class ViewImage extends Activity for (ImageViewTouchBase ivt: mImageViews) { ivt.clear(); } - + if (false) { Log.v(TAG, "current is " + this.mSlideShowImageCurrent); this.mSlideShowImageViews[0].dump(); @@ -1161,7 +1163,7 @@ public class ViewImage extends Activity Log.v(TAG, "read prefs... animidx: " + mAnimationIndex); Log.v(TAG, "read prefs... interval: " + mSlideShowInterval); } - + if (mUseShuffleOrder) { generateShuffleOrder(); } @@ -1345,16 +1347,16 @@ public class ViewImage extends Activity public void onSaveInstanceState(Bundle b) { super.onSaveInstanceState(b); ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition); - + if (image != null){ - Uri uri = image.fullSizeImageUri(); - String bucket = null; - if(getIntent()!= null && getIntent().getData()!=null) - bucket = getIntent().getData().getQueryParameter("bucketId"); - - if(bucket!=null) - uri = uri.buildUpon().appendQueryParameter("bucketId", bucket).build(); - + Uri uri = image.fullSizeImageUri(); + String bucket = null; + if(getIntent()!= null && getIntent().getData()!=null) + bucket = getIntent().getData().getQueryParameter("bucketId"); + + if(bucket!=null) + uri = uri.buildUpon().appendQueryParameter("bucketId", bucket).build(); + b.putString("uri", uri.toString()); } if (mMode == MODE_SLIDESHOW) @@ -1364,8 +1366,15 @@ public class ViewImage extends Activity @Override public void onResume() { - super.onResume(); - + super.onResume(); + + // normally this will never be zero but if one "backs" into this + // activity after removing the sdcard it could be zero. in that + // case just "finish" since there's nothing useful that can happen. + if (mAllImages.getCount() == 0) { + finish(); + } + ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition); String sortOrder = mPrefs.getString("pref_gallery_sort_key", null); @@ -1376,7 +1385,7 @@ public class ViewImage extends Activity if (sortAscending != mSortAscending) { init(image.fullSizeImageUri()); } - + if (mGetter == null) { makeGetter(); } @@ -1394,20 +1403,13 @@ public class ViewImage extends Activity }); setImage(mCurrentPosition); - // normally this will never be zero but if one "backs" into this - // activity after removing the sdcard it could be zero. in that - // case just "finish" since there's nothing useful that can happen. - if (mAllImages.getCount() == 0) { - finish(); - } else { - MenuHelper.requestOrientation(this, mPrefs); - } + MenuHelper.requestOrientation(this, mPrefs); } @Override public void onPause() { - super.onPause(); + super.onPause(); mGetter.cancelCurrent(); mGetter.stop(); @@ -1415,18 +1417,18 @@ public class ViewImage extends Activity setMode(MODE_NORMAL); mAllImages.deactivate(); - + for (ImageViewTouchBase iv: mImageViews) { iv.recycleBitmaps(); iv.setImageBitmap(null, true); } - + for (ImageViewTouchBase iv: mSlideShowImageViews) { iv.recycleBitmaps(); iv.setImageBitmap(null, true); } } - + @Override public void onStop() { super.onStop(); diff --git a/src/com/android/camera/ViewVideo.java b/src/com/android/camera/ViewVideo.java deleted file mode 100644 index 527f0bb..0000000 --- a/src/com/android/camera/ViewVideo.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2008 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.camera; - -import android.media.MediaPlayer; -import android.app.Activity; -import android.os.Bundle; -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; -import android.os.PowerManager; -import android.util.Log; -import android.view.Menu; -import android.view.View; -import android.view.WindowManager; -import android.widget.MediaController; -import android.view.Window; -import android.widget.VideoView; -import android.util.Config; - -class ViewVideo extends Activity -{ - static final String TAG = "ViewVideo"; - - private ImageManager.IImageList mAllVideos; - private PowerManager.WakeLock mWakeLock; - private ContentResolver mContentResolver; - private VideoView mVideoView; - private ImageManager.IImage mVideo; - private int mCurrentPosition = -1; - private MediaController mMediaController; - - // if the activity gets paused the stash the current position here - int mPausedPlaybackPosition = 0; - - - public ViewVideo() - { - } - - @Override - public void onCreate(Bundle icicle) - { - super.onCreate(icicle); - if (Config.LOGV) - Log.v(TAG, "onCreate"); - //getWindow().setFormat(android.graphics.PixelFormat.TRANSLUCENT); - - PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); - - mContentResolver = getContentResolver(); - - setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); - setContentView(R.layout.viewvideo); - - mMediaController = new MediaController(this); - mVideoView = (VideoView) findViewById(R.id.video); - mVideoView.setMediaController(mMediaController); - mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - public void onCompletion(MediaPlayer mp) { - // TODO what do we really want to do at the end of playback? - finish(); - } - }); - } - - @Override - public void onSaveInstanceState(Bundle b) { - if (Config.LOGV) - Log.v(TAG, "onSaveInstanceState"); - b.putInt("playback_position", mPausedPlaybackPosition); - } - - @Override - public void onRestoreInstanceState(Bundle b) { - if (Config.LOGV) - Log.v(TAG, "onRestoreInstanceState"); - mPausedPlaybackPosition = b.getInt("playback_position", 0); - } - - @Override - public void onPause() { - super.onPause(); - if (Config.LOGV) - Log.v(TAG, "onPause"); - mAllVideos.deactivate(); - - mVideoView.pause(); - mPausedPlaybackPosition = mVideoView.getCurrentPosition(); - mVideoView.setVideoURI(null); - } - - @Override - public void onStop() { - super.onStop(); - if (Config.LOGV) - Log.v(TAG, "onStop"); - } - - @Override - public void onResume() - { - super.onResume(); - if (Config.LOGV) - Log.v(TAG, "onResume"); - - mAllVideos = ImageManager.instance().allImages( - ViewVideo.this, - mContentResolver, - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_VIDEOS, - ImageManager.SORT_DESCENDING); - - // TODO smarter/faster here please - Uri uri = getIntent().getData(); - if (mVideo == null) { - for (int i = 0; i < mAllVideos.getCount(); i++) { - ImageManager.IImage video = mAllVideos.getImageAt(i); - if (video.fullSizeImageUri().equals(uri)) { - mCurrentPosition = i; - mVideo = video; - break; - } - } - } - - if (mCurrentPosition != -1) { - mMediaController.setPrevNextListeners( - new android.view.View.OnClickListener() { - public void onClick(View v) { - if (++mCurrentPosition == mAllVideos.getCount()) - mCurrentPosition = 0; - ImageManager.IImage video = mAllVideos.getImageAt(mCurrentPosition); - mVideo = video; - mVideoView.setVideoURI(video.fullSizeImageUri()); - mVideoView.start(); - } - }, - new android.view.View.OnClickListener() { - public void onClick(View v) { - if (--mCurrentPosition == -1) - mCurrentPosition = mAllVideos.getCount() - 1; - ImageManager.IImage video = mAllVideos.getImageAt(mCurrentPosition); - mVideo = video; - mVideoView.setVideoURI(video.fullSizeImageUri()); - mVideoView.start(); - } - }); - } - if (Config.LOGV) - android.util.Log.v("camera", "seekTo " + mPausedPlaybackPosition); - mVideoView.setVideoURI(uri); - mVideoView.seekTo(mPausedPlaybackPosition); - mVideoView.start(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - super.onCreateOptionsMenu(menu); - MenuHelper.addVideoMenuItems( - menu, - MenuHelper.INCLUDE_ALL & ~MenuHelper.INCLUDE_VIEWPLAY_MENU, - ViewVideo.this, // activity - null, // handler - new SelectedImageGetter() { - public ImageManager.IImage getCurrentImage() { - return mVideo; - } - public Uri getCurrentImageUri() { - return mVideo.fullSizeImageUri(); - } - }, - - // deletion case - new Runnable() { - public void run() { - mAllVideos.removeImage(mVideo); - finish(); - } - }, - - // pre-work - null, - - // post-work - null); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) - { - return super.onPrepareOptionsMenu(menu); - } -} diff --git a/src/com/android/camera/YouTubeUpload.java b/src/com/android/camera/YouTubeUpload.java deleted file mode 100644 index 0963d09..0000000 --- a/src/com/android/camera/YouTubeUpload.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2006 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.camera; - -import android.content.Intent; -import android.net.Uri; -import android.app.Activity; -import android.app.ProgressDialog; -import android.net.http.AndroidHttpClient; -import android.os.Bundle; -import android.widget.TextView; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -import android.util.Log; -import android.util.Xml; - - -public class YouTubeUpload extends Activity -{ - private static final String TAG = "YouTubeUpload"; - - private static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; - private static final boolean mDevServer = false; - - private ArrayList<String> mCategoriesShort = new ArrayList<String>(); - private ArrayList<String> mCategoriesLong = new ArrayList<String>(); - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - TextView tv = new TextView(this); - tv.setText(""); - setContentView(tv); - } - - @Override - public void onResume() { - super.onResume(); - - final ProgressDialog pd = ProgressDialog.show(this, "please wait", ""); - - final ImageManager.IImageList all = ImageManager.instance().allImages( - YouTubeUpload.this, - getContentResolver(), - ImageManager.DataLocation.ALL, - ImageManager.INCLUDE_VIDEOS, - ImageManager.SORT_ASCENDING); - - android.net.Uri uri = getIntent().getData(); - if (uri == null) { - uri = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM); - } - if (uri != null) { - final ImageManager.VideoObject vid = (ImageManager.VideoObject) all.getImageForUri(uri); - if (vid != null) { - new Thread(new Runnable() { - public void run() { - getCategories(); - runOnUiThread(new Runnable() { - public void run() { - pd.cancel(); - MenuHelper.YouTubeUploadInfoDialog infoDialog = new MenuHelper.YouTubeUploadInfoDialog( - YouTubeUpload.this, - mCategoriesShort, - mCategoriesLong, - vid, - new Runnable() { - public void run() { - finish(); - } - }); - infoDialog.show(); - } - }); - } - }).start(); - } - } - } - - protected String getYouTubeBaseUrl() { - if (mDevServer) { - return "http://dev.gdata.youtube.com"; - } else { - return "http://gdata.youtube.com"; - } - } - - public void getCategories() { - String uri = getYouTubeBaseUrl() + "/schemas/2007/categories.cat"; - AndroidHttpClient mClient = AndroidHttpClient.newInstance("Android-Camera/0.1"); - - try { - org.apache.http.HttpResponse r = mClient.execute(new org.apache.http.client.methods.HttpGet(uri)); - processReturnedData(r.getEntity().getContent()); - } catch (Exception ex) { - Log.e(TAG, "got exception getting categories... " + ex.toString()); - } - } - - public void processReturnedData(InputStream s) throws IOException, SAXException, XmlPullParserException { - try { - Xml.parse(s, Xml.findEncodingByName(null), new DefaultHandler() { - @Override - public void startElement(String uri, String localName, String qName, - Attributes attributes) throws SAXException { - if (ATOM_NAMESPACE.equals(uri)) { - if ("category".equals(localName)) { - String catShortName = attributes.getValue("", "term"); - String catLongName = attributes.getValue("", "label"); - mCategoriesLong .add(catLongName); - mCategoriesShort.add(catShortName); - return; - } - } - } - }); - } catch (SAXException e) { - e.printStackTrace(); - } - } -} |