diff options
28 files changed, 638 insertions, 157 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 574d301..ee36428 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5,6 +5,9 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.SET_WALLPAPER" /> + <!-- Needed by the ZoomRingController to set the bit saying we've already shown the + tutorial toast. --> + <uses-permission android:name="android.permission.WRITE_SETTINGS"/> <application android:icon="@drawable/ic_launcher_camera" android:label="@string/camera_label" android:taskAffinity=""> @@ -101,7 +104,6 @@ <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.dir/image" /> </intent-filter> - </activity> <activity android:name="CropImage" @@ -194,6 +196,20 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> + + <receiver android:name="PhotoGadgetProvider" android:label="@string/gadget_title"> + <intent-filter> + <action android:name="android.gadget.action.GADGET_UPDATE" /> + </intent-filter> + <meta-data android:name="android.gadget.provider" android:resource="@xml/gadget_info" /> + </receiver> + + <!-- We configure a gadget by asking to pick a photo, then crop it, and store the config internally --> + <activity android:name="PhotoGadgetConfigure"> + <intent-filter> + <action android:name="android.gadget.action.GADGET_CONFIGURE" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/res/drawable/photo_frame.9.png b/res/drawable/photo_frame.9.png Binary files differnew file mode 100644 index 0000000..b153260 --- /dev/null +++ b/res/drawable/photo_frame.9.png diff --git a/res/layout/photo_frame.xml b/res/layout/photo_frame.xml new file mode 100644 index 0000000..5537b1e --- /dev/null +++ b/res/layout/photo_frame.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/photo" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:scaleType="center" + android:cropToPadding="true" + android:background="@drawable/photo_frame" + /> diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 8062838..09489cd 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 12a4f88..7d309b0 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index b826de9..17dffa7 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index d2c6e3d..6afc9ca 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 15f6136..27183e2 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 329e51b..9f6baf9 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 72e8d2e..33f5ec0 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 1ec9c8a..7359acf 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -29,8 +29,7 @@ <string name="wait">"Vent litt…"</string> <string name="no_storage">"Sett inn et minnekort før du bruker kameraet."</string> <string name="not_enough_space">"Minnekortet er fullt."</string> - <!-- no translation found for preparing_sd (2914969119574812666) --> - <skip /> + <string name="preparing_sd">"Forbereder minnekort…"</string> <string name="wallpaper">"Setter bakgrunnsbilde, vent litt…"</string> <string name="savingImage">"Lagrer bilde…"</string> <string name="runningFaceDetection">"Vent litt…"</string> @@ -50,15 +49,12 @@ <string name="confirm_delete_message">"Bildet vil bli slettet."</string> <string name="confirm_delete_video_message">"Videoen vil bli slettet."</string> <string name="camera_toss">"Slett"</string> - <!-- no translation found for camera_gallery (815753042966032398) --> - <skip /> + <string name="camera_gallery">"Galleri"</string> <string name="camera_share">"Del"</string> <string name="camera_set">"Bruk som"</string> <string name="camera_play">"Spill"</string> - <!-- no translation found for camera_attach (4048659554893435232) --> - <skip /> - <!-- no translation found for camera_cancel (8203341746672085747) --> - <skip /> + <string name="camera_attach">"Legg ved"</string> + <string name="camera_cancel">"Avbryt"</string> <string name="camera_crop">"Beskjær"</string> <string name="camera_tossing">"Sletter…"</string> <string name="no_way_to_share_image">"Bildet kan ikke deles."</string> @@ -146,16 +142,11 @@ <string name="loading_video">"Laster video…"</string> <string name="spaceIsLow_title">"Lite plass"</string> <string name="spaceIsLow_content">"Minnekortet begynner å gå tom for plass. Endre kvalitetsinnstillingen eller slett bilder fra galleriet."</string> - <!-- no translation found for resume_playing_title (8996677350649355013) --> - <skip /> - <!-- no translation found for resume_playing_message (5184414518126703481) --> - <skip /> - <!-- no translation found for resume_playing_resume (3847915469173852416) --> - <skip /> - <!-- no translation found for resume_playing_restart (5471008499835769292) --> - <skip /> - <!-- no translation found for gadget_title (259405922673466798) --> - <skip /> + <string name="resume_playing_title">"Fortsett avspilling"</string> + <string name="resume_playing_message">"Fortsett avspilling fra %s?"</string> + <string name="resume_playing_resume">"Fortsett avspilling"</string> + <string name="resume_playing_restart">"Begynn på nytt"</string> + <string name="gadget_title">"Bilderamme"</string> <!-- no translation found for video_file_name_format (8555507706353616970) --> <skip /> </resources> diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 9f7aa3b..5d3740d 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 882ae3b..9081af4 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index ef3b196..06fbbaa 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 324c9e6..eba681b 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index c3243ca..14ff0eb 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -175,4 +175,6 @@ <skip /> <!-- no translation found for gadget_title (259405922673466798) --> <skip /> + <!-- no translation found for video_file_name_format (8555507706353616970) --> + <skip /> </resources> diff --git a/res/xml/gadget_info.xml b/res/xml/gadget_info.xml new file mode 100644 index 0000000..05fb515 --- /dev/null +++ b/res/xml/gadget_info.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<gadget-provider xmlns:android="http://schemas.android.com/apk/res/android" + android:minWidth="148px" + android:minHeight="148px" + android:updatePeriodMillis="0" + android:initialLayout="@layout/photo_frame" + android:configure="com.android.camera.PhotoGadgetConfigure" + > +</gadget-provider> diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index a6775ae..079528b 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -759,7 +759,19 @@ public class Camera extends Activity implements View.OnClickListener, public void onCreate(Bundle icicle) { super.onCreate(icicle); - mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + // To reduce startup time, we run some service creation code in another thread. + // We make sure the services are loaded at the end of onCreate(). + Thread loadServiceThread = new Thread(new Runnable() { + public void run() { + mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + mOrientationListener = new OrientationListener(Camera.this) { + public void onOrientationChanged(int orientation) { + mLastOrientation = orientation; + } + }; + } + }); + loadServiceThread.start(); mPreferences = PreferenceManager.getDefaultSharedPreferences(this); mContentResolver = getContentResolver(); @@ -816,25 +828,25 @@ public class Camera extends Activity implements View.OnClickListener, afd.getLength()); if (mClickSound != null) { - mClickSound.setAudioStreamType(AudioManager.STREAM_SYSTEM); + mClickSound.setAudioStreamType(AudioManager.STREAM_ALARM); mClickSound.prepare(); } } 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); mPostCaptureAlert = findViewById(R.id.post_picture_panel); + + // Make sure the services are loaded. + try { + loadServiceThread.join(); + } catch (InterruptedException ex) { + } } @Override @@ -1268,10 +1280,7 @@ public class Camera extends Activity implements View.OnClickListener, private void restartPreview() { VideoPreview surfaceView = mSurfaceView; - if (surfaceView == null || - surfaceView.getWidth() == 0 || surfaceView.getHeight() == 0) { - return; - } + // make sure the surfaceview fills the whole screen when previewing surfaceView.setAspectRatio(VideoPreview.DONT_CARE); setViewFinder(mOriginalViewFinderWidth, mOriginalViewFinderHeight, true); @@ -1290,9 +1299,11 @@ public class Camera extends Activity implements View.OnClickListener, Animation a = mShowLastPictureButtonAnimation; a.setDuration(500); mLastPictureButton.setAnimation(a); - } else if (mShouldTransitionThumbnails) { + } + + if (mShouldTransitionThumbnails) { mShouldTransitionThumbnails = false; - mThumbnailTransition.reverseTransition(500); + mThumbnailTransition.startTransition(500); } } @@ -1435,13 +1446,8 @@ public class Camera extends Activity implements View.OnClickListener, private void viewLastImage() { Uri targetUri = mLastPictureUri; if (targetUri != null) { - Uri thisUri = Images.Media.INTERNAL_CONTENT_URI; - if (thisUri != null) { - String bucket = thisUri.getQueryParameter("bucketId"); - if (bucket != null) { - targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", bucket).build(); - } - } + targetUri = targetUri.buildUpon(). + appendQueryParameter("bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build(); Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); diff --git a/src/com/android/camera/CropImage.java b/src/com/android/camera/CropImage.java index 5250b56..cefaf83 100644 --- a/src/com/android/camera/CropImage.java +++ b/src/com/android/camera/CropImage.java @@ -399,6 +399,9 @@ public class CropImage extends Activity { mHandler.postDelayed(new Runnable() { public void run() { + if (isFinishing()) { + return; + } mFaceDetectionDialog = ProgressDialog.show(CropImage.this, null, getResources().getString(R.string.runningFaceDetection), diff --git a/src/com/android/camera/ImageGallery2.java b/src/com/android/camera/ImageGallery2.java index d58f04c..89afd9e 100644 --- a/src/com/android/camera/ImageGallery2.java +++ b/src/com/android/camera/ImageGallery2.java @@ -356,6 +356,20 @@ public class ImageGallery2 extends Activity { if (Config.LOGV) Log.v(TAG, "onActivityResult: " + requestCode + "; resultCode is " + resultCode + "; data is " + data); switch (requestCode) { + case MenuHelper.RESULT_COMMON_MENU_CROP: { + if (resultCode == RESULT_OK) { + // The CropImage activity passes back the Uri of the cropped image as + // the Action rather than the Data. + Uri dataUri = Uri.parse(data.getAction()); + rebake(false,false); + IImage image = mAllImages.getImageForUri(dataUri); + if (image != null ) { + int rowId = image.getRow(); + mGvs.select(rowId, false); + } + } + break; + } case CROP_MSG: { if (Config.LOGV) Log.v(TAG, "onActivityResult " + data); if (resultCode == RESULT_OK) { @@ -627,7 +641,6 @@ public class ImageGallery2 extends Activity { mNoImagesView = findViewById(R.id.no_images); mInclusion = ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS; - ImageManager.DataLocation location = ImageManager.DataLocation.ALL; Intent intent = getIntent(); if (intent != null) { @@ -665,7 +678,6 @@ public class ImageGallery2 extends Activity { if (extras != null && extras.getBoolean("pick-drm")) { Log.d(TAG, "pick-drm is true"); mInclusion = ImageManager.INCLUDE_DRM_IMAGES; - location = ImageManager.DataLocation.INTERNAL; } } if (Config.LOGV) diff --git a/src/com/android/camera/ImageLoader.java b/src/com/android/camera/ImageLoader.java index f3e04d7..e398fba 100644 --- a/src/com/android/camera/ImageLoader.java +++ b/src/com/android/camera/ImageLoader.java @@ -16,34 +16,29 @@ package com.android.camera; +import java.util.ArrayList; + import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Rect; import android.graphics.Matrix; +import android.graphics.Rect; import android.net.Uri; import android.util.Config; import android.util.Log; -import java.lang.ref.SoftReference; -import java.util.ArrayList; -import java.util.HashMap; - class ImageLoader { private static final String TAG = "ImageLoader"; - // queue of work to do in the workder thread + // queue of work to do in the worker thread private ArrayList<WorkItem> mQueue = new ArrayList<WorkItem>(); private ArrayList<WorkItem> mInProgress = new ArrayList<WorkItem>(); - // array of image id's that have bad thumbnails - private ArrayList<Uri> mBadThumbnailList = new ArrayList<Uri>(); - // the worker thread and a done flag so we know when to exit // currently we only exit from finalize private boolean mDone; private ArrayList<Thread> mDecodeThreads = new ArrayList<Thread>(); private android.os.Handler mHandler; - + private int mThreadCount = 1; synchronized void clear(Uri uri) { @@ -59,11 +54,11 @@ class ImageLoader { public interface LoadedCallback { public void run(Bitmap result); } - + public void pushToFront(final ImageManager.IImage image) { synchronized (mQueue) { WorkItem w = new WorkItem(image, 0, null, false); - + int existing = mQueue.indexOf(w); if (existing >= 1) { WorkItem existingWorkItem = mQueue.remove(existing); @@ -72,11 +67,11 @@ class ImageLoader { } } } - + public boolean cancel(final ImageManager.IImage image) { synchronized (mQueue) { WorkItem w = new WorkItem(image, 0, null, false); - + int existing = mQueue.indexOf(w); if (existing >= 0) { mQueue.remove(existing); @@ -85,7 +80,7 @@ class ImageLoader { return false; } } - + public Bitmap getBitmap(final ImageManager.IImage image, final LoadedCallback imageLoadedRunnable, final boolean postAtFront, boolean postBack) { return getBitmap(image, 0, imageLoadedRunnable, postAtFront, postBack); } @@ -126,7 +121,7 @@ class ImageLoader { // Log.v(TAG, "getBitmap breakdown: tot= " + (t4-t1) + "; " + "; " + (t4-t3) + "; " + (t3-t2) + "; " + (t2-t1)); return null; } - + private void dumpQueue(String s) { synchronized (mQueue) { StringBuilder sb = new StringBuilder(s); @@ -174,7 +169,7 @@ class ImageLoader { mHandler = handler; start(); } - + synchronized private void start() { if (Config.LOGV) Log.v(TAG, "ImageLoader.start() <<<<<<<<<<<<<<<<<<<<<<<<<<<<"); @@ -211,12 +206,11 @@ class ImageLoader { try { b = workItem.mImage.miniThumbBitmap(); } catch (Exception ex) { - Log.e(TAG, "couldn't load miniThumbBitmap " + ex.toString()); - // sd card removal?? + if (Config.LOGV) Log.v(TAG, "couldn't load miniThumbBitmap " + ex.toString()); + // sd card removal or sd card full } if (b == null) { if (Config.LOGV) Log.v(TAG, "unable to read thumbnail for " + workItem.mImage.fullSizeImageUri()); - mBadThumbnailList.add(workItem.mImage.fullSizeImageUri()); } synchronized (mQueue) { @@ -248,7 +242,7 @@ class ImageLoader { } } } - + public static Bitmap transform(Matrix scaler, Bitmap source, int targetWidth, int targetHeight, boolean scaleUp) { int deltaX = source.getWidth() - targetWidth; @@ -261,20 +255,20 @@ class ImageLoader { */ Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b2); - + int deltaXHalf = Math.max(0, deltaX/2); int deltaYHalf = Math.max(0, deltaY/2); Rect src = new Rect( - deltaXHalf, - deltaYHalf, - deltaXHalf + Math.min(targetWidth, source.getWidth()), + deltaXHalf, + deltaYHalf, + deltaXHalf + Math.min(targetWidth, source.getWidth()), deltaYHalf + Math.min(targetHeight, source.getHeight())); int dstX = (targetWidth - src.width()) / 2; int dstY = (targetHeight - src.height()) / 2; Rect dst = new Rect( - dstX, - dstY, - targetWidth - dstX, + dstX, + dstY, + targetWidth - dstX, targetHeight - dstY); if (Config.LOGV) Log.v(TAG, "draw " + src.toString() + " ==> " + dst.toString()); @@ -311,23 +305,23 @@ class ImageLoader { } else { b1 = source; } - + int dx1 = Math.max(0, b1.getWidth() - targetWidth); int dy1 = Math.max(0, b1.getHeight() - targetHeight); - + Bitmap b2 = Bitmap.createBitmap( - b1, - dx1/2, - dy1/2, - targetWidth, + b1, + dx1/2, + dy1/2, + targetWidth, targetHeight); - + if (b1 != source) b1.recycle(); return b2; } - + public void stop() { if (Config.LOGV) Log.v(TAG, "ImageLoader.stop " + mDecodeThreads.size() + " threads"); diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java index b21b243..fb02f9e 100755 --- a/src/com/android/camera/ImageManager.java +++ b/src/com/android/camera/ImageManager.java @@ -710,9 +710,12 @@ public class ImageManager { } return null; } catch (Exception ex) { - Log.e(TAG, "miniThumbBitmap got exception " + ex.toString()); - for (StackTraceElement s : ex.getStackTrace()) - Log.e(TAG, "... " + s.toString()); + // Typically IOException because the sd card is full. + if (VERBOSE) { + Log.e(TAG, "miniThumbBitmap got exception " + ex.toString()); + for (StackTraceElement s : ex.getStackTrace()) + Log.e(TAG, "... " + s.toString()); + } return null; } } @@ -873,7 +876,7 @@ public class ImageManager { return thumb; } catch (Exception ex) { - Log.d(TAG, "unable to store thumbnail: " + ex); + if (VERBOSE) Log.d(TAG, "unable to store thumbnail: " + ex); return thumb; } } @@ -1369,7 +1372,9 @@ public class ImageManager { } catch (IOException ex1) { fileLength = -1; } - Log.e(TAG, "couldn't read thumbnail for " + id + "; " + ex.toString() + "; pos is " + pos + "; length is " + fileLength); + if (VERBOSE) { + Log.e(TAG, "couldn't read thumbnail for " + id + "; " + ex.toString() + "; pos is " + pos + "; length is " + fileLength); + } return null; } } @@ -1513,7 +1518,9 @@ public class ImageManager { if (VERBOSE) Log.v(TAG, "saveMiniThumbToFile took " + (t3-t0) + "; " + (t1-t0) + " " + (t2-t1) + " " + (t3-t2)); } } catch (IOException ex) { - Log.e(TAG, "couldn't save mini thumbnail data for " + id + "; " + ex.toString()); + if (VERBOSE) { + Log.e(TAG, "couldn't save mini thumbnail data for " + id + "; " + ex.toString()); + } } } } @@ -2000,10 +2007,7 @@ public class ImageManager { // setting this to zero will force the call to checkCursor to generate fresh thumbs mMiniThumbMagic = 0; - Cursor c = mContainer.getCursor(); - synchronized (c) { - mContainer.checkThumbnail(this, mContainer.getCursor(), this.getRow()); - } + mContainer.checkThumbnail(this, mContainer.getCursor(), this.getRow()); return true; } diff --git a/src/com/android/camera/ImageViewTouchBase.java b/src/com/android/camera/ImageViewTouchBase.java index 7cdf55e..1774e46 100644 --- a/src/com/android/camera/ImageViewTouchBase.java +++ b/src/com/android/camera/ImageViewTouchBase.java @@ -444,7 +444,7 @@ abstract public class ImageViewTouchBase extends ImageView { } static final float sPanRate = 7; - static final float sScaleRate = 1.05F; + static final float sScaleRate = 1.25F; // Sets the maximum zoom, which is a scale relative to the base matrix. It is calculated to show // the image at 400% zoom regardless of screen or image orientation. If in the future we decode diff --git a/src/com/android/camera/MenuHelper.java b/src/com/android/camera/MenuHelper.java index d4358f6..2aedb02 100644 --- a/src/com/android/camera/MenuHelper.java +++ b/src/com/android/camera/MenuHelper.java @@ -84,6 +84,10 @@ public class MenuHelper { public static final int NO_STORAGE_ERROR = -1; public static final int CANNOT_STAT_ERROR = -2; + /** Activity result code used to report crop results. + */ + public static final int RESULT_COMMON_MENU_CROP = 490; + public interface MenuItemsResult { public void gettingReadyToOpen(Menu menu, ImageManager.IImage image); public void aboutToCall(MenuItem item, ImageManager.IImage image); @@ -158,7 +162,7 @@ public class MenuHelper { Intent cropIntent = new Intent(); cropIntent.setClass(activity, CropImage.class); cropIntent.setData(u); - activity.startActivity(cropIntent); + activity.startActivityForResult(cropIntent, RESULT_COMMON_MENU_CROP); } }); return true; diff --git a/src/com/android/camera/PhotoGadgetConfigure.java b/src/com/android/camera/PhotoGadgetConfigure.java new file mode 100644 index 0000000..a94b5a3 --- /dev/null +++ b/src/com/android/camera/PhotoGadgetConfigure.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2009 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.PhotoGadgetProvider.PhotoDatabaseHelper; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; +import android.gadget.GadgetManager; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; +import android.widget.RemoteViews; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class PhotoGadgetConfigure extends Activity { + static final private String TAG = "PhotoGadgetConfigure"; + + static final int REQUEST_GET_PHOTO = 2; + + int gadgetId = -1; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // Someone is requesting that we configure the given gadgetId, which means + // we prompt the user to pick and crop a photo. + + gadgetId = getIntent().getIntExtra(GadgetManager.EXTRA_GADGET_ID, -1); + if (gadgetId == -1) { + setResult(Activity.RESULT_CANCELED); + finish(); + } + + // TODO: get these values from constants somewhere + // TODO: Adjust the PhotoFrame's image size to avoid on the fly scaling + Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); + intent.setType("image/*"); + intent.putExtra("crop", "true"); + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + intent.putExtra("outputX", 192); + intent.putExtra("outputY", 192); + intent.putExtra("noFaceDetection", true); + intent.putExtra("return-data", true); + + startActivityForResult(intent, REQUEST_GET_PHOTO); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK && gadgetId != -1) { + // Store the cropped photo in our database + Bitmap bitmap = (Bitmap) data.getParcelableExtra("data"); + + PhotoDatabaseHelper helper = new PhotoDatabaseHelper(this); + if (helper.setPhoto(gadgetId, bitmap)) { + resultCode = Activity.RESULT_OK; + + // Push newly updated gadget to surface + RemoteViews views = PhotoGadgetProvider.buildUpdate(this, gadgetId, helper); + GadgetManager gadgetManager = GadgetManager.getInstance(this); + gadgetManager.updateGadget(new int[] { gadgetId }, views); + } + helper.close(); + } else { + resultCode = Activity.RESULT_CANCELED; + } + + // Make sure we pass back the original gadgetId + Intent resultValue = new Intent(); + resultValue.putExtra(GadgetManager.EXTRA_GADGET_ID, gadgetId); + setResult(resultCode, resultValue); + finish(); + } + +} diff --git a/src/com/android/camera/PhotoGadgetProvider.java b/src/com/android/camera/PhotoGadgetProvider.java new file mode 100644 index 0000000..b03217d --- /dev/null +++ b/src/com/android/camera/PhotoGadgetProvider.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2009 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.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.gadget.GadgetManager; +import android.gadget.GadgetProvider; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.provider.Settings; +import android.provider.Calendar.Attendees; +import android.provider.Calendar.Calendars; +import android.provider.Calendar.Instances; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Config; +import android.util.Log; +import android.util.Xml; +import android.view.View; +import android.widget.RemoteViews; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +/** + * Simple gadget to show a user-selected picture. + */ +public class PhotoGadgetProvider extends GadgetProvider { + static final String TAG = "PhotoGadgetProvider"; + static final boolean LOGD = Config.LOGD || true; + + @Override + public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) { + // Update each requested gadgetId with its unique photo + PhotoDatabaseHelper helper = new PhotoDatabaseHelper(context); + for (int gadgetId : gadgetIds) { + int[] specificGadget = new int[] { gadgetId }; + RemoteViews views = buildUpdate(context, gadgetId, helper); + if (LOGD) Log.d(TAG, "sending out views="+views+" for id="+gadgetId); + gadgetManager.updateGadget(specificGadget, views); + } + helper.close(); + } + + @Override + public void onDeleted(Context context, int[] gadgetIds) { + // Clean deleted photos out of our database + PhotoDatabaseHelper helper = new PhotoDatabaseHelper(context); + for (int gadgetId : gadgetIds) { + helper.deletePhoto(gadgetId); + } + helper.close(); + } + + /** + * Load photo for given gadget and build {@link RemoteViews} for it. + */ + static RemoteViews buildUpdate(Context context, int gadgetId, PhotoDatabaseHelper helper) { + RemoteViews views = null; + Bitmap bitmap = helper.getPhoto(gadgetId); + if (bitmap != null) { + views = new RemoteViews(context.getPackageName(), R.layout.photo_frame); + views.setImageViewBitmap(R.id.photo, bitmap); + } + return views; + } + + static class PhotoDatabaseHelper extends SQLiteOpenHelper { + private final Context mContext; + + private static final String DATABASE_NAME = "launcher.db"; + + private static final int DATABASE_VERSION = 1; + + static final String TABLE_PHOTOS = "photos"; + static final String FIELD_GADGET_ID = "gadgetId"; + static final String FIELD_PHOTO_BLOB = "photoBlob"; + + PhotoDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mContext = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_PHOTOS + " (" + + FIELD_GADGET_ID + " INTEGER PRIMARY KEY," + + FIELD_PHOTO_BLOB + " BLOB" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + int version = oldVersion; + + if (version != DATABASE_VERSION) { + Log.w(TAG, "Destroying all old data."); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_PHOTOS); + onCreate(db); + } + } + + /** + * Store the given bitmap in this database for the given gadgetId. + */ + public boolean setPhoto(int gadgetId, Bitmap bitmap) { + boolean success = false; + try { + // Try go guesstimate how much space the icon will take when serialized + // to avoid unnecessary allocations/copies during the write. + int size = bitmap.getWidth() * bitmap.getHeight() * 4; + ByteArrayOutputStream out = new ByteArrayOutputStream(size); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + out.close(); + + ContentValues values = new ContentValues(); + values.put(PhotoDatabaseHelper.FIELD_GADGET_ID, gadgetId); + values.put(PhotoDatabaseHelper.FIELD_PHOTO_BLOB, out.toByteArray()); + + SQLiteDatabase db = getWritableDatabase(); + db.insertOrThrow(PhotoDatabaseHelper.TABLE_PHOTOS, null, values); + + success = true; + } catch (SQLiteException e) { + Log.e(TAG, "Could not open database", e); + } catch (IOException e) { + Log.e(TAG, "Could not serialize photo", e); + } + if (LOGD) Log.d(TAG, "setPhoto success="+success); + return success; + } + + static final String[] PHOTOS_PROJECTION = { + FIELD_PHOTO_BLOB, + }; + + static final int INDEX_PHOTO_BLOB = 0; + + /** + * Inflate and return a bitmap for the given gadgetId. + */ + public Bitmap getPhoto(int gadgetId) { + Cursor c = null; + Bitmap bitmap = null; + try { + SQLiteDatabase db = getReadableDatabase(); + String selection = String.format("%s=%d", FIELD_GADGET_ID, gadgetId); + c = db.query(TABLE_PHOTOS, PHOTOS_PROJECTION, selection, null, + null, null, null, null); + + if (c != null && LOGD) Log.d(TAG, "getPhoto query count="+c.getCount()); + + if (c != null && c.moveToFirst()) { + byte[] data = c.getBlob(INDEX_PHOTO_BLOB); + if (data != null) { + bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + } + } + } catch (SQLiteException e) { + Log.e(TAG, "Could not load photo from database", e); + } finally { + if (c != null) { + c.close(); + } + } + return bitmap; + } + + /** + * Remove any bitmap associated with the given gadgetId. + */ + public void deletePhoto(int gadgetId) { + try { + SQLiteDatabase db = getWritableDatabase(); + String whereClause = String.format("%s=%d", FIELD_GADGET_ID, gadgetId); + db.delete(TABLE_PHOTOS, whereClause, null); + } catch (SQLiteException e) { + Log.e(TAG, "Could not delete photo from database", e); + } + } + } + +} + diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index 3474da6..e3b7ebe 100644 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -150,17 +150,26 @@ public class VideoCamera extends Activity implements View.OnClickListener, long delta = now - mRecordingStartTime; long seconds = delta / 1000; long minutes = seconds / 60; + long hours = minutes / 60; + long remainderMinutes = minutes - (hours * 60); long remainderSeconds = seconds - (minutes * 60); String secondsString = Long.toString(remainderSeconds); if (secondsString.length() < 2) { secondsString = "0" + secondsString; } - String minutesString = Long.toString(minutes); + String minutesString = Long.toString(remainderMinutes); if (minutesString.length() < 2) { minutesString = "0" + minutesString; } String text = minutesString + ":" + secondsString; + if (hours > 0) { + String hoursString = Long.toString(hours); + if (hoursString.length() < 2) { + hoursString = "0" + hoursString; + } + text = hoursString + ":" + text; + } mRecordingTimeView.setText(text); // Work around a limitation of the T-Mobile G1: The T-Mobile // hardware blitter can't pixel-accurately scale and clip at the same time, @@ -910,7 +919,7 @@ public class VideoCamera extends Activity implements View.OnClickListener, private void stopVideoRecording() { Log.v(TAG, "stopVideoRecording"); if (mMediaRecorderRecording || mMediaRecorder != null) { - if (mMediaRecorderRecording) { + if (mMediaRecorderRecording && mMediaRecorder != null) { mMediaRecorder.stop(); mCurrentVideoFilename = mCameraVideoFilename; Log.v(TAG, "Setting current video filename: " + mCurrentVideoFilename); diff --git a/src/com/android/camera/ViewImage.java b/src/com/android/camera/ViewImage.java index 293f26b..56150ac 100644 --- a/src/com/android/camera/ViewImage.java +++ b/src/com/android/camera/ViewImage.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 5163 The Android Open Source Project + * 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. @@ -41,16 +41,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.view.View.OnClickListener; -import android.view.ViewGroup.LayoutParams; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.Toast; -import android.widget.ZoomControls; +import android.widget.ZoomRingController; import com.android.camera.ImageManager.IImage; @@ -127,7 +124,6 @@ public class ViewImage extends Activity implements View.OnClickListener private MenuHelper.MenuItemsResult mImageMenuRunnable; private Runnable mDismissOnScreenControlsRunnable; - private ZoomControls mZoomControls; private boolean mCameraReviewMode; public ViewImage() { @@ -170,16 +166,8 @@ public class ViewImage extends Activity implements View.OnClickListener } private void showOnScreenControls() { - if (mZoomControls != null) { - if (mZoomControls.getVisibility() == View.GONE) { - mZoomControls.show(); - if (! mShowActionIcons) { - mZoomControls.requestFocus(); // this shouldn't be necessary - } - } - updateNextPrevControls(); - scheduleDismissOnScreenControls(); - } + updateNextPrevControls(); + scheduleDismissOnScreenControls(); } @Override @@ -201,49 +189,28 @@ public class ViewImage extends Activity implements View.OnClickListener mHandler.postDelayed(mDismissOnScreenControlsRunnable, 1500); } - public View getZoomControls() { - if (mZoomControls == null) { - mZoomControls = new ZoomControls(this); - mZoomControls.setVisibility(View.GONE); - mZoomControls.setZoomSpeed(0); - mDismissOnScreenControlsRunnable = new Runnable() { - public void run() { - mZoomControls.hide(); - if (!mShowActionIcons) { - if (mNextImageView.getVisibility() == View.VISIBLE) { - Animation a = mHideNextImageViewAnimation; - a.setDuration(500); - a.startNow(); - mNextImageView.setAnimation(a); - mNextImageView.setVisibility(View.INVISIBLE); - } + public void setupDismissOnScreenControlRunnable() { + mDismissOnScreenControlsRunnable = new Runnable() { + public void run() { + if (!mShowActionIcons) { + if (mNextImageView.getVisibility() == View.VISIBLE) { + Animation a = mHideNextImageViewAnimation; + a.setDuration(500); + a.startNow(); + mNextImageView.setAnimation(a); + mNextImageView.setVisibility(View.INVISIBLE); + } - if (mPrevImageView.getVisibility() == View.VISIBLE) { - Animation a = mHidePrevImageViewAnimation; - a.setDuration(500); - a.startNow(); - mPrevImageView.setAnimation(a); - mPrevImageView.setVisibility(View.INVISIBLE); - } + if (mPrevImageView.getVisibility() == View.VISIBLE) { + Animation a = mHidePrevImageViewAnimation; + a.setDuration(500); + a.startNow(); + mPrevImageView.setAnimation(a); + mPrevImageView.setVisibility(View.INVISIBLE); } } - }; - mZoomControls.setOnZoomInClickListener(new OnClickListener() { - public void onClick(View v) { - mHandler.removeCallbacks(mDismissOnScreenControlsRunnable); - mImageViews[1].zoomIn(); - scheduleDismissOnScreenControls(); - } - }); - mZoomControls.setOnZoomOutClickListener(new OnClickListener() { - public void onClick(View v) { - mHandler.removeCallbacks(mDismissOnScreenControlsRunnable); - mImageViews[1].zoomOut(); - scheduleDismissOnScreenControls(); - } - }); - } - return mZoomControls; + } + }; } private boolean isPickIntent() { @@ -267,14 +234,79 @@ public class ViewImage extends Activity implements View.OnClickListener private int mTouchState = TOUCH_STATE_REST; + // The event time of the previous touch up. + private long mPreviousUpTime; + // The duration in milliseconds we will wait to see if it is a double tap. + private static final int DOUBLE_TAP_TIMEOUT = 200; + + // The zoom ring is set to visible by a double tap. + private ZoomRingController mZoomRingController; + private ZoomRingController.OnZoomListener mZoomListener = + new ZoomRingController.OnZoomListener() { + public void onCenter(int x, int y) { + } + + public boolean onPan(int deltaX, int deltaY) { + postTranslate(-deltaX, -deltaY, sUseBounce); + ImageViewTouch.this.center(true, true, false); + return true; + } + + public void onVisibilityChanged(boolean visible) { + } + + public void onBeginDrag(float startAngle) { + } + + public void onEndDrag(float endAngle) { + } + + public boolean onDragZoom(int deltaZoomLevel, int centerX, + int centerY, float startAngle, float curAngle) { + float oldScale = getScale(); + + // First move centerX/centerY to the center of the view. + int deltaX = getWidth() / 2 - centerX; + int deltaY = getHeight() / 2 - centerY; + panBy(deltaX, deltaY); + + // Do zoom in/out. + while (deltaZoomLevel > 0) { + zoomIn(); + deltaZoomLevel--; + } + while (deltaZoomLevel < 0) { + zoomOut(); + deltaZoomLevel++; + } + + // Reverse the first centering. + panBy(-deltaX, -deltaY); + + // Return true if the zoom succeeds. + return (oldScale != getScale()); + } + + public void onSimpleZoom(boolean zoomIn) { + if (zoomIn) zoomIn(); + else zoomOut(); + } + }; + public ImageViewTouch(Context context) { super(context); mViewImage = (ViewImage) context; + + mZoomRingController = new ZoomRingController(context, this); + mZoomRingController.setCallback(mZoomListener); } public ImageViewTouch(Context context, AttributeSet attrs) { super(context, attrs); mViewImage = (ViewImage) context; + + mZoomRingController = new ZoomRingController(context, this); + mZoomRingController.setCallback(mZoomListener); } public void setEnableTrackballScroll(boolean enable) { @@ -381,6 +413,15 @@ public class ViewImage extends Activity implements View.OnClickListener viewImage.mPrevImageView.setPressed(false); viewImage.mNextImageView.setPressed(false); mTouchState = TOUCH_STATE_REST; + + long eventTime = m.getEventTime(); + if (eventTime - mPreviousUpTime < DOUBLE_TAP_TIMEOUT) { + mZoomRingController.setVisible(true); + mPreviousUpTime = 0; + } else { + mPreviousUpTime = eventTime; + } + break; case MotionEvent.ACTION_CANCEL: viewImage.mPrevImageView.setPressed(false); @@ -1146,15 +1187,7 @@ public class ViewImage extends Activity implements View.OnClickListener } } - // Get the zoom controls and add them to the bottom of the map - View zoomControls = getZoomControls(); - RelativeLayout root = (RelativeLayout) findViewById(R.id.rootLayout); - RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - p.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); - p.addRule(RelativeLayout.CENTER_HORIZONTAL); - root.addView(zoomControls, p); + setupDismissOnScreenControlRunnable(); mNextImageView = findViewById(R.id.next_image); mPrevImageView = findViewById(R.id.prev_image); @@ -1166,6 +1199,9 @@ public class ViewImage extends Activity implements View.OnClickListener mPrevImageView.setFocusable(true); } setOrientation(); + + // Show a tutorial for the new zoom interaction (the method ensure we only show it once) + ZoomRingController.showZoomTutorialOnce(this); } private void setOrientation() { @@ -1593,4 +1629,18 @@ public class ViewImage extends Activity implements View.OnClickListener setImage(nextImagePos); } } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case MenuHelper.RESULT_COMMON_MENU_CROP: + if (resultCode == RESULT_OK) { + // The CropImage activity passes back the Uri of the cropped image as + // the Action rather than the Data. + Uri dataUri = Uri.parse(data.getAction()); + init(dataUri); + } + break; + } + } } |