OSDN Git Service

Merge "for intent capture and pano, fix the redo/ok/cancel button sizes so touch...
authorSpike Sprague <spikuru@google.com>
Mon, 29 Sep 2014 23:38:10 +0000 (23:38 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Mon, 29 Sep 2014 23:38:10 +0000 (23:38 +0000)
54 files changed:
res/drawable-hdpi/ic_share_disabled.png
res/drawable-hdpi/ic_share_normal.png
res/drawable-mdpi/ic_share_disabled.png
res/drawable-mdpi/ic_share_normal.png
res/drawable-xhdpi/ic_share_disabled.png
res/drawable-xhdpi/ic_share_normal.png
res/drawable-xxhdpi/ic_share_disabled.png
res/drawable-xxhdpi/ic_share_normal.png
res/drawable-xxxhdpi/ic_share_disabled.png
res/drawable-xxxhdpi/ic_share_normal.png
res/drawable/ic_share.xml [moved from res/drawable/ic_menu_share.xml with 63% similarity]
res/layout/filmstrip_bottom_controls.xml
res/raw/beep_once.ogg [deleted file]
res/raw/beep_twice.ogg [deleted file]
res/raw/shutter.ogg [new file with mode: 0644]
res/raw/staged_shot_complete.ogg [new file with mode: 0644]
res/raw/timer_final_second.ogg [new file with mode: 0644]
res/raw/timer_increment.ogg [new file with mode: 0644]
res/values-am/strings.xml
res/values-es/strings.xml
res/values-hi/strings.xml
sounds/shutter.wav [new file with mode: 0644]
sounds/staged_shot_complete.wav [new file with mode: 0644]
sounds/timer_final_second.wav [new file with mode: 0644]
sounds/timer_increment.wav [new file with mode: 0644]
src/com/android/camera/CameraActivity.java
src/com/android/camera/CaptureModule.java
src/com/android/camera/PhotoModule.java
src/com/android/camera/PhotoUI.java
src/com/android/camera/PreviewGestures.java [deleted file]
src/com/android/camera/SoundClips.java
src/com/android/camera/VideoModule.java
src/com/android/camera/app/CameraAppUI.java
src/com/android/camera/data/LocalMediaData.java
src/com/android/camera/data/LocalSessionData.java
src/com/android/camera/one/OneCamera.java
src/com/android/camera/one/OneCameraManager.java
src/com/android/camera/one/Settings3A.java
src/com/android/camera/one/v1/OneCameraManagerImpl.java
src/com/android/camera/one/v2/AutoFocusHelper.java
src/com/android/camera/one/v2/OneCameraImpl.java
src/com/android/camera/one/v2/OneCameraManagerImpl.java
src/com/android/camera/one/v2/OneCameraZslImpl.java
src/com/android/camera/ui/ModeListView.java
src/com/android/camera/ui/OverlayRenderer.java [deleted file]
src/com/android/camera/ui/PieItem.java [deleted file]
src/com/android/camera/ui/PieMenuButton.java [deleted file]
src/com/android/camera/ui/PieRenderer.java [deleted file]
src/com/android/camera/ui/PreviewOverlay.java
src/com/android/camera/ui/RenderOverlay.java [deleted file]
src/com/android/camera/ui/ZoomRenderer.java [deleted file]
src/com/android/camera/util/ApiHelper.java
src/com/android/camera/util/CameraUtil.java
src/com/android/camera/widget/FilmstripView.java

index 2794c4c..587fe1b 100644 (file)
Binary files a/res/drawable-hdpi/ic_share_disabled.png and b/res/drawable-hdpi/ic_share_disabled.png differ
index be97383..5a2efc0 100644 (file)
Binary files a/res/drawable-hdpi/ic_share_normal.png and b/res/drawable-hdpi/ic_share_normal.png differ
index 15dbc53..d410bc0 100644 (file)
Binary files a/res/drawable-mdpi/ic_share_disabled.png and b/res/drawable-mdpi/ic_share_disabled.png differ
index 5c4d841..23d5d0d 100644 (file)
Binary files a/res/drawable-mdpi/ic_share_normal.png and b/res/drawable-mdpi/ic_share_normal.png differ
index dd01a87..63845ee 100644 (file)
Binary files a/res/drawable-xhdpi/ic_share_disabled.png and b/res/drawable-xhdpi/ic_share_disabled.png differ
index b0ddf23..42bce2a 100644 (file)
Binary files a/res/drawable-xhdpi/ic_share_normal.png and b/res/drawable-xhdpi/ic_share_normal.png differ
index 4334ea6..bba9aee 100644 (file)
Binary files a/res/drawable-xxhdpi/ic_share_disabled.png and b/res/drawable-xxhdpi/ic_share_disabled.png differ
index 75e732a..9aa6089 100644 (file)
Binary files a/res/drawable-xxhdpi/ic_share_normal.png and b/res/drawable-xxhdpi/ic_share_normal.png differ
index 704b67f..c7a441a 100644 (file)
Binary files a/res/drawable-xxxhdpi/ic_share_disabled.png and b/res/drawable-xxxhdpi/ic_share_disabled.png differ
index dfb49ec..58a4696 100644 (file)
Binary files a/res/drawable-xxxhdpi/ic_share_normal.png and b/res/drawable-xxxhdpi/ic_share_normal.png differ
similarity index 63%
rename from res/drawable/ic_menu_share.xml
rename to res/drawable/ic_share.xml
index cffd8fc..aafb97c 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2013 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      limitations under the License.
 -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:drawable="@drawable/ic_share_disabled" />
-    <item android:state_enabled="true"
-          android:drawable="@drawable/ic_share_normal" />
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          android:constantSize="true">
+    <item android:state_enabled="false">
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/ic_share_disabled" />
+    </item>
+    <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/ic_share_normal" />
+    </item>
 </selector>
index 4606a42..c5ef660 100644 (file)
@@ -45,7 +45,7 @@
             <ImageButton
                 android:id="@+id/filmstrip_bottom_control_share"
                 style="@style/FilmstripBottomControlButton"
-                android:src="@drawable/ic_menu_share"
+                android:src="@drawable/ic_share"
                 android:visibility="invisible"
                 android:contentDescription="@string/share_button_description" />
 
diff --git a/res/raw/beep_once.ogg b/res/raw/beep_once.ogg
deleted file mode 100644 (file)
index 06e8be8..0000000
Binary files a/res/raw/beep_once.ogg and /dev/null differ
diff --git a/res/raw/beep_twice.ogg b/res/raw/beep_twice.ogg
deleted file mode 100644 (file)
index 94a7c14..0000000
Binary files a/res/raw/beep_twice.ogg and /dev/null differ
diff --git a/res/raw/shutter.ogg b/res/raw/shutter.ogg
new file mode 100644 (file)
index 0000000..503991b
Binary files /dev/null and b/res/raw/shutter.ogg differ
diff --git a/res/raw/staged_shot_complete.ogg b/res/raw/staged_shot_complete.ogg
new file mode 100644 (file)
index 0000000..89882ea
Binary files /dev/null and b/res/raw/staged_shot_complete.ogg differ
diff --git a/res/raw/timer_final_second.ogg b/res/raw/timer_final_second.ogg
new file mode 100644 (file)
index 0000000..79945f2
Binary files /dev/null and b/res/raw/timer_final_second.ogg differ
diff --git a/res/raw/timer_increment.ogg b/res/raw/timer_increment.ogg
new file mode 100644 (file)
index 0000000..e8d9d02
Binary files /dev/null and b/res/raw/timer_increment.ogg differ
index bcad3e5..baf0247 100644 (file)
     <string name="crop_save" msgid="2841974981340098579">"አስቀምጥ"</string>
     <string name="cannot_load_image" msgid="4100136187076585580">"ምስሉን መጫን አልተቻለም!"</string>
     <string name="switch_photo_filmstrip" msgid="1448511001008888767">"የድርድር ፊልም እይታ"</string>
-    <string name="setting_wallpaper" msgid="2397759659347872725">"á\8b¨á\8c\8dá\8bµá\8c\8dá\8b³ á\8b\88á\88¨á\89\80á\89µ በማዘጋጀት ላይ"</string>
+    <string name="setting_wallpaper" msgid="2397759659347872725">"á\88\8dá\8c£á\8d\8d  በማዘጋጀት ላይ"</string>
     <string name="mode_settings" msgid="2021937261522670921">"ቅንብሮች"</string>
     <string name="mode_camera" msgid="279763925715250603">"ካሜራ"</string>
     <string name="mode_video" msgid="8633929034048169139">"ቪዲዮ"</string>
index 7cff645..357d8d1 100644 (file)
     <string name="set_duration" msgid="1638453882581604341">"Define la duración en segundos"</string>
     <string name="count_down_title_text" msgid="7586031110595513050">"Cuenta atrás para hacer una foto"</string>
     <string name="remember_location_title" msgid="3045040613094030429">"¿Recordar ubicaciones de las fotos?"</string>
-    <string name="remember_location_prompt" msgid="5104210757873140169">"Etiqueta tus fotos y vídeos con las ubicaciones donde se han realizado.\n\nOtras aplicaciones pueden acceder a esta información, así como a las imágenes guardadas."</string>
+    <string name="remember_location_prompt" msgid="5104210757873140169">"Etiqueta tus fotos y vídeos con las ubicaciones donde se han realizado.\n\nOtras aplicaciones pueden acceder a esta información y a imágenes guardadas."</string>
     <string name="remember_location_no" msgid="4412802756840226925">"No, gracias"</string>
     <string name="remember_location_yes" msgid="4339424460683531388">"Sí"</string>
     <string name="camera_menu_more_label" msgid="7951917844735828365">"MÁS OPCIONES"</string>
     <string name="setting_front_camera_photo" msgid="4131886734622868637">"Foto de cámara frontal"</string>
     <string name="setting_front_camera_video" msgid="2178799452805359752">"Vídeo de cámara frontal"</string>
     <string name="setting_default_camera" msgid="6954076799301004779">"Cámara predeterminada"</string>
-    <string name="setting_google_help_and_feedback" msgid="2079580537079242775">"Ayuda y opiniones"</string>
+    <string name="setting_google_help_and_feedback" msgid="2079580537079242775">"Ayuda y sugerencias"</string>
     <string name="processing_hdr_plus" msgid="9160093263037540304">"Procesando HDR+…"</string>
     <string name="open_source_licenses" msgid="2169711954264883060">"Licencias de código abierto"</string>
     <string name="pref_category_general" msgid="6737748849700581019">"Ajustes generales"</string>
     <string name="setting_summary_x_megapixels" msgid="6533463462760866830">"%1$s megapíxeles"</string>
     <string name="setting_summary_aspect_ratio_and_megapixels" msgid="5828440902461064821">"(%1$d:%2$d) %3$s MP"</string>
     <string name="cling_text_for_refocus_editor_button" msgid="4785017397116829802">"Toca para volver a enfocar"</string>
-    <string name="pref_category_advanced" msgid="5921085080077574872">"Avanzados"</string>
+    <string name="pref_category_advanced" msgid="5921085080077574872">"Avanzado"</string>
     <string name="pref_camera_exposure_compensation" msgid="4143245817259719147">"Exposición manual"</string>
     <string name="settings_cling_text" msgid="5368054627268181292">"Cambia la resolución y la calidad en Ajustes o prueba las funciones avanzadas."</string>
     <string name="photo_size_selection_title" msgid="2789753590719172645">"Selecciona el tamaño de foto"</string>
index 3c5d826..ec3f566 100644 (file)
     <string name="pref_camera_id_label_back" msgid="1645608049757733858">"सामने का कैमरा"</string>
     <string name="pref_camera_id_label_front" msgid="349308803062874842">"पीछे का कैमरा"</string>
     <string name="dialog_ok" msgid="774141340500181131">"ठीक"</string>
-    <string name="dialog_cancel" msgid="692365061128351656">"रदà¥\8dद à¤\95रें"</string>
+    <string name="dialog_cancel" msgid="692365061128351656">"रहनà¥\87 à¤¦ें"</string>
     <string name="spaceIsLow_content" product="default" msgid="4522771065344332702">"आपके SD कार्ड में स्थान कम है. गुणवत्ता सेटिंग बदलें या कुछ चित्र या अन्य फ़ाइलें हटाएं."</string>
     <string name="video_reach_size_limit" msgid="9196836111505731836">"आकार सीमा तक पहुंच गए."</string>
     <string name="pano_too_fast_prompt" msgid="2503148095578052177">"बहुत तेज़"</string>
     <string name="effect_silly_faces" msgid="7952713419757286453">"मज़ाकिया चेहरे"</string>
     <string name="effect_background" msgid="1358432220077975015">"पृष्ठभूमि"</string>
     <string name="accessibility_shutter_button" msgid="6040483605347230438">"शटर"</string>
-    <string name="accessibility_cancel_button" msgid="5679989494636116448">"रदà¥\8dद à¤\95रें"</string>
+    <string name="accessibility_cancel_button" msgid="5679989494636116448">"रहनà¥\87 à¤¦ें"</string>
     <string name="accessibility_menu_button" msgid="7692103503958544723">"मेनू बटन"</string>
     <string name="accessibility_check_box" msgid="1084094675439953723">"%1$s चेक बॉक्स"</string>
     <string name="accessibility_switch_to_camera" msgid="4518394037216725274">"फ़ोटो पर स्‍विच करें"</string>
     <string name="accessibility_switch_to_photo_sphere" msgid="5803217570370854725">"Photo Sphere पर स्‍विच करें"</string>
     <string name="accessibility_switch_to_gcam" msgid="7562625440767034695">"उच्‍च गुणवत्ता पर स्विच करें"</string>
     <string name="accessibility_switch_to_refocus" msgid="6796169367953860106">"फिर से फ़ोकस पर स्विच करें"</string>
-    <string name="accessibility_review_cancel" msgid="5462850829869569629">"समà¥\80à¤\95à¥\8dषा à¤°à¤¦à¥\8dद करें"</string>
+    <string name="accessibility_review_cancel" msgid="5462850829869569629">"समà¥\80à¤\95à¥\8dषा à¤¨à¤¾ करें"</string>
     <string name="accessibility_review_ok" msgid="3486465319880320270">"समीक्षा पूर्ण"</string>
     <string name="accessibility_review_retake" msgid="2547112860787022130">"समीक्षा रीटेक"</string>
     <string name="accessibility_mode_options" msgid="6376831760155403217">"विकल्प"</string>
     <string name="countdown_timer_duration_3s" msgid="7435393834886072664">"काउंटडाउन टाइमर अवधि 3 सेकंड पर सेट है"</string>
     <string name="countdown_timer_duration_10s" msgid="9085308782250002795">"काउंटडाउन टाइमर अवधि 10 सेकंड पर सेट है"</string>
     <string name="more_options_desc" msgid="4628738800610478353">"अधिक विकल्प"</string>
-    <string name="cancel_button_description" msgid="3801167024006905033">"रदà¥\8dद à¤\95रें"</string>
+    <string name="cancel_button_description" msgid="3801167024006905033">"रहनà¥\87 à¤¦ें"</string>
     <string name="done_button_description" msgid="1334963435441544592">"पूर्ण"</string>
     <string name="retake_button_description" msgid="4234613030674787714">"फिर से लें"</string>
     <string name="share_button_description" msgid="5108508790540832053">"साझा करें"</string>
     <string name="pref_category_general" msgid="6737748849700581019">"सामान्य सेटिंग"</string>
     <string name="pref_category_resolution_quality" msgid="6641462402321962896">"रिज़ॉल्यूशन और गुणवत्ता"</string>
     <string name="pref_category_about" msgid="1966255405679342337">"संक्षिप्त विवरण"</string>
-    <string name="pref_title_build_version" msgid="481489988124832651">"बिलà¥\8dड à¤¸à¤\82सà¥\8dà¤\95रण"</string>
+    <string name="pref_title_build_version" msgid="481489988124832651">"बिलà¥\8dड à¤µà¤°à¥\8dशन"</string>
     <string name="pref_video_quality_entry_low" msgid="737962621299050603">"निम्न"</string>
     <string name="pref_video_quality_entry_high" msgid="1613578418842803393">"उच्च"</string>
     <string name="pref_video_quality_entry_qcif" msgid="1717816794399266216">"QCIF"</string>
     <string name="photo_aspect_ratio_selection_content" msgid="6668224437962196229">"आप इसे बाद में सेटिंग में बदल सकते हैं."</string>
     <string name="share_to" msgid="5144911209144798122">"इनसे साझा करें:"</string>
     <string name="edit_with" msgid="615569327230783971">"इससे संपादित करें:"</string>
-    <string name="startup_dialog_button_next" msgid="1011831256978228993">"à¤\85à¤\97ला"</string>
+    <string name="startup_dialog_button_next" msgid="1011831256978228993">"à¤\86à¤\97à¥\87"</string>
     <string name="confirm_button_text" msgid="7389949384482206814">"ठीक है, समझ लिया"</string>
     <string name="full_sensor_4x3_aspect_ratio" msgid="1270461419743888925">"पूर्ण सेंसर \n(4:3)"</string>
     <string name="cropped_sensor_16x9_aspect_ratio" msgid="4742161537633251795">"काटा गया सेंसर \n(16:9)"</string>
diff --git a/sounds/shutter.wav b/sounds/shutter.wav
new file mode 100644 (file)
index 0000000..dfb26af
Binary files /dev/null and b/sounds/shutter.wav differ
diff --git a/sounds/staged_shot_complete.wav b/sounds/staged_shot_complete.wav
new file mode 100644 (file)
index 0000000..da6f2ef
Binary files /dev/null and b/sounds/staged_shot_complete.wav differ
diff --git a/sounds/timer_final_second.wav b/sounds/timer_final_second.wav
new file mode 100644 (file)
index 0000000..350929a
Binary files /dev/null and b/sounds/timer_final_second.wav differ
diff --git a/sounds/timer_increment.wav b/sounds/timer_increment.wav
new file mode 100644 (file)
index 0000000..af92298
Binary files /dev/null and b/sounds/timer_increment.wav differ
index a368b98..73f1c42 100644 (file)
@@ -194,6 +194,7 @@ public class CameraActivity extends Activity
      */
     private LocalDataAdapter mDataAdapter;
 
+    private OneCameraManager mCameraManager;
     private SettingsManager mSettingsManager;
     private ModeListView mModeListView;
     private boolean mModeListVisible = false;
@@ -1260,7 +1261,7 @@ public class CameraActivity extends Activity
 
     @Override
     public OneCameraManager getCameraManager() {
-        return OneCameraManager.get(this);
+        return mCameraManager;
     }
 
     private void removeData(int dataID) {
@@ -1323,7 +1324,7 @@ public class CameraActivity extends Activity
         super.onCreate(state);
         if (!Glide.isSetup()) {
             Glide.setup(new GlideBuilder(this)
-                .setResizeService(new FifoPriorityThreadPoolExecutor(1)));
+                .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
             Glide.get(this).setMemoryCategory(MemoryCategory.HIGH);
         }
 
@@ -1331,6 +1332,8 @@ public class CameraActivity extends Activity
         mAppContext = getApplicationContext();
         mSoundPlayer = new SoundPlayer(mAppContext);
 
+        mCameraManager = OneCameraManager.get(this);
+
         // TODO: Try to move all the resources allocation to happen as soon as
         // possible so we can call module.init() at the earliest time.
         mModuleManager = new ModuleManagerImpl();
index 07d779a..9b2bb7b 100644 (file)
@@ -30,6 +30,7 @@ import android.hardware.SensorManager;
 import android.location.Location;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.provider.MediaStore;
 import android.view.KeyEvent;
@@ -74,6 +75,8 @@ import com.android.camera2.R;
 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
 
 import java.io.File;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 /**
  * New Capture module that is made to support photo and video capture on top of
@@ -145,6 +148,9 @@ public class CaptureModule extends CameraModule
      */
     private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
 
+    /** Timeout for camera open/close operations. */
+    private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
+
     /** System Properties switch to enable debugging focus UI. */
     private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI();
 
@@ -170,8 +176,10 @@ public class CaptureModule extends CameraModule
     private CaptureModuleUI mUI;
     /** The camera manager used to open cameras. */
     private OneCameraManager mCameraManager;
-    /** The currently opened camera device. */
+    /** The currently opened camera device, or null if the camera is closed. */
     private OneCamera mCamera;
+    /** Held when opening or closing the camera. */
+    private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
     /** The direction the currently opened camera is facing to. */
     private Facing mCameraFacing = Facing.BACK;
     /** Whether HDR is currently enabled. */
@@ -247,6 +255,8 @@ public class CaptureModule extends CameraModule
 
     /** Main thread handler. */
     private Handler mMainHandler;
+    /** Handler thread for camera-related operations. */
+    private Handler mCameraHandler;
 
     /** Current display rotation in degrees. */
     private int mDisplayRotation;
@@ -298,6 +308,9 @@ public class CaptureModule extends CameraModule
         Log.d(TAG, "init");
         mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
         mMainHandler = new Handler(activity.getMainLooper());
+        HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
+        thread.start();
+        mCameraHandler = new Handler(thread.getLooper());
         mCameraManager = mAppController.getCameraManager();
         mLocationManager = mAppController.getLocationManager();
         mDisplayRotation = CameraUtil.getDisplayRotation(mContext);
@@ -395,9 +408,9 @@ public class CaptureModule extends CameraModule
     @Override
     public void onRemainingSecondsChanged(int remainingSeconds) {
         if (remainingSeconds == 1) {
-            mCountdownSoundPlayer.play(R.raw.beep_twice, 0.6f);
+            mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
         } else if (remainingSeconds == 2 || remainingSeconds == 3) {
-            mCountdownSoundPlayer.play(R.raw.beep_once, 0.6f);
+            mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
         }
     }
 
@@ -561,8 +574,8 @@ public class CaptureModule extends CameraModule
             initSurface(mPreviewTexture);
         }
 
-        mCountdownSoundPlayer.loadSound(R.raw.beep_once);
-        mCountdownSoundPlayer.loadSound(R.raw.beep_twice);
+        mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
+        mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
     }
 
     @Override
@@ -571,8 +584,8 @@ public class CaptureModule extends CameraModule
         cancelCountDown();
         resetTextureBufferSize();
         closeCamera();
-        mCountdownSoundPlayer.unloadSound(R.raw.beep_once);
-        mCountdownSoundPlayer.unloadSound(R.raw.beep_twice);
+        mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
+        mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
         // Remove delayed resume trigger, if it hasn't been executed yet.
         mMainHandler.removeCallbacksAndMessages(null);
 
@@ -588,6 +601,7 @@ public class CaptureModule extends CameraModule
     @Override
     public void destroy() {
         mCountdownSoundPlayer.release();
+        mCameraHandler.getLooper().quitSafely();
     }
 
     @Override
@@ -725,6 +739,11 @@ public class CaptureModule extends CameraModule
     // PhotoModule uses FocusOverlayManager which uses API1/portability
     // logic and coordinates.
     private void triggerFocusAtScreenCoord(int x, int y) {
+        if (mCamera == null) {
+            // If we receive this after the camera is closed, do nothing.
+            return;
+        }
+
         mTapToFocusWaitForActiveScan = true;
         // Show UI immediately even though scan has not started yet.
         float minEdge = Math.min(mPreviewArea.width(), mPreviewArea.height());
@@ -1205,16 +1224,37 @@ public class CaptureModule extends CameraModule
     private void openCameraAndStartPreview() {
         // Only enable HDR on the back camera
         boolean useHdr = mHdrEnabled && mCameraFacing == Facing.BACK;
+
+        try {
+            // TODO Given the current design, we cannot guarantee that one of
+            // CaptureReadyCallback.onSetupFailed or onReadyForCapture will
+            // be called (see below), so it's possible that
+            // mCameraOpenCloseLock.release() is never called under extremely
+            // rare cases.  If we leak the lock, this timeout ensures that we at
+            // least crash so we don't deadlock the app.
+            if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
+                throw new RuntimeException("Time out waiting to acquire camera-open lock.");
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
+        }
         mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
                 new OpenCallback() {
                     @Override
                     public void onFailure() {
                         Log.e(TAG, "Could not open camera.");
                         mCamera = null;
+                        mCameraOpenCloseLock.release();
                         mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
                     }
 
                     @Override
+                    public void onCameraClosed() {
+                        mCamera = null;
+                        mCameraOpenCloseLock.release();
+                    }
+
+                    @Override
                     public void onCameraOpened(final OneCamera camera) {
                         Log.d(TAG, "onCameraOpened: " + camera);
                         mCamera = camera;
@@ -1232,32 +1272,67 @@ public class CaptureModule extends CameraModule
                                 new CaptureReadyCallback() {
                                     @Override
                                     public void onSetupFailed() {
+                                        // We must release this lock here, before posting
+                                        // to the main handler since we may be blocked
+                                        // in pause(), getting ready to close the camera.
+                                        mCameraOpenCloseLock.release();
                                         Log.e(TAG, "Could not set up preview.");
-                                        mCamera.close(null);
-                                        mCamera = null;
-                                        // TODO: Show an error message and exit.
+                                        mMainHandler.post(new Runnable() {
+                                           @Override
+                                           public void run() {
+                                               if (mCamera == null) {
+                                                   Log.d(TAG, "Camera closed, aborting.");
+                                                   return;
+                                               }
+                                               mCamera.close(null);
+                                               mCamera = null;
+                                               // TODO: Show an error message and exit.
+                                           }
+                                        });
                                     }
 
                                     @Override
                                     public void onReadyForCapture() {
-                                        Log.d(TAG, "Ready for capture.");
-                                        onPreviewStarted();
-                                        // Enable zooming after preview has
-                                        // started.
-                                        mUI.initializeZoom(mCamera.getMaxZoom());
-                                        mCamera.setFocusStateListener(CaptureModule.this);
-                                        mCamera.setReadyStateChangedListener(CaptureModule.this);
+                                        // We must release this lock here, before posting
+                                        // to the main handler since we may be blocked
+                                        // in pause(), getting ready to close the camera.
+                                        mCameraOpenCloseLock.release();
+                                        mMainHandler.post(new Runnable() {
+                                           @Override
+                                           public void run() {
+                                               Log.d(TAG, "Ready for capture.");
+                                               if (mCamera == null) {
+                                                   Log.d(TAG, "Camera closed, aborting.");
+                                                   return;
+                                               }
+                                               onPreviewStarted();
+                                               // Enable zooming after preview has
+                                               // started.
+                                               mUI.initializeZoom(mCamera.getMaxZoom());
+                                               mCamera.setFocusStateListener(CaptureModule.this);
+                                               mCamera.setReadyStateChangedListener(CaptureModule.this);
+                                           }
+                                        });
                                     }
                                 });
                     }
-                });
+                }, mCameraHandler);
     }
 
     private void closeCamera() {
-        if (mCamera != null) {
-            mCamera.setFocusStateListener(null);
-            mCamera.close(null);
-            mCamera = null;
+        try {
+            mCameraOpenCloseLock.acquire();
+        } catch(InterruptedException e) {
+            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
+        }
+        try {
+            if (mCamera != null) {
+                mCamera.setFocusStateListener(null);
+                mCamera.close(null);
+                mCamera = null;
+            }
+        } finally {
+            mCameraOpenCloseLock.release();
         }
     }
 
index ab6afdb..bed2684 100644 (file)
@@ -29,7 +29,9 @@ import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.location.Location;
+import android.media.AudioManager;
 import android.media.CameraProfile;
+import android.media.SoundPool;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -747,7 +749,9 @@ public class PhotoModule
                                             CameraCapabilities.SceneMode.AUTO));
                         }
                         updateParametersSceneMode();
-                        mCameraDevice.applySettings(mCameraSettings);
+                        if (mCameraDevice != null) {
+                            mCameraDevice.applySettings(mCameraSettings);
+                        }
                         updateSceneMode();
                     }
                 }
@@ -904,7 +908,7 @@ public class PhotoModule
 
     @Override
     public void startFaceDetection() {
-        if (mFaceDetectionStarted) {
+        if (mFaceDetectionStarted || mCameraDevice == null) {
             return;
         }
         if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
@@ -918,7 +922,7 @@ public class PhotoModule
 
     @Override
     public void stopFaceDetection() {
-        if (!mFaceDetectionStarted) {
+        if (!mFaceDetectionStarted || mCameraDevice == null) {
             return;
         }
         if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
@@ -1008,18 +1012,6 @@ public class PhotoModule
         int xOffset = (originalWidth - newWidth)/2;
         int yOffset = (originalHeight - newHeight)/2;
 
-        // For some reason L needs this to work.
-        // This code is only run on the Nexus 5.
-        // TODO: Determine why this is needed.
-        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
-            Log.v(TAG,"xOffset = " + xOffset);
-            Log.v(TAG,"yOffset = " + yOffset);
-            xOffset *= 2;
-            yOffset = 0;
-            Log.v(TAG,"new xOffset = " + xOffset);
-            Log.v(TAG,"new yOffset = " + yOffset);
-        }
-
         if (xOffset < 0 || yOffset < 0) {
             return dataBundle;
         }
@@ -1354,10 +1346,12 @@ public class PhotoModule
             CameraCapabilities.FocusMode focusMode) {
         CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
         SettingsManager settingsManager = mActivity.getSettingsManager();
-        settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
-                            stringifier.stringify(flashMode));
+        if (!CameraCapabilities.FlashMode.NO_FLASH.equals(flashMode)) {
+            settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
+                    stringifier.stringify(flashMode));
+        }
         settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
-                            stringifier.stringify(focusMode));
+                stringifier.stringify(focusMode));
     }
 
     @Override
@@ -1583,9 +1577,9 @@ public class PhotoModule
     @Override
     public void onRemainingSecondsChanged(int remainingSeconds) {
         if (remainingSeconds == 1) {
-            mCountdownSoundPlayer.play(R.raw.beep_twice, 0.6f);
+            mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
         } else if (remainingSeconds == 2 || remainingSeconds == 3) {
-            mCountdownSoundPlayer.play(R.raw.beep_once, 0.6f);
+            mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
         }
     }
 
@@ -1609,8 +1603,8 @@ public class PhotoModule
         }
         Log.v(TAG, "Executing onResumeTasks.");
 
-        mCountdownSoundPlayer.loadSound(R.raw.beep_once);
-        mCountdownSoundPlayer.loadSound(R.raw.beep_twice);
+        mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
+        mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
         if (mFocusManager != null) {
             // If camera is not open when resume is called, focus manager will
             // not be initialized yet, in which case it will start listening to
@@ -1802,6 +1796,9 @@ public class PhotoModule
 
     @Override
     public void autoFocus() {
+        if (mCameraDevice == null) {
+            return;
+        }
         Log.v(TAG,"Starting auto focus");
         mFocusStartTime = System.currentTimeMillis();
         mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
@@ -1811,6 +1808,9 @@ public class PhotoModule
 
     @Override
     public void cancelAutoFocus() {
+        if (mCameraDevice == null) {
+            return;
+        }
         mCameraDevice.cancelAutoFocus();
         setCameraState(IDLE);
         setCameraParameters(UPDATE_PARAM_PREFERENCE);
@@ -1966,7 +1966,7 @@ public class PhotoModule
         // eventually recurse back into startPreview().
         // To avoid calling startPreview() twice, we must acquire
         // mStartPreviewLock.
-        if (mStartPreviewLock) {
+        if (mStartPreviewLock || mCameraDevice == null) {
             // do nothing
             return;
         }
@@ -2151,6 +2151,10 @@ public class PhotoModule
     }
 
     private void updateParametersPictureSize() {
+        if (mCameraDevice == null) {
+            return;
+        }
+
         SettingsManager settingsManager = mActivity.getSettingsManager();
         String pictureSizeKey = isCameraFrontFacing() ? Keys.KEY_PICTURE_SIZE_FRONT
             : Keys.KEY_PICTURE_SIZE_BACK;
@@ -2180,15 +2184,17 @@ public class PhotoModule
                 (double) size.width() / size.height());
         Size original = mCameraSettings.getCurrentPreviewSize();
         if (!optimalSize.equals(original)) {
-            Log.v(TAG, "setting preview size");
+            Log.v(TAG, "setting preview size. optimal: " + optimalSize + "original: " + original);
             mCameraSettings.setPreviewSize(optimalSize);
 
             // Zoom related settings will be changed for different preview
             // sizes, so set and read the parameters to get latest values
             if (mHandler.getLooper() == Looper.myLooper()) {
+                Log.v(TAG, "matched looper, setting up preview");
                 // On UI thread only, not when camera starts up
                 setupPreview();
             } else {
+                Log.v(TAG, "no looper match, directly applying settings");
                 mCameraDevice.applySettings(mCameraSettings);
             }
             mCameraSettings = mCameraDevice.getSettings();
@@ -2278,6 +2284,9 @@ public class PhotoModule
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     private void updateAutoFocusMoveCallback() {
+        if (mCameraDevice == null) {
+            return;
+        }
         if (mCameraSettings.getCurrentFocusMode() ==
                 CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
             mCameraDevice.setAutoFocusMoveCallback(mHandler,
@@ -2321,7 +2330,9 @@ public class PhotoModule
             updateCameraParametersPreference();
         }
 
-        mCameraDevice.applySettings(mCameraSettings);
+        if (mCameraDevice != null) {
+            mCameraDevice.applySettings(mCameraSettings);
+        }
     }
 
     // If the Camera is idle, update the parameters immediately, otherwise
@@ -2465,4 +2476,42 @@ public class PhotoModule
             }
         });
     }
+
+    /**
+     * This class manages the loading/releasing/playing of the sounds needed for
+     * countdown timer.
+     */
+    private class CountdownSoundPlayer {
+        private SoundPool mSoundPool;
+        private int mTimerIncrement;
+        private int mTimerFinalSecond;
+
+        void loadSounds() {
+            // Load the sounds.
+            if (mSoundPool == null) {
+                mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
+                mTimerIncrement = mSoundPool.load(mAppController.getAndroidContext(), R.raw.timer_increment, 1);
+                mTimerFinalSecond = mSoundPool.load(mAppController.getAndroidContext(), R.raw.timer_final_second, 1);
+            }
+        }
+
+        void onRemainingSecondsChanged(int newVal) {
+            if (mSoundPool == null) {
+                Log.e(TAG, "Cannot play sound - they have not been loaded.");
+                return;
+            }
+            if (newVal == 1) {
+                mSoundPool.play(mTimerFinalSecond, 1.0f, 1.0f, 0, 0, 1.0f);
+            } else if (newVal == 2 || newVal == 3) {
+                mSoundPool.play(mTimerIncrement, 1.0f, 1.0f, 0, 0, 1.0f);
+            }
+        }
+
+        void release() {
+            if (mSoundPool != null) {
+                mSoundPool.release();
+                mSoundPool = null;
+            }
+        }
+    }
 }
index c0fd8c2..dac4cb3 100644 (file)
@@ -39,7 +39,9 @@ import com.android.camera.ui.CountDownView;
 import com.android.camera.ui.FaceView;
 import com.android.camera.ui.PreviewOverlay;
 import com.android.camera.ui.PreviewStatusListener;
+import com.android.camera.util.ApiHelper;
 import com.android.camera.util.CameraUtil;
+import com.android.camera.util.GservicesHelper;
 import com.android.camera.widget.AspectRatioDialogLayout;
 import com.android.camera.widget.AspectRatioSelector;
 import com.android.camera.widget.LocationDialogLayout;
@@ -451,8 +453,10 @@ public class PhotoUI implements PreviewStatusListener,
      *         intro dialog on.
      */
     private boolean showAspectRatioDialogOnThisDevice() {
-        // We only want to show that dialog on N4 and N5
-        return "hammerhead".equals(Build.DEVICE) || "mako".equals(Build.DEVICE);
+        // We only want to show that dialog on N4/N5/N6
+        // Don't show if using API2 portability, b/17462976
+        return !GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity) &&
+                (ApiHelper.IS_NEXUS_4 || ApiHelper.IS_NEXUS_5 || ApiHelper.IS_NEXUS_6);
     }
 
     public void initializeZoom(CameraCapabilities capabilities, CameraSettings settings) {
diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java
deleted file mode 100644 (file)
index 8ab4a7f..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.camera;
-
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.View;
-
-import com.android.camera.debug.Log;
-import com.android.camera.ui.PieRenderer;
-import com.android.camera.ui.RenderOverlay;
-import com.android.camera.ui.ZoomRenderer;
-
-/* PreviewGestures disambiguates touch events received on RenderOverlay
- * and dispatch them to the proper recipient (i.e. zoom renderer or pie renderer).
- * Touch events on CameraControls will be handled by framework.
- * */
-public class PreviewGestures
-        implements ScaleGestureDetector.OnScaleGestureListener {
-
-    private static final Log.Tag TAG = new Log.Tag("PreviewGestures");
-
-    private static final int MODE_NONE = 0;
-    private static final int MODE_ZOOM = 2;
-
-    public static final int DIR_UP = 0;
-    public static final int DIR_DOWN = 1;
-    public static final int DIR_LEFT = 2;
-    public static final int DIR_RIGHT = 3;
-
-    private final SingleTapListener mTapListener;
-    private RenderOverlay mOverlay;
-    private final PieRenderer mPie;
-    private final ZoomRenderer mZoom;
-    private MotionEvent mDown;
-    private MotionEvent mCurrent;
-    private final ScaleGestureDetector mScale;
-    private int mMode;
-    private boolean mZoomEnabled;
-    private boolean mEnabled;
-    private boolean mZoomOnly;
-    private final GestureDetector mGestureDetector;
-
-    private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
-        @Override
-        public void onLongPress (MotionEvent e) {
-            // Open pie
-            if (!mZoomOnly && mPie != null && !mPie.showsItems()) {
-                openPie();
-            }
-        }
-
-        @Override
-        public boolean onSingleTapUp (MotionEvent e) {
-            // Tap to focus when pie is not open
-            if (mPie == null || !mPie.showsItems()) {
-                mTapListener.onSingleTapUp(null, (int) e.getX(), (int) e.getY());
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-            if (e1 == null) {
-                // e1 can be null if for some cases.
-                return false;
-            }
-            if (mZoomOnly || mMode == MODE_ZOOM) return false;
-            int deltaX = (int) (e1.getX() - e2.getX());
-            int deltaY = (int) (e1.getY() - e2.getY());
-            if (deltaY > 2 * deltaX && deltaY > -2 * deltaX) {
-                // Open pie on swipe up
-                if (mPie != null && !mPie.showsItems()) {
-                    openPie();
-                    return true;
-                }
-            }
-            return false;
-        }
-    };
-
-    public interface SingleTapListener {
-        public void onSingleTapUp(View v, int x, int y);
-    }
-
-    public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener,
-            ZoomRenderer zoom, PieRenderer pie) {
-        mTapListener = tapListener;
-        mPie = pie;
-        mZoom = zoom;
-        mMode = MODE_NONE;
-        mScale = new ScaleGestureDetector(ctx, this);
-        mEnabled = true;
-        mGestureDetector = new GestureDetector(mGestureListener);
-    }
-
-    public void setRenderOverlay(RenderOverlay overlay) {
-        mOverlay = overlay;
-    }
-
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-    }
-
-    public void setZoomEnabled(boolean enable) {
-        mZoomEnabled = enable;
-    }
-
-    public void setZoomOnly(boolean zoom) {
-        mZoomOnly = zoom;
-    }
-
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    public boolean dispatchTouch(MotionEvent m) {
-        if (!mEnabled) {
-            return false;
-        }
-        mCurrent = m;
-        if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
-            mMode = MODE_NONE;
-            mDown = MotionEvent.obtain(m);
-        }
-
-        // If pie is open, redirects all the touch events to pie.
-        if (mPie != null && mPie.isOpen()) {
-            return sendToPie(m);
-        }
-
-        // If pie is not open, send touch events to gesture detector and scale
-        // listener to recognize the gesture.
-        mGestureDetector.onTouchEvent(m);
-        if (mZoom != null) {
-            mScale.onTouchEvent(m);
-            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
-                mMode = MODE_ZOOM;
-                if (mZoomEnabled) {
-                    // Start showing zoom UI as soon as there is a second finger down
-                    mZoom.onScaleBegin(mScale);
-                }
-            } else if (MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
-                mZoom.onScaleEnd(mScale);
-            }
-        }
-        return true;
-    }
-
-    private MotionEvent makeCancelEvent(MotionEvent m) {
-        MotionEvent c = MotionEvent.obtain(m);
-        c.setAction(MotionEvent.ACTION_CANCEL);
-        return c;
-    }
-
-    private void openPie() {
-        mGestureDetector.onTouchEvent(makeCancelEvent(mDown));
-        mScale.onTouchEvent(makeCancelEvent(mDown));
-        mOverlay.directDispatchTouch(mDown, mPie);
-    }
-
-    private boolean sendToPie(MotionEvent m) {
-        return mOverlay.directDispatchTouch(m, mPie);
-    }
-
-    // OnScaleGestureListener implementation
-    @Override
-    public boolean onScale(ScaleGestureDetector detector) {
-        return mZoom.onScale(detector);
-    }
-
-    @Override
-    public boolean onScaleBegin(ScaleGestureDetector detector) {
-        if (mPie == null || !mPie.isOpen()) {
-            mMode = MODE_ZOOM;
-            mGestureDetector.onTouchEvent(makeCancelEvent(mCurrent));
-            if (!mZoomEnabled) return false;
-            return mZoom.onScaleBegin(detector);
-        }
-        return false;
-    }
-
-    @Override
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        mZoom.onScaleEnd(detector);
-    }
-}
-
index ac06be8..5315a84 100644 (file)
@@ -114,13 +114,15 @@ public class SoundClips {
         private static final int[] SOUND_RES = { // Soundtrack res IDs.
                 R.raw.focus_complete,
                 R.raw.video_record,
+                R.raw.video_record,
+                R.raw.shutter
         };
 
         // ID returned by load() should be non-zero.
         private static final int ID_NOT_LOADED = 0;
 
         // Maps a sound action to the id;
-        private final int[] mSoundRes = {0, 1, 1, 1};
+        private final int[] mSoundRes = {0, 1, 2, 3};
         // Store the context for lazy loading.
         private Context mContext;
         // mSoundPool is created every time load() is called and cleared every
index d4dcdad..f855d95 100644 (file)
@@ -414,7 +414,7 @@ public class VideoModule extends CameraModule
         }
         if (!mIsVideoCaptureIntent) {
             if (!mMediaRecorderRecording || mPaused || mSnapshotInProgress
-                    || !mAppController.isShutterEnabled()) {
+                    || !mAppController.isShutterEnabled() || mCameraDevice == null) {
                 return;
             }
 
@@ -432,7 +432,7 @@ public class VideoModule extends CameraModule
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
      private void updateAutoFocusMoveCallback() {
-        if (mPaused) {
+        if (mPaused || mCameraDevice == null) {
             return;
         }
 
@@ -587,6 +587,10 @@ public class VideoModule extends CameraModule
 
     @Override
     public void onCameraAvailable(CameraProxy cameraProxy) {
+        if (cameraProxy == null) {
+            Log.w(TAG, "onCameraAvailable returns a null CameraProxy object");
+            return;
+        }
         mCameraDevice = cameraProxy;
         mCameraCapabilities = mCameraDevice.getCapabilities();
         mCameraSettings = mCameraDevice.getSettings();
@@ -946,7 +950,7 @@ public class VideoModule extends CameraModule
 
     @Override
     public void stopPreview() {
-        if (!mPreviewing) {
+        if (!mPreviewing || mCameraDevice == null) {
             return;
         }
         mCameraDevice.stopPreview();
@@ -1056,7 +1060,7 @@ public class VideoModule extends CameraModule
 
     // Prepares media recorder.
     private void initializeRecorder() {
-        Log.i(TAG, "initializeRecorder");
+        Log.i(TAG, "initializeRecorder: " + Thread.currentThread());
         // If the mCameraDevice is null, then this activity is going to finish
         if (mCameraDevice == null) {
             return;
@@ -1083,7 +1087,6 @@ public class VideoModule extends CameraModule
             requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
         }
         mMediaRecorder = new MediaRecorder();
-
         // Unlock the camera object before passing it to media recorder.
         mCameraDevice.unlock();
         mMediaRecorder.setCamera(mCameraDevice.getCamera());
@@ -1285,7 +1288,7 @@ public class VideoModule extends CameraModule
     }
 
     private void startVideoRecording() {
-        Log.i(TAG, "startVideoRecording");
+        Log.i(TAG, "startVideoRecording: " + Thread.currentThread());
         mUI.cancelAnimations();
         mUI.setSwipingEnabled(false);
         mUI.showFocusUI(false);
@@ -1297,12 +1300,23 @@ public class VideoModule extends CameraModule
                 if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
                     Log.w(TAG, "Storage issue, ignore the start request");
                 } else {
-                    //??
-                    //if (!mCameraDevice.waitDone()) return;
+                    if (mCameraDevice == null) {
+                        Log.v(TAG, "in storage callback after camera closed");
+                        return;
+                    }
                     if (mPaused == true) {
                         Log.v(TAG, "in storage callback after module paused");
                         return;
                     }
+
+                    // Monkey is so fast so it could trigger startVideoRecording twice. To prevent
+                    // app crash (b/17313985), do nothing here for the second storage-checking
+                    // callback because recording is already started.
+                    if (mMediaRecorderRecording) {
+                        Log.v(TAG, "in storage callback after recording started");
+                        return;
+                    }
+
                     mCurrentVideoUri = null;
 
                     initializeRecorder();
@@ -1387,6 +1401,14 @@ public class VideoModule extends CameraModule
 
     private boolean stopVideoRecording() {
         Log.i(TAG, "stopVideoRecording");
+
+        // Do nothing if camera device is still capturing photo. Monkey test can trigger app crashes
+        // (b/17313985) without this check. Crash could also be reproduced by continuously tapping
+        // on shutter button and preview with two fingers.
+        if (mSnapshotInProgress) {
+            return true;
+        }
+
         mUI.setSwipingEnabled(true);
         mUI.showFocusUI(true);
         mUI.showVideoRecordingHints(true);
@@ -1438,7 +1460,7 @@ public class VideoModule extends CameraModule
 
         mAppController.getCameraAppUI().showModeOptions();
         mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
-        if (!mPaused) {
+        if (!mPaused && mCameraDevice != null) {
             setFocusParameters();
             mCameraDevice.lock();
             if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
@@ -1598,10 +1620,12 @@ public class VideoModule extends CameraModule
                 CameraProfile.QUALITY_HIGH);
         mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
 
-        mCameraDevice.applySettings(mCameraSettings);
-        // Nexus 5 through KitKat 4.4.2 requires a second call to
-        // .setParameters() for frame rate settings to take effect.
-        mCameraDevice.applySettings(mCameraSettings);
+        if (mCameraDevice != null) {
+            mCameraDevice.applySettings(mCameraSettings);
+            // Nexus 5 through KitKat 4.4.2 requires a second call to
+            // .setParameters() for frame rate settings to take effect.
+            mCameraDevice.applySettings(mCameraSettings);
+        }
 
         // Update UI based on the new parameters.
         mUI.updateOnScreenIndicators(mCameraSettings);
@@ -1811,7 +1835,9 @@ public class VideoModule extends CameraModule
                 mParameters.setFlashMode(flashMode);
             }
         }*/
-        mCameraDevice.applySettings(mCameraSettings);
+        if (mCameraDevice != null) {
+            mCameraDevice.applySettings(mCameraSettings);
+        }
         mUI.updateOnScreenIndicators(mCameraSettings);
     }
 
@@ -1914,13 +1940,17 @@ public class VideoModule extends CameraModule
     /***********************FocusOverlayManager Listener****************************/
     @Override
     public void autoFocus() {
-        mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
+        if (mCameraDevice != null) {
+            mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
+        }
     }
 
     @Override
     public void cancelAutoFocus() {
-        mCameraDevice.cancelAutoFocus();
-        setFocusParameters();
+        if (mCameraDevice != null) {
+            mCameraDevice.cancelAutoFocus();
+            setFocusParameters();
+        }
     }
 
     @Override
@@ -1940,8 +1970,9 @@ public class VideoModule extends CameraModule
 
     @Override
     public void setFocusParameters() {
-        updateFocusParameters();
-        mCameraDevice.applySettings(mCameraSettings);
+        if (mCameraDevice != null) {
+            updateFocusParameters();
+            mCameraDevice.applySettings(mCameraSettings);
+        }
     }
-
 }
index 04d6105..d79f013 100644 (file)
@@ -1881,7 +1881,8 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
         buttonManager.setToInitialState();
 
         /** Standard mode options */
-        if (hardwareSpec.isFrontCameraSupported()) {
+        if (mController.getCameraProvider().getNumberOfCameras() > 1 &&
+                hardwareSpec.isFrontCameraSupported()) {
             if (bottomBarSpec.enableCamera) {
                 buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA,
                         bottomBarSpec.cameraCallback);
index 708bf53..3f7e413 100644 (file)
@@ -73,6 +73,10 @@ public abstract class LocalMediaData implements LocalData {
     protected final double mLongitude;
     protected final Bundle mMetaData;
 
+    private static final int JPEG_COMPRESS_QUALITY = 90;
+    private static final BitmapEncoder JPEG_ENCODER =
+            new BitmapEncoder(Bitmap.CompressFormat.JPEG, JPEG_COMPRESS_QUALITY);
+
     /**
      * Used for thumbnail loading optimization. True if this data has a
      * corresponding visible view.
@@ -354,9 +358,6 @@ public abstract class LocalMediaData implements LocalData {
         private static final int mSupportedDataActions =
                 DATA_ACTION_DELETE | DATA_ACTION_EDIT | DATA_ACTION_SHARE;
 
-        private static final int JPEG_COMPRESS_QUALITY = 90;
-        private static final BitmapEncoder JPEG_ENCODER = new BitmapEncoder(null, JPEG_COMPRESS_QUALITY);
-
         /** from MediaStore, can only be 0, 90, 180, 270 */
         private final int mOrientation;
         /** @see #getSignature() */
@@ -535,32 +536,50 @@ public abstract class LocalMediaData implements LocalData {
                 return;
             }
 
-            BitmapRequestBuilder<Uri, Bitmap> request = Glide.with(context)
-                .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, mOrientation)
-                .asBitmap()
-                .encoder(JPEG_ENCODER)
-                .placeholder(placeHolderResourceId)
-                .fitCenter();
+            final int overrideWidth;
+            final int overrideHeight;
+            final BitmapRequestBuilder<Uri, Bitmap> thumbnailRequest;
             if (full) {
-                request.thumbnail(Glide.with(context)
-                        .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds,
-                            mOrientation)
-                        .asBitmap()
-                        .encoder(JPEG_ENCODER)
-                        .override(thumbWidth, thumbHeight)
-                        .fitCenter())
-                    .override(Math.min(getWidth(), MAXIMUM_TEXTURE_SIZE),
-                        Math.min(getHeight(), MAXIMUM_TEXTURE_SIZE));
+                // Load up to the maximum size Bitmap we can render.
+                overrideWidth = Math.min(getWidth(), MAXIMUM_TEXTURE_SIZE);
+                overrideHeight = Math.min(getHeight(), MAXIMUM_TEXTURE_SIZE);
+
+                // Load two thumbnails, first the small low quality thumb from the media store,
+                // then a medium quality thumbWidth/thumbHeight image. Using two thumbnails ensures
+                // we don't flicker to grey while we load the maximum size image.
+                thumbnailRequest = loadUri(context)
+                    .override(thumbWidth, thumbHeight)
+                    .fitCenter()
+                    .thumbnail(loadMediaStoreThumb(context));
             } else {
-                request.thumbnail(Glide.with(context)
-                        .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds,
-                            mOrientation)
-                        .asBitmap()
-                        .encoder(JPEG_ENCODER)
-                        .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT))
-                    .override(thumbWidth, thumbHeight);
+                // Load a medium quality thumbWidth/thumbHeight image.
+                overrideWidth = thumbWidth;
+                overrideHeight = thumbHeight;
+
+                // Load a single small low quality thumbnail from the media store.
+                thumbnailRequest = loadMediaStoreThumb(context);
             }
-            request.into(imageView);
+
+            loadUri(context)
+                .placeholder(placeHolderResourceId)
+                .fitCenter()
+                .override(overrideWidth, overrideHeight)
+                .thumbnail(thumbnailRequest)
+                .into(imageView);
+        }
+
+        /** Loads a thumbnail with a size targeted to use MediaStore.Images.Thumbnails. */
+        private BitmapRequestBuilder<Uri, Bitmap> loadMediaStoreThumb(Context context) {
+            return loadUri(context)
+                .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT);
+        }
+
+        /** Loads an image using a MediaStore Uri with our default options. */
+        private BitmapRequestBuilder<Uri, Bitmap> loadUri(Context context) {
+            return Glide.with(context)
+                .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, mOrientation)
+                .asBitmap()
+                .encoder(JPEG_ENCODER);
         }
 
         @Override
@@ -802,8 +821,12 @@ public abstract class LocalMediaData implements LocalData {
 
             Glide.with(context)
                 .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
+                .asBitmap()
+                .encoder(JPEG_ENCODER)
                 .thumbnail(Glide.with(context)
                     .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
+                    .asBitmap()
+                    .encoder(JPEG_ENCODER)
                     .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT))
                 .placeholder(placeHolderResourceId)
                 .fitCenter()
index 84efd03..8254baa 100644 (file)
@@ -22,9 +22,11 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.ImageView;
+
 import com.android.camera.Storage;
 import com.android.camera2.R;
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DecodeFormat;
 
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
@@ -70,6 +72,7 @@ public class LocalSessionData implements LocalData {
         int currentVersion = Storage.getJpegVersionForSession(mUri);
         Glide.with(context)
             .loadFromImage(jpegData, mUri.toString() + currentVersion)
+            .skipDiskCache(true)
             .fitCenter()
             .into(imageView);
 
index 4e95f7f..593dea4 100644 (file)
@@ -98,6 +98,12 @@ public interface OneCamera {
          * Called if opening the camera failed.
          */
         public void onFailure();
+
+        /**
+         * Called if the camera is closed or disconnected while attempting to
+         * open.
+         */
+        public void onCameraClosed();
     }
 
     /**
@@ -265,11 +271,11 @@ public interface OneCamera {
     /**
      * Meters and triggers auto focus scan with ROI around tap point.
      * <p/>
-     * Normalized coordinates are referenced to portrait preview window with 0,0
-     * top left and 1,1 bottom right. Rotation has no effect.
+     * Normalized coordinates are referenced to portrait preview window with
+     * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
      *
      * @param nx normalized x coordinate.
-     * @param nx normalized y coordinate.
+     * @param ny normalized y coordinate.
      */
     public void triggerFocusAndMeterAtPoint(float nx, float ny);
 
index 13f7dfe..ee505ae 100644 (file)
@@ -20,6 +20,8 @@ import android.content.Context;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
+import android.os.Build;
+import android.os.Handler;
 import android.util.DisplayMetrics;
 import android.view.WindowManager;
 
@@ -41,6 +43,9 @@ public abstract class OneCameraManager {
      * Attempts to open the camera facing the given direction with the given
      * capture size.
      *
+     * Exactly one call will always be made to a single method in the provided
+     * {@link OpenCallback}.
+     *
      * @param facing which camera to open. The first camera found in the given
      *            direction will be opened.
      * @param enableHdr if an HDR feature exists, open a camera that supports it
@@ -48,9 +53,10 @@ public abstract class OneCameraManager {
      *            sizes.
      * @param callback this listener is called when the camera was opened or
      *            when it failed to open.
+     * @param handler the handler on which callback methods are invoked.
      */
     public abstract void open(Facing facing, boolean enableHdr, Size captureSize,
-            OpenCallback callback);
+            OpenCallback callback, Handler handler);
 
     /**
      * Returns whether the device has a camera facing the given direction.
@@ -58,22 +64,11 @@ public abstract class OneCameraManager {
     public abstract boolean hasCameraFacing(Facing facing);
 
     /**
-     * Singleton camera manager to be used throughout the app.
-     */
-    private static OneCameraManager sCameraManager;
-
-    /**
-     * Returns a camera manager that is based on Camera2 API, if available, or
+     * Creates a camera manager that is based on Camera2 API, if available, or
      * otherwise uses the portability layer API.
-     * <p>
-     * The instance is created the first time this method is called and cached
-     * in a singleton thereafter, so successive calls are cheap.
      */
     public static OneCameraManager get(CameraActivity activity) {
-        if (sCameraManager == null) {
-            sCameraManager = create(activity);
-        }
-        return sCameraManager;
+        return create(activity);
     }
 
     /**
@@ -105,6 +100,9 @@ public abstract class OneCameraManager {
      *         HALs.
      */
     private static boolean isCamera2Supported(CameraManager cameraManager) {
+        if (Build.VERSION.SDK_INT < 21) {
+            return false;
+        }
         try {
             final String id = cameraManager.getCameraIdList()[0];
             // TODO: We should check for all the flags we need to ensure the
index 24edd28..a8abf69 100644 (file)
@@ -23,24 +23,82 @@ package com.android.camera.one;
 public class Settings3A {
 
     /**
-     * Width of touch AF region relative to shortest edge at 1.0 zoom.
-     * Was 0.125 * longest edge prior to L release.
+     * Width of touch AF region in [0,1] relative to shorter edge of the current
+     * crop region. Multiply this number by the number of pixels along the
+     * shorter edge of the current crop region's width to get a value in pixels.
+     *
+     * <p>
+     * This value has been tested on Nexus 5 and Shamu, but will need to be
+     * tuned per device depending on how its ISP interprets the metering box and weight.
+     * </p>
+     *
+     * <p>
+     * Values prior to L release:
+     * Normal mode: 0.125 * longest edge
+     * Gcam: Fixed at 300px x 300px.
+     * </p>
      */
     private static final float AF_REGION_BOX = 0.2f;
 
     /**
-     * Width of touch metering region relative to shortest edge at 1.0 zoom.
-     * Larger than {@link #AF_REGION_BOX} because exposure is sensitive and it is
-     * easy to over- or underexposure if area is too small.
+     * Width of touch metering region in [0,1] relative to shorter edge of the
+     * current crop region. Multiply this number by the number of pixels along
+     * shorter edge of the current crop region's width to get a value in pixels.
+     *
+     * <p>
+     * This value has been tested on Nexus 5 and Shamu, but will need to be
+     * tuned per device depending on how its ISP interprets the metering box and weight.
+     * </p>
+     *
+     * <p>
+     * Values prior to L release:
+     * Normal mode: 0.1875 * longest edge
+     * Gcam: Fixed at 300px x 300px.
+     * </p>
      */
     private static final float AE_REGION_BOX = 0.3f;
 
-    /** Metering region weight between 0 and 1. */
-    private static final float REGION_WEIGHT = 0.25f;
+    /** Metering region weight between 0 and 1.
+     *
+     * <p>
+     * This value has been tested on Nexus 5 and Shamu, but will need to be
+     * tuned per device depending on how its ISP interprets the metering box and weight.
+     * </p>
+     */
+    private static final float REGION_WEIGHT = 0.022f;
 
     /** Duration to hold after manual tap to focus. */
     private static final int FOCUS_HOLD_MILLIS = 3000;
 
+    /**
+     * Width of touch metering region in [0,1] relative to shorter edge of the
+     * current crop region. Multiply this number by the number of pixels along
+     * shorter edge of the current crop region's width to get a value in pixels.
+     *
+     * <p>
+     * This value has been tested on Nexus 5 and Shamu, but will need to be
+     * tuned per device depending on how its ISP interprets the metering box and weight.
+     * </p>
+     *
+     * <p>
+     * Was fixed at 300px x 300px prior to L release.
+     * </p>
+     */
+    private static final float GCAM_METERING_REGION_FRACTION = 0.1225f;
+
+    /**
+     * Weight of a touch metering region, in [0, \inf).
+     *
+     * <p>
+     * This value has been tested on Nexus 5 and Shamu, but will need to be
+     * tuned per device.
+     * </p>
+     *
+     * <p>
+     * Was fixed at 15.0f prior to L release.
+     * </p>
+     */
+    private static final float GCAM_METERING_REGION_WEIGHT = 22.0f;
 
     public static float getAutoFocusRegionWidth() {
         return AF_REGION_BOX;
@@ -54,6 +112,14 @@ public class Settings3A {
         return REGION_WEIGHT;
     }
 
+    public static float getGcamMeteringRegionFraction() {
+        return GCAM_METERING_REGION_FRACTION;
+    }
+
+    public static float getGcamMeteringRegionWeight() {
+        return GCAM_METERING_REGION_WEIGHT;
+    }
+
     public static int getFocusHoldMillis() {
         return FOCUS_HOLD_MILLIS;
     }
index 0be46f2..839ca5a 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.camera.one.v1;
 
+import android.os.Handler;
+
 import com.android.camera.one.OneCamera.Facing;
 import com.android.camera.one.OneCamera.OpenCallback;
 import com.android.camera.one.OneCameraManager;
@@ -28,7 +30,7 @@ import com.android.camera.util.Size;
 public class OneCameraManagerImpl extends OneCameraManager {
 
     @Override
-    public void open(Facing facing, boolean enableHdr, Size pictureSize, OpenCallback callback) {
+    public void open(Facing facing, boolean enableHdr, Size pictureSize, OpenCallback callback, Handler handler) {
         throw new RuntimeException("Not implemented yet.");
     }
 
index f0e7f85..8cfab68 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.camera.one.v2;
 
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureResult;
@@ -24,6 +25,7 @@ import android.hardware.camera2.params.MeteringRectangle;
 import com.android.camera.debug.Log;
 import com.android.camera.one.OneCamera;
 import com.android.camera.one.Settings3A;
+import com.android.camera.util.CameraUtil;
 
 /**
  * Helper class to implement auto focus and 3A in camera2-based
@@ -34,8 +36,8 @@ public class AutoFocusHelper {
 
     /** camera2 API metering region weight. */
     private static final int CAMERA2_REGION_WEIGHT = (int)
-            (((1 - Settings3A.getMeteringRegionWeight()) * MeteringRectangle.METERING_WEIGHT_MIN +
-                    Settings3A.getMeteringRegionWeight() * MeteringRectangle.METERING_WEIGHT_MAX));
+        (CameraUtil.lerp(MeteringRectangle.METERING_WEIGHT_MIN, MeteringRectangle.METERING_WEIGHT_MAX,
+                        Settings3A.getMeteringRegionWeight()));
 
     /** Zero weight 3A region, to reset regions per API. */
     private static final MeteringRectangle[] ZERO_WEIGHT_3A_REGION = new MeteringRectangle[]{
@@ -110,35 +112,79 @@ public class AutoFocusHelper {
         ));
     }
 
-    /** Compute 3A regions for a sensor-referenced touch coordinate. */
-    private static MeteringRectangle[] regionsForSensorCoord(int xc, int yc, float width,
-                                                             Rect cropRegion) {
-        float minCropEdge = (float) Math.min(cropRegion.width(), cropRegion.height());
-        int delta = (int) (0.5 * width * minCropEdge);
-        Rect region = new Rect(xc - delta, yc - delta, xc + delta, yc + delta);
-        // Make sure region is inside the sensor area.
-        if (!region.intersect(cropRegion)) {
-            region = cropRegion;
-        }
-        return new MeteringRectangle[]{new MeteringRectangle(region, CAMERA2_REGION_WEIGHT)};
+    /** Compute 3A regions for a sensor-referenced touch coordinate.
+     * Returns a MeteringRectangle[] with length 1.
+     *
+     * @param nx x coordinate of the touch point, in normalized portrait coordinates.
+     * @param ny y coordinate of the touch point, in normalized portrait coordinates.
+     * @param fraction Fraction in [0,1]. Multiplied by min(cropRegion.width(), cropRegion.height())
+     *             to determine the side length of the square MeteringRectangle.
+     * @param cropRegion Crop region of the image.
+     * @param sensorOrientation sensor orientation as defined by
+     *             CameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION).
+     *
+     * */
+    private static MeteringRectangle[] regionsForNormalizedCoord(float nx, float ny, float fraction,
+        final Rect cropRegion, int sensorOrientation) {
+        // Compute half side length in pixels.
+        int minCropEdge = Math.min(cropRegion.width(), cropRegion.height());
+        int halfSideLength = (int) (0.5f * fraction * minCropEdge);
+
+        // Compute the output MeteringRectangle in sensor space.
+        // nx, ny is normalized to the screen.
+        // Crop region itself is specified in sensor coordinates.
+
+        // Normalized coordinates, now rotated into sensor space.
+        PointF nsc = CameraUtil.normalizedSensorCoordsForNormalizedDisplayCoords(
+            nx, ny, sensorOrientation);
+
+        int xCenterSensor = (int)(cropRegion.left + nsc.x * cropRegion.width());
+        int yCenterSensor = (int)(cropRegion.top + nsc.y * cropRegion.height());
+
+        Rect meteringRegion = new Rect(xCenterSensor - halfSideLength,
+            yCenterSensor - halfSideLength,
+            xCenterSensor + halfSideLength,
+            yCenterSensor + halfSideLength);
+
+        // Clamp meteringRegion to cropRegion.
+        meteringRegion.left = CameraUtil.clamp(meteringRegion.left, cropRegion.left, cropRegion.right);
+        meteringRegion.top = CameraUtil.clamp(meteringRegion.top, cropRegion.top, cropRegion.bottom);
+        meteringRegion.right = CameraUtil.clamp(meteringRegion.right, cropRegion.left, cropRegion.right);
+        meteringRegion.bottom = CameraUtil.clamp(meteringRegion.bottom, cropRegion.top, cropRegion.bottom);
+
+        return new MeteringRectangle[]{new MeteringRectangle(meteringRegion, CAMERA2_REGION_WEIGHT)};
     }
 
     /**
      * Return AF region(s) for a sensor-referenced touch coordinate.
      *
+     * <p>
+     * Normalized coordinates are referenced to portrait preview window with
+     * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
+     * </p>
+     *
      * @return AF region(s).
      */
-    public static MeteringRectangle[] afRegionsForSensorCoord(int xc, int yc, Rect cropRegion) {
-        return regionsForSensorCoord(xc, yc, Settings3A.getAutoFocusRegionWidth(), cropRegion);
+    public static MeteringRectangle[] afRegionsForNormalizedCoord(float nx,
+        float ny, final Rect cropRegion, int sensorOrientation) {
+        return regionsForNormalizedCoord(nx, ny, Settings3A.getAutoFocusRegionWidth(),
+            cropRegion, sensorOrientation);
     }
 
     /**
      * Return AE region(s) for a sensor-referenced touch coordinate.
      *
+     * <p>
+     * Normalized coordinates are referenced to portrait preview window with
+     * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
+     * </p>
+     *
      * @return AE region(s).
      */
-    public static MeteringRectangle[] aeRegionsForSensorCoord(int xc, int yc, Rect cropRegion) {
-        return regionsForSensorCoord(xc, yc, Settings3A.getMeteringRegionWidth(), cropRegion);
+    public static MeteringRectangle[] aeRegionsForNormalizedCoord(float nx,
+        float ny, final Rect cropRegion, int sensorOrientation) {
+        return regionsForNormalizedCoord(nx, ny, Settings3A.getMeteringRegionWidth(),
+            cropRegion, sensorOrientation);
     }
 
     /**
index 258eadd..8f7dc44 100644 (file)
@@ -622,12 +622,10 @@ public class OneCameraImpl extends AbstractOneCamera {
 
     @Override
     public void triggerFocusAndMeterAtPoint(float nx, float ny) {
-        // xc, yc is center of tap point in sensor coordinate system.
-        int xc = mCropRegion.left + (int) (mCropRegion.width() * ny);
-        int yc = mCropRegion.top + (int) (mCropRegion.height() * (1f - nx));
-
-        mAERegions = AutoFocusHelper.aeRegionsForSensorCoord(xc, yc, mCropRegion);
-        mAFRegions = AutoFocusHelper.afRegionsForSensorCoord(xc, yc, mCropRegion);
+        int sensorOrientation = mCharacteristics.get(
+            CameraCharacteristics.SENSOR_ORIENTATION);
+        mAERegions = AutoFocusHelper.aeRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
+        mAFRegions = AutoFocusHelper.afRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
 
         sendAutoFocusTriggerCaptureRequest(RequestTag.TAP_TO_FOCUS);
     }
index 0f22477..249e4ec 100644 (file)
@@ -21,6 +21,7 @@ import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
+import android.os.Handler;
 import android.util.DisplayMetrics;
 
 import com.android.camera.SoundPlayer;
@@ -62,44 +63,81 @@ public class OneCameraManagerImpl extends OneCameraManager {
 
     @Override
     public void open(Facing facing, final boolean useHdr, final Size pictureSize,
-            final OpenCallback openCallback) {
+            final OpenCallback openCallback, Handler handler) {
         try {
             final String cameraId = getCameraId(facing);
             Log.i(TAG, "Opening Camera ID " + cameraId);
             mCameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
+                // We may get multiple calls to StateCallback, but only the
+                // first callback indicates the status of the camera-opening
+                // operation.  For example, we may receive onOpened() and later
+                // onClosed(), but only the first should be relayed to
+                // openCallback.
+                private boolean isFirstCallback = true;
 
                 @Override
                 public void onDisconnected(CameraDevice device) {
-                    // TODO, Re-route through the camera instance?
+                    if (isFirstCallback) {
+                        isFirstCallback = false;
+                        // If the camera is disconnected before it is opened
+                        // then we must call close.
+                        device.close();
+                        openCallback.onCameraClosed();
+                    }
+                }
+
+                @Override
+                public void onClosed(CameraDevice device) {
+                    if (isFirstCallback) {
+                        isFirstCallback = false;
+                        openCallback.onCameraClosed();
+                    }
                 }
 
                 @Override
                 public void onError(CameraDevice device, int error) {
-                    openCallback.onFailure();
+                    if (isFirstCallback) {
+                        isFirstCallback = false;
+                        device.close();
+                        openCallback.onFailure();
+                    }
                 }
 
                 @Override
                 public void onOpened(CameraDevice device) {
-                    try {
-                        CameraCharacteristics characteristics = mCameraManager
-                                .getCameraCharacteristics(device.getId());
-                        // TODO: Set boolean based on whether HDR+ is enabled.
-                        OneCamera oneCamera = OneCameraCreator.create(mContext, useHdr, device,
-                                characteristics, pictureSize, mMaxMemoryMB, mDisplayMetrics,
-                                mSoundPlayer);
-                        openCallback.onCameraOpened(oneCamera);
-                    } catch (CameraAccessException e) {
-                        Log.d(TAG, "Could not get camera characteristics");
-                        openCallback.onFailure();
+                    if (isFirstCallback) {
+                        isFirstCallback = false;
+                        try {
+                            CameraCharacteristics characteristics = mCameraManager
+                                    .getCameraCharacteristics(device.getId());
+                            // TODO: Set boolean based on whether HDR+ is enabled.
+                            OneCamera oneCamera = OneCameraCreator.create(mContext, useHdr, device,
+                                    characteristics, pictureSize, mMaxMemoryMB, mDisplayMetrics,
+                                    mSoundPlayer);
+                            openCallback.onCameraOpened(oneCamera);
+                        } catch (CameraAccessException e) {
+                            Log.d(TAG, "Could not get camera characteristics");
+                            openCallback.onFailure();
+                        }
                     }
                 }
-            }, null);
+            }, handler);
         } catch (CameraAccessException ex) {
             Log.e(TAG, "Could not open camera. " + ex.getMessage());
-            openCallback.onFailure();
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    openCallback.onFailure();
+                }
+            });
         } catch (UnsupportedOperationException ex) {
             Log.e(TAG, "Could not open camera. " + ex.getMessage());
-            openCallback.onFailure();
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    openCallback.onFailure();
+                }
+            });
         }
     }
 
index 4d6f632..aa03b88 100644 (file)
@@ -999,12 +999,10 @@ public class OneCameraZslImpl extends AbstractOneCamera {
      */
     @Override
     public void triggerFocusAndMeterAtPoint(float nx, float ny) {
-        // xc, yc is center of tap point in sensor coordinate system.
-        int xc = mCropRegion.left + (int) (mCropRegion.width() * ny);
-        int yc = mCropRegion.top + (int) (mCropRegion.height() * (1f - nx));
-
-        mAERegions = AutoFocusHelper.aeRegionsForSensorCoord(xc, yc, mCropRegion);
-        mAFRegions = AutoFocusHelper.afRegionsForSensorCoord(xc, yc, mCropRegion);
+        int sensorOrientation = mCharacteristics.get(
+            CameraCharacteristics.SENSOR_ORIENTATION);
+        mAERegions = AutoFocusHelper.aeRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
+        mAFRegions = AutoFocusHelper.afRegionsForNormalizedCoord(nx, ny, mCropRegion, sensorOrientation);
 
         startAFCycle();
     }
index 41bd3d6..63901db 100644 (file)
@@ -1895,7 +1895,7 @@ public class ModeListView extends FrameLayout
     private class PeepholeAnimationEffect extends AnimationEffects {
 
         private final static int UNSET = -1;
-        private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 300;
+        private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 500;
 
         private final Paint mMaskPaint = new Paint();
         private final RectF mBackgroundDrawArea = new RectF();
diff --git a/src/com/android/camera/ui/OverlayRenderer.java b/src/com/android/camera/ui/OverlayRenderer.java
deleted file mode 100644 (file)
index 110e10f..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2012 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.ui;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.view.MotionEvent;
-
-import com.android.camera.debug.Log;
-
-public abstract class OverlayRenderer implements RenderOverlay.Renderer {
-
-    private static final Log.Tag TAG = new Log.Tag("OverlayRenderer");
-    protected RenderOverlay mOverlay;
-
-    protected int mLeft, mTop, mRight, mBottom;
-
-    protected boolean mVisible;
-
-    public void setVisible(boolean vis) {
-        mVisible = vis;
-        update();
-    }
-
-    public boolean isVisible() {
-        return mVisible;
-    }
-
-    // default does not handle touch
-    @Override
-    public boolean handlesTouch() {
-        return false;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent evt) {
-        return false;
-    }
-
-    public abstract void onDraw(Canvas canvas);
-
-    public void draw(Canvas canvas) {
-        if (mVisible) {
-            onDraw(canvas);
-        }
-    }
-
-    @Override
-    public void setOverlay(RenderOverlay overlay) {
-        mOverlay = overlay;
-    }
-
-    @Override
-    public void layout(int left, int top, int right, int bottom) {
-        mLeft = left;
-        mRight = right;
-        mTop = top;
-        mBottom = bottom;
-    }
-
-    protected Context getContext() {
-        if (mOverlay != null) {
-            return mOverlay.getContext();
-        } else {
-            return null;
-        }
-    }
-
-    public int getWidth() {
-        return mRight - mLeft;
-    }
-
-    public int getHeight() {
-        return mBottom - mTop;
-    }
-
-    protected void update() {
-        if (mOverlay != null) {
-            mOverlay.update();
-        }
-    }
-
-}
diff --git a/src/com/android/camera/ui/PieItem.java b/src/com/android/camera/ui/PieItem.java
deleted file mode 100644 (file)
index 47fe067..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2012 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.ui;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Pie menu item
- */
-public class PieItem {
-
-    public static interface OnClickListener {
-        void onClick(PieItem item);
-    }
-
-    private Drawable mDrawable;
-    private int level;
-
-    private boolean mSelected;
-    private boolean mEnabled;
-    private List<PieItem> mItems;
-    private Path mPath;
-    private OnClickListener mOnClickListener;
-    private float mAlpha;
-    private CharSequence mLabel;
-
-    // Gray out the view when disabled
-    private static final float ENABLED_ALPHA = 1;
-    private static final float DISABLED_ALPHA = (float) 0.3;
-    private boolean mChangeAlphaWhenDisabled = true;
-
-    public PieItem(Drawable drawable, int level) {
-        mDrawable = drawable;
-        this.level = level;
-        if (drawable != null) {
-            setAlpha(1f);
-        }
-        mEnabled = true;
-    }
-
-    public void setLabel(CharSequence txt) {
-        mLabel = txt;
-    }
-
-    public CharSequence getLabel() {
-        return mLabel;
-    }
-
-    public boolean hasItems() {
-        return mItems != null;
-    }
-
-    public List<PieItem> getItems() {
-        return mItems;
-    }
-
-    public void addItem(PieItem item) {
-        if (mItems == null) {
-            mItems = new ArrayList<PieItem>();
-        }
-        mItems.add(item);
-    }
-
-    public void clearItems() {
-        mItems = null;
-    }
-
-    public void setLevel(int level) {
-        this.level = level;
-    }
-
-    public void setPath(Path p) {
-        mPath = p;
-    }
-
-    public Path getPath() {
-        return mPath;
-    }
-
-    public void setChangeAlphaWhenDisabled (boolean enable) {
-        mChangeAlphaWhenDisabled = enable;
-    }
-
-    public void setAlpha(float alpha) {
-        mAlpha = alpha;
-        mDrawable.setAlpha((int) (255 * alpha));
-    }
-
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-        if (mChangeAlphaWhenDisabled) {
-            if (mEnabled) {
-                setAlpha(ENABLED_ALPHA);
-            } else {
-                setAlpha(DISABLED_ALPHA);
-            }
-        }
-    }
-
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    public void setSelected(boolean s) {
-        mSelected = s;
-    }
-
-    public boolean isSelected() {
-        return mSelected;
-    }
-
-    public int getLevel() {
-        return level;
-    }
-
-
-    public void setOnClickListener(OnClickListener listener) {
-        mOnClickListener = listener;
-    }
-
-    public void performClick() {
-        if (mOnClickListener != null) {
-            mOnClickListener.onClick(this);
-        }
-    }
-
-    public int getIntrinsicWidth() {
-        return mDrawable.getIntrinsicWidth();
-    }
-
-    public int getIntrinsicHeight() {
-        return mDrawable.getIntrinsicHeight();
-    }
-
-    public void setBounds(int left, int top, int right, int bottom) {
-        mDrawable.setBounds(left, top, right, bottom);
-    }
-
-    public void draw(Canvas canvas) {
-        mDrawable.draw(canvas);
-    }
-
-    public void setImageResource(Context context, int resId) {
-        Drawable d = context.getResources().getDrawable(resId).mutate();
-        d.setBounds(mDrawable.getBounds());
-        mDrawable = d;
-        setAlpha(mAlpha);
-    }
-
-}
diff --git a/src/com/android/camera/ui/PieMenuButton.java b/src/com/android/camera/ui/PieMenuButton.java
deleted file mode 100644 (file)
index 0ad93fa..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.camera.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-public class PieMenuButton extends View {
-    private boolean mPressed;
-    private boolean mReadyToClick = false;
-    public PieMenuButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-        mPressed = isPressed();
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean handled = super.onTouchEvent(event);
-        if (MotionEvent.ACTION_UP == event.getAction() && mPressed) {
-            // Perform a customized click as soon as the ACTION_UP event
-            // is received. The reason for doing this is that Framework
-            // delays the performClick() call after ACTION_UP. But we do not
-            // want the delay because it affects an important state change
-            // for PieRenderer.
-            mReadyToClick = true;
-            performClick();
-        }
-        return handled;
-    }
-
-    @Override
-    public boolean performClick() {
-        if (mReadyToClick) {
-            // We only respond to our customized click which happens right
-            // after ACTION_UP event is received, with no delay.
-            mReadyToClick = false;
-            return super.performClick();
-        }
-        return false;
-    }
-};
diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java
deleted file mode 100644 (file)
index 935b5f6..0000000
+++ /dev/null
@@ -1,1093 +0,0 @@
-/*
- * Copyright (C) 2012 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.ui;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.os.Handler;
-import android.os.Message;
-import android.util.FloatMath;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import com.android.camera.debug.Log;
-import com.android.camera.drawable.TextDrawable;
-import com.android.camera2.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An overlay renderer that is used to display focus state and progress state.
- */
-public class PieRenderer extends OverlayRenderer
-        implements FocusIndicator {
-
-    private static final Log.Tag TAG = new Log.Tag("PieRenderer");
-
-    // Sometimes continuous autofocus starts and stops several times quickly.
-    // These states are used to make sure the animation is run for at least some
-    // time.
-    private volatile int mState;
-    private ScaleAnimation mAnimation = new ScaleAnimation();
-    private static final int STATE_IDLE = 0;
-    private static final int STATE_FOCUSING = 1;
-    private static final int STATE_FINISHING = 2;
-    private static final int STATE_PIE = 8;
-
-    private static final float MATH_PI_2 = (float)(Math.PI / 2);
-
-    private Runnable mDisappear = new Disappear();
-    private Animation.AnimationListener mEndAction = new EndAction();
-    private static final int SCALING_UP_TIME = 600;
-    private static final int SCALING_DOWN_TIME = 100;
-    private static final int DISAPPEAR_TIMEOUT = 200;
-    private static final int DIAL_HORIZONTAL = 157;
-    // fade out timings
-    private static final int PIE_FADE_OUT_DURATION = 600;
-
-    private static final long PIE_FADE_IN_DURATION = 200;
-    private static final long PIE_XFADE_DURATION = 200;
-    private static final long PIE_SELECT_FADE_DURATION = 300;
-    private static final long PIE_OPEN_SUB_DELAY = 400;
-    private static final long PIE_SLICE_DURATION = 80;
-
-    private static final int MSG_OPEN = 0;
-    private static final int MSG_CLOSE = 1;
-    private static final int MSG_OPENSUBMENU = 2;
-
-    protected static float CENTER = (float) Math.PI / 2;
-    protected static float RAD24 = (float)(24 * Math.PI / 180);
-    protected static final float SWEEP_SLICE = 0.14f;
-    protected static final float SWEEP_ARC = 0.23f;
-
-    // geometry
-    private int mRadius;
-    private int mRadiusInc;
-
-    // the detection if touch is inside a slice is offset
-    // inbounds by this amount to allow the selection to show before the
-    // finger covers it
-    private int mTouchOffset;
-
-    private List<PieItem> mOpen;
-
-    private Paint mSelectedPaint;
-    private Paint mSubPaint;
-    private Paint mMenuArcPaint;
-
-    // touch handling
-    private PieItem mCurrentItem;
-
-    private Paint mFocusPaint;
-    private int mSuccessColor;
-    private int mFailColor;
-    private int mCircleSize;
-    private int mFocusX;
-    private int mFocusY;
-    private int mCenterX;
-    private int mCenterY;
-    private int mArcCenterY;
-    private int mSliceCenterY;
-    private int mPieCenterX;
-    private int mPieCenterY;
-    private int mSliceRadius;
-    private int mArcRadius;
-    private int mArcOffset;
-
-    private int mDialAngle;
-    private RectF mCircle;
-    private RectF mDial;
-    private Point mPoint1;
-    private Point mPoint2;
-    private int mStartAnimationAngle;
-    private boolean mFocused;
-    private int mInnerOffset;
-    private int mOuterStroke;
-    private int mInnerStroke;
-    private boolean mTapMode;
-    private boolean mBlockFocus;
-    private int mTouchSlopSquared;
-    private Point mDown;
-    private boolean mOpening;
-    private ValueAnimator mXFade;
-    private ValueAnimator mFadeIn;
-    private ValueAnimator mFadeOut;
-    private ValueAnimator mSlice;
-    private volatile boolean mFocusCancelled;
-    private PointF mPolar = new PointF();
-    private TextDrawable mLabel;
-    private int mDeadZone;
-    private int mAngleZone;
-    private float mCenterAngle;
-
-    private Handler mHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-            case MSG_OPEN:
-                if (mListener != null) {
-                    mListener.onPieOpened(mPieCenterX, mPieCenterY);
-                }
-                break;
-            case MSG_CLOSE:
-                if (mListener != null) {
-                    mListener.onPieClosed();
-                }
-                break;
-            case MSG_OPENSUBMENU:
-                onEnterOpen();
-                break;
-            }
-
-        }
-    };
-
-    private PieListener mListener;
-
-    static public interface PieListener {
-        public void onPieOpened(int centerX, int centerY);
-        public void onPieClosed();
-    }
-
-    public void setPieListener(PieListener pl) {
-        mListener = pl;
-    }
-
-    public PieRenderer(Context context) {
-        init(context);
-    }
-
-    private void init(Context ctx) {
-        setVisible(false);
-        mOpen = new ArrayList<PieItem>();
-        mOpen.add(new PieItem(null, 0));
-        Resources res = ctx.getResources();
-        mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
-        mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
-        mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
-        mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
-        mSelectedPaint = new Paint();
-        mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
-        mSelectedPaint.setAntiAlias(true);
-        mSubPaint = new Paint();
-        mSubPaint.setAntiAlias(true);
-        mSubPaint.setColor(Color.argb(200, 250, 230, 128));
-        mFocusPaint = new Paint();
-        mFocusPaint.setAntiAlias(true);
-        mFocusPaint.setColor(Color.WHITE);
-        mFocusPaint.setStyle(Paint.Style.STROKE);
-        mSuccessColor = Color.GREEN;
-        mFailColor = Color.RED;
-        mCircle = new RectF();
-        mDial = new RectF();
-        mPoint1 = new Point();
-        mPoint2 = new Point();
-        mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
-        mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
-        mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
-        mState = STATE_IDLE;
-        mBlockFocus = false;
-        mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
-        mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
-        mDown = new Point();
-        mMenuArcPaint = new Paint();
-        mMenuArcPaint.setAntiAlias(true);
-        mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255));
-        mMenuArcPaint.setStrokeWidth(10);
-        mMenuArcPaint.setStyle(Paint.Style.STROKE);
-        mSliceRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
-        mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius);
-        mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset);
-        mLabel = new TextDrawable(res);
-        mLabel.setDropShadow(true);
-        mDeadZone = res.getDimensionPixelSize(R.dimen.pie_deadzone_width);
-        mAngleZone = res.getDimensionPixelSize(R.dimen.pie_anglezone_width);
-    }
-
-    private PieItem getRoot() {
-        return mOpen.get(0);
-    }
-
-    public boolean showsItems() {
-        return mTapMode;
-    }
-
-    public void addItem(PieItem item) {
-        // add the item to the pie itself
-        getRoot().addItem(item);
-    }
-
-    public void clearItems() {
-        getRoot().clearItems();
-    }
-
-    public void showInCenter() {
-        if ((mState == STATE_PIE) && isVisible()) {
-            mTapMode = false;
-            show(false);
-        } else {
-            if (mState != STATE_IDLE) {
-                cancelFocus();
-            }
-            mState = STATE_PIE;
-            resetPieCenter();
-            setCenter(mPieCenterX, mPieCenterY);
-            mTapMode = true;
-            show(true);
-        }
-    }
-
-    public void hide() {
-        show(false);
-    }
-
-    /**
-     * guaranteed has center set
-     * @param show
-     */
-    private void show(boolean show) {
-        if (show) {
-            if (mXFade != null) {
-                mXFade.cancel();
-            }
-            mState = STATE_PIE;
-            // ensure clean state
-            mCurrentItem = null;
-            PieItem root = getRoot();
-            for (PieItem openItem : mOpen) {
-                if (openItem.hasItems()) {
-                    for (PieItem item : openItem.getItems()) {
-                        item.setSelected(false);
-                    }
-                }
-            }
-            mLabel.setText("");
-            mOpen.clear();
-            mOpen.add(root);
-            layoutPie();
-            fadeIn();
-        } else {
-            mState = STATE_IDLE;
-            mTapMode = false;
-            if (mXFade != null) {
-                mXFade.cancel();
-            }
-            if (mLabel != null) {
-                mLabel.setText("");
-            }
-        }
-        setVisible(show);
-        mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
-    }
-
-    public boolean isOpen() {
-        return mState == STATE_PIE && isVisible();
-    }
-
-    private void fadeIn() {
-        mFadeIn = new ValueAnimator();
-        mFadeIn.setFloatValues(0f, 1f);
-        mFadeIn.setDuration(PIE_FADE_IN_DURATION);
-        // linear interpolation
-        mFadeIn.setInterpolator(null);
-        mFadeIn.addListener(new AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mFadeIn = null;
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-            }
-
-            @Override
-            public void onAnimationCancel(Animator arg0) {
-            }
-        });
-        mFadeIn.start();
-    }
-
-    public void setCenter(int x, int y) {
-        mPieCenterX = x;
-        mPieCenterY = y;
-        mSliceCenterY = y + mSliceRadius - mArcOffset;
-        mArcCenterY = y - mArcOffset + mArcRadius;
-    }
-
-    @Override
-    public void layout(int l, int t, int r, int b) {
-        super.layout(l, t, r, b);
-        mCenterX = (r - l) / 2;
-        mCenterY = (b - t) / 2;
-
-        mFocusX = mCenterX;
-        mFocusY = mCenterY;
-        resetPieCenter();
-        setCircle(mFocusX, mFocusY);
-        if (isVisible() && mState == STATE_PIE) {
-            setCenter(mPieCenterX, mPieCenterY);
-            layoutPie();
-        }
-    }
-
-    private void resetPieCenter() {
-        mPieCenterX = mCenterX;
-        mPieCenterY = (int) (getHeight() - 2.5f * mDeadZone);
-    }
-
-    private void layoutPie() {
-        mCenterAngle = getCenterAngle();
-        layoutItems(0, getRoot().getItems());
-        layoutLabel(getLevel());
-    }
-
-    private void layoutLabel(int level) {
-        int x = mPieCenterX - (int) (FloatMath.sin(mCenterAngle - CENTER)
-                * (mArcRadius + (level + 2) * mRadiusInc));
-        int y = mArcCenterY - mArcRadius - (level + 2) * mRadiusInc;
-        int w = mLabel.getIntrinsicWidth();
-        int h = mLabel.getIntrinsicHeight();
-        mLabel.setBounds(x - w/2, y - h/2, x + w/2, y + h/2);
-    }
-
-    private void layoutItems(int level, List<PieItem> items) {
-        int extend = 1;
-        Path path = makeSlice(getDegrees(0) + extend, getDegrees(SWEEP_ARC) - extend,
-                mArcRadius, mArcRadius + mRadiusInc + mRadiusInc / 4,
-                mPieCenterX, mArcCenterY - level * mRadiusInc);
-        final int count = items.size();
-        int pos = 0;
-        for (PieItem item : items) {
-            // shared between items
-            item.setPath(path);
-            float angle = getArcCenter(item, pos, count);
-            int w = item.getIntrinsicWidth();
-            int h = item.getIntrinsicHeight();
-            // move views to outer border
-            int r = mArcRadius + mRadiusInc * 2 / 3;
-            int x = (int) (r * Math.cos(angle));
-            int y = mArcCenterY - (level * mRadiusInc) - (int) (r * Math.sin(angle)) - h / 2;
-            x = mPieCenterX + x - w / 2;
-            item.setBounds(x, y, x + w, y + h);
-            item.setLevel(level);
-            if (item.hasItems()) {
-                layoutItems(level + 1, item.getItems());
-            }
-            pos++;
-        }
-    }
-
-    private Path makeSlice(float start, float end, int inner, int outer, int cx, int cy) {
-        RectF bb =
-                new RectF(cx - outer, cy - outer, cx + outer,
-                        cy + outer);
-        RectF bbi =
-                new RectF(cx - inner, cy - inner, cx + inner,
-                        cy + inner);
-        Path path = new Path();
-        path.arcTo(bb, start, end - start, true);
-        path.arcTo(bbi, end, start - end);
-        path.close();
-        return path;
-    }
-
-    private float getArcCenter(PieItem item, int pos, int count) {
-        return getCenter(pos, count, SWEEP_ARC);
-    }
-
-    private float getSliceCenter(PieItem item, int pos, int count) {
-        float center = (getCenterAngle() - CENTER) * 0.5f + CENTER;
-        return center + (count - 1) * SWEEP_SLICE / 2f
-                - pos * SWEEP_SLICE;
-    }
-
-    private float getCenter(int pos, int count, float sweep) {
-        return mCenterAngle + (count - 1) * sweep / 2f - pos * sweep;
-    }
-
-    private float getCenterAngle() {
-        float center = CENTER;
-        if (mPieCenterX < mDeadZone + mAngleZone) {
-            center = CENTER - (mAngleZone - mPieCenterX + mDeadZone) * RAD24
-                    / (float) mAngleZone;
-        } else if (mPieCenterX > getWidth() - mDeadZone - mAngleZone) {
-            center = CENTER + (mPieCenterX - (getWidth() - mDeadZone - mAngleZone)) * RAD24
-                    / (float) mAngleZone;
-        }
-        return center;
-    }
-
-    /**
-     * converts a
-     * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
-     * @return skia angle
-     */
-    private float getDegrees(double angle) {
-        return (float) (360 - 180 * angle / Math.PI);
-    }
-
-    private void startFadeOut(final PieItem item) {
-        if (mFadeIn != null) {
-            mFadeIn.cancel();
-        }
-        if (mXFade != null) {
-            mXFade.cancel();
-        }
-        mFadeOut = new ValueAnimator();
-        mFadeOut.setFloatValues(1f, 0f);
-        mFadeOut.setDuration(PIE_FADE_OUT_DURATION);
-        mFadeOut.addListener(new AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animator) {
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                item.performClick();
-                mFadeOut = null;
-                deselect();
-                show(false);
-                mOverlay.setAlpha(1);
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animator) {
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-            }
-
-        });
-        mFadeOut.start();
-    }
-
-    // root does not count
-    private boolean hasOpenItem() {
-        return mOpen.size() > 1;
-    }
-
-    // pop an item of the open item stack
-    private PieItem closeOpenItem() {
-        PieItem item = getOpenItem();
-        mOpen.remove(mOpen.size() -1);
-        return item;
-    }
-
-    private PieItem getOpenItem() {
-        return mOpen.get(mOpen.size() - 1);
-    }
-
-    // return the children either the root or parent of the current open item
-    private PieItem getParent() {
-        return mOpen.get(Math.max(0, mOpen.size() - 2));
-    }
-
-    private int getLevel() {
-        return mOpen.size() - 1;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        float alpha = 1;
-        if (mXFade != null) {
-            alpha = (Float) mXFade.getAnimatedValue();
-        } else if (mFadeIn != null) {
-            alpha = (Float) mFadeIn.getAnimatedValue();
-        } else if (mFadeOut != null) {
-            alpha = (Float) mFadeOut.getAnimatedValue();
-        }
-        int state = canvas.save();
-        if (mFadeIn != null) {
-            float sf = 0.9f + alpha * 0.1f;
-            canvas.scale(sf, sf, mPieCenterX, mPieCenterY);
-        }
-        if (mState != STATE_PIE) {
-            drawFocus(canvas);
-        }
-        if (mState == STATE_FINISHING) {
-            canvas.restoreToCount(state);
-            return;
-        }
-        if (mState != STATE_PIE) return;
-        if (!hasOpenItem() || (mXFade != null)) {
-            // draw base menu
-            drawArc(canvas, getLevel(), getParent());
-            List<PieItem> items = getParent().getItems();
-            final int count = items.size();
-            int pos = 0;
-            for (PieItem item : getParent().getItems()) {
-                drawItem(Math.max(0, mOpen.size() - 2), pos, count, canvas, item, alpha);
-                pos++;
-            }
-            mLabel.draw(canvas);
-        }
-        if (hasOpenItem()) {
-            int level = getLevel();
-            drawArc(canvas, level, getOpenItem());
-            List<PieItem> items = getOpenItem().getItems();
-            final int count = items.size();
-            int pos = 0;
-            for (PieItem inner : items) {
-                if (mFadeOut != null) {
-                    drawItem(level, pos, count, canvas, inner, alpha);
-                } else {
-                    drawItem(level, pos, count, canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
-                }
-                pos++;
-            }
-            mLabel.draw(canvas);
-        }
-        canvas.restoreToCount(state);
-    }
-
-    private void drawArc(Canvas canvas, int level, PieItem item) {
-        // arc
-        if (mState == STATE_PIE) {
-            final int count = item.getItems().size();
-            float start = mCenterAngle + (count * SWEEP_ARC / 2f);
-            float end =  mCenterAngle - (count * SWEEP_ARC / 2f);
-            int cy = mArcCenterY - level * mRadiusInc;
-            canvas.drawArc(new RectF(mPieCenterX - mArcRadius, cy - mArcRadius,
-                    mPieCenterX + mArcRadius, cy + mArcRadius),
-                    getDegrees(end), getDegrees(start) - getDegrees(end), false, mMenuArcPaint);
-        }
-    }
-
-    private void drawItem(int level, int pos, int count, Canvas canvas, PieItem item, float alpha) {
-        if (mState == STATE_PIE) {
-            if (item.getPath() != null) {
-                int y = mArcCenterY - level * mRadiusInc;
-                if (item.isSelected()) {
-                    Paint p = mSelectedPaint;
-                    int state = canvas.save();
-                    float angle = 0;
-                    if (mSlice != null) {
-                        angle = (Float) mSlice.getAnimatedValue();
-                    } else {
-                        angle = getArcCenter(item, pos, count) - SWEEP_ARC / 2f;
-                    }
-                    angle = getDegrees(angle);
-                    canvas.rotate(angle, mPieCenterX, y);
-                    if (mFadeOut != null) {
-                        p.setAlpha((int)(255 * alpha));
-                    }
-                    canvas.drawPath(item.getPath(), p);
-                    if (mFadeOut != null) {
-                        p.setAlpha(255);
-                    }
-                    canvas.restoreToCount(state);
-                }
-                if (mFadeOut == null) {
-                    alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
-                    // draw the item view
-                    item.setAlpha(alpha);
-                }
-                item.draw(canvas);
-            }
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent evt) {
-        float x = evt.getX();
-        float y = evt.getY();
-        int action = evt.getActionMasked();
-        getPolar(x, y, !mTapMode, mPolar);
-        if (MotionEvent.ACTION_DOWN == action) {
-            if ((x < mDeadZone) || (x > getWidth() - mDeadZone)) {
-                return false;
-            }
-            mDown.x = (int) evt.getX();
-            mDown.y = (int) evt.getY();
-            mOpening = false;
-            if (mTapMode) {
-                PieItem item = findItem(mPolar);
-                if ((item != null) && (mCurrentItem != item)) {
-                    mState = STATE_PIE;
-                    onEnter(item);
-                }
-            } else {
-                setCenter((int) x, (int) y);
-                show(true);
-            }
-            return true;
-        } else if (MotionEvent.ACTION_UP == action) {
-            if (isVisible()) {
-                PieItem item = mCurrentItem;
-                if (mTapMode) {
-                    item = findItem(mPolar);
-                    if (mOpening) {
-                        mOpening = false;
-                        return true;
-                    }
-                }
-                if (item == null) {
-                    mTapMode = false;
-                    show(false);
-                } else if (!mOpening && !item.hasItems()) {
-                        startFadeOut(item);
-                        mTapMode = false;
-                } else {
-                    mTapMode = true;
-                }
-                return true;
-            }
-        } else if (MotionEvent.ACTION_CANCEL == action) {
-            if (isVisible() || mTapMode) {
-                show(false);
-            }
-            deselect();
-            mHandler.removeMessages(MSG_OPENSUBMENU);
-            return false;
-        } else if (MotionEvent.ACTION_MOVE == action) {
-            if (pulledToCenter(mPolar)) {
-                mHandler.removeMessages(MSG_OPENSUBMENU);
-                if (hasOpenItem()) {
-                    if (mCurrentItem != null) {
-                        mCurrentItem.setSelected(false);
-                    }
-                    closeOpenItem();
-                    mCurrentItem = null;
-                } else {
-                    deselect();
-                }
-                mLabel.setText("");
-                return false;
-            }
-            PieItem item = findItem(mPolar);
-            boolean moved = hasMoved(evt);
-            if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
-                mHandler.removeMessages(MSG_OPENSUBMENU);
-                // only select if we didn't just open or have moved past slop
-                if (moved) {
-                    // switch back to swipe mode
-                    mTapMode = false;
-                }
-                onEnterSelect(item);
-                mHandler.sendEmptyMessageDelayed(MSG_OPENSUBMENU, PIE_OPEN_SUB_DELAY);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean isVisible() {
-        return super.isVisible();
-    }
-
-    private boolean pulledToCenter(PointF polarCoords) {
-        return polarCoords.y < mArcRadius - mRadiusInc;
-    }
-
-    private boolean inside(PointF polar, PieItem item, int pos, int count) {
-        float start = getSliceCenter(item, pos, count) - SWEEP_SLICE / 2f;
-        boolean res =  (mArcRadius < polar.y)
-                && (start < polar.x)
-                && (start + SWEEP_SLICE > polar.x)
-                && (!mTapMode || (mArcRadius + mRadiusInc > polar.y));
-        return res;
-    }
-
-    private void getPolar(float x, float y, boolean useOffset, PointF res) {
-        // get angle and radius from x/y
-        res.x = (float) Math.PI / 2;
-        x = x - mPieCenterX;
-        float y1 = mSliceCenterY - getLevel() * mRadiusInc - y;
-        float y2 = mArcCenterY - getLevel() * mRadiusInc - y;
-        res.y = (float) Math.sqrt(x * x + y2 * y2);
-        if (x != 0) {
-            res.x = (float) Math.atan2(y1,  x);
-            if (res.x < 0) {
-                res.x = (float) (2 * Math.PI + res.x);
-            }
-        }
-        res.y = res.y + (useOffset ? mTouchOffset : 0);
-    }
-
-    private boolean hasMoved(MotionEvent e) {
-        return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
-                + (e.getY() - mDown.y) * (e.getY() - mDown.y);
-    }
-
-    private void onEnterSelect(PieItem item) {
-        if (mCurrentItem != null) {
-            mCurrentItem.setSelected(false);
-        }
-        if (item != null && item.isEnabled()) {
-            moveSelection(mCurrentItem, item);
-            item.setSelected(true);
-            mCurrentItem = item;
-            mLabel.setText(mCurrentItem.getLabel());
-            layoutLabel(getLevel());
-        } else {
-            mCurrentItem = null;
-        }
-    }
-
-    private void onEnterOpen() {
-        if ((mCurrentItem != null) && (mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
-            openCurrentItem();
-        }
-    }
-
-    /**
-     * enter a slice for a view
-     * updates model only
-     * @param item
-     */
-    private void onEnter(PieItem item) {
-        if (mCurrentItem != null) {
-            mCurrentItem.setSelected(false);
-        }
-        if (item != null && item.isEnabled()) {
-            item.setSelected(true);
-            mCurrentItem = item;
-            mLabel.setText(mCurrentItem.getLabel());
-            if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
-                openCurrentItem();
-                layoutLabel(getLevel());
-            }
-        } else {
-            mCurrentItem = null;
-        }
-    }
-
-    private void deselect() {
-        if (mCurrentItem != null) {
-            mCurrentItem.setSelected(false);
-        }
-        if (hasOpenItem()) {
-            PieItem item = closeOpenItem();
-            onEnter(item);
-        } else {
-            mCurrentItem = null;
-        }
-    }
-
-    private int getItemPos(PieItem target) {
-        List<PieItem> items = getOpenItem().getItems();
-        return items.indexOf(target);
-    }
-
-    private int getCurrentCount() {
-        return getOpenItem().getItems().size();
-    }
-
-    private void moveSelection(PieItem from, PieItem to) {
-        final int count = getCurrentCount();
-        final int fromPos = getItemPos(from);
-        final int toPos = getItemPos(to);
-        if (fromPos != -1 && toPos != -1) {
-            float startAngle = getArcCenter(from, getItemPos(from), count)
-                    - SWEEP_ARC / 2f;
-            float endAngle = getArcCenter(to, getItemPos(to), count)
-                    - SWEEP_ARC / 2f;
-            mSlice = new ValueAnimator();
-            mSlice.setFloatValues(startAngle, endAngle);
-            // linear interpolater
-            mSlice.setInterpolator(null);
-            mSlice.setDuration(PIE_SLICE_DURATION);
-            mSlice.addListener(new AnimatorListener() {
-                @Override
-                public void onAnimationEnd(Animator arg0) {
-                    mSlice = null;
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator arg0) {
-                }
-
-                @Override
-                public void onAnimationStart(Animator arg0) {
-                }
-
-                @Override
-                public void onAnimationCancel(Animator arg0) {
-                }
-            });
-            mSlice.start();
-        }
-    }
-
-    private void openCurrentItem() {
-        if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
-            mOpen.add(mCurrentItem);
-            layoutLabel(getLevel());
-            mOpening = true;
-            if (mFadeIn != null) {
-                mFadeIn.cancel();
-            }
-            mXFade = new ValueAnimator();
-            mXFade.setFloatValues(1f, 0f);
-            mXFade.setDuration(PIE_XFADE_DURATION);
-            // Linear interpolation
-            mXFade.setInterpolator(null);
-            final PieItem ci = mCurrentItem;
-            mXFade.addListener(new AnimatorListener() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mXFade = null;
-                    ci.setSelected(false);
-                    mOpening = false;
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator animation) {
-                }
-
-                @Override
-                public void onAnimationCancel(Animator arg0) {
-                }
-            });
-            mXFade.start();
-        }
-    }
-
-    /**
-     * @param polar x: angle, y: dist
-     * @return the item at angle/dist or null
-     */
-    private PieItem findItem(PointF polar) {
-        // find the matching item:
-        List<PieItem> items = getOpenItem().getItems();
-        final int count = items.size();
-        int pos = 0;
-        for (PieItem item : items) {
-            if (inside(polar, item, pos, count)) {
-                return item;
-            }
-            pos++;
-        }
-        return null;
-    }
-
-
-    @Override
-    public boolean handlesTouch() {
-        return true;
-    }
-
-    // focus specific code
-
-    public void setBlockFocus(boolean blocked) {
-        mBlockFocus = blocked;
-        if (blocked) {
-            clear();
-        }
-    }
-
-    public void setFocus(int x, int y) {
-        mOverlay.removeCallbacks(mDisappear);
-        mFocusX = x;
-        mFocusY = y;
-        setCircle(mFocusX, mFocusY);
-    }
-
-    public int getSize() {
-        return 2 * mCircleSize;
-    }
-
-    private int getRandomRange() {
-        return (int)(-60 + 120 * Math.random());
-    }
-
-    private void setCircle(int cx, int cy) {
-        mCircle.set(cx - mCircleSize, cy - mCircleSize,
-                cx + mCircleSize, cy + mCircleSize);
-        mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
-                cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
-    }
-
-    public void drawFocus(Canvas canvas) {
-        if (mBlockFocus) return;
-        mFocusPaint.setStrokeWidth(mOuterStroke);
-        canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
-        if (mState == STATE_PIE) return;
-        int color = mFocusPaint.getColor();
-        if (mState == STATE_FINISHING) {
-            mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
-        }
-        mFocusPaint.setStrokeWidth(mInnerStroke);
-        drawLine(canvas, mDialAngle, mFocusPaint);
-        drawLine(canvas, mDialAngle + 45, mFocusPaint);
-        drawLine(canvas, mDialAngle + 180, mFocusPaint);
-        drawLine(canvas, mDialAngle + 225, mFocusPaint);
-        canvas.save();
-        // rotate the arc instead of its offset to better use framework's shape caching
-        canvas.rotate(mDialAngle, mFocusX, mFocusY);
-        canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
-        canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
-        canvas.restore();
-        mFocusPaint.setColor(color);
-    }
-
-    private void drawLine(Canvas canvas, int angle, Paint p) {
-        convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
-        convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
-        canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
-                mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
-    }
-
-    private static void convertCart(int angle, int radius, Point out) {
-        double a = 2 * Math.PI * (angle % 360) / 360;
-        out.x = (int) (radius * Math.cos(a) + 0.5);
-        out.y = (int) (radius * Math.sin(a) + 0.5);
-    }
-
-    @Override
-    public void showStart() {
-        if (mState == STATE_PIE) return;
-        cancelFocus();
-        mStartAnimationAngle = 67;
-        int range = getRandomRange();
-        startAnimation(SCALING_UP_TIME,
-                false, mStartAnimationAngle, mStartAnimationAngle + range);
-        mState = STATE_FOCUSING;
-    }
-
-    @Override
-    public void showSuccess(boolean timeout) {
-        if (mState == STATE_FOCUSING) {
-            startAnimation(SCALING_DOWN_TIME,
-                    timeout, mStartAnimationAngle);
-            mState = STATE_FINISHING;
-            mFocused = true;
-        }
-    }
-
-    @Override
-    public void showFail(boolean timeout) {
-        if (mState == STATE_FOCUSING) {
-            startAnimation(SCALING_DOWN_TIME,
-                    timeout, mStartAnimationAngle);
-            mState = STATE_FINISHING;
-            mFocused = false;
-        }
-    }
-
-    private void cancelFocus() {
-        mFocusCancelled = true;
-        mOverlay.removeCallbacks(mDisappear);
-        if (mAnimation != null && !mAnimation.hasEnded()) {
-            mAnimation.cancel();
-        }
-        mFocusCancelled = false;
-        mFocused = false;
-        mState = STATE_IDLE;
-    }
-
-    public void clear(boolean waitUntilProgressIsHidden) {
-        if (mState == STATE_PIE)
-            return;
-        cancelFocus();
-        mOverlay.post(mDisappear);
-    }
-
-    @Override
-    public void clear() {
-        clear(false);
-    }
-
-    private void startAnimation(long duration, boolean timeout,
-            float toScale) {
-        startAnimation(duration, timeout, mDialAngle,
-                toScale);
-    }
-
-    private void startAnimation(long duration, boolean timeout,
-            float fromScale, float toScale) {
-        setVisible(true);
-        mAnimation.reset();
-        mAnimation.setDuration(duration);
-        mAnimation.setScale(fromScale, toScale);
-        mAnimation.setAnimationListener(timeout ? mEndAction : null);
-        mOverlay.startAnimation(mAnimation);
-        update();
-    }
-
-    private class EndAction implements Animation.AnimationListener {
-        @Override
-        public void onAnimationEnd(Animation animation) {
-            // Keep the focus indicator for some time.
-            if (!mFocusCancelled && mOverlay != null) {
-                mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
-            }
-        }
-
-        @Override
-        public void onAnimationRepeat(Animation animation) {
-        }
-
-        @Override
-        public void onAnimationStart(Animation animation) {
-        }
-    }
-
-    private class Disappear implements Runnable {
-        @Override
-        public void run() {
-            if (mState == STATE_PIE) return;
-            setVisible(false);
-            mFocusX = mCenterX;
-            mFocusY = mCenterY;
-            mState = STATE_IDLE;
-            setCircle(mFocusX, mFocusY);
-            mFocused = false;
-        }
-    }
-
-    private class ScaleAnimation extends Animation {
-        private float mFrom = 1f;
-        private float mTo = 1f;
-
-        public ScaleAnimation() {
-            setFillAfter(true);
-        }
-
-        public void setScale(float from, float to) {
-            mFrom = from;
-            mTo = to;
-        }
-
-        @Override
-        protected void applyTransformation(float interpolatedTime, Transformation t) {
-            mDialAngle = (int)(mFrom + (mTo - mFrom) * interpolatedTime);
-        }
-    }
-
-}
index ba0e697..3ccc758 100644 (file)
@@ -198,15 +198,6 @@ public class PreviewOverlay extends View
                 if (ev.getPointerCount() > 1) {
                     mDeltaX = ev.getX(1) - ev.getX(0);
                     mDeltaY = ev.getY(1) - ev.getY(0);
-                    if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                        if (!mZoomProcessor.isVisible()) {
-                            mZoomProcessor.showZoomUI();
-                        }
-                    } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
-                        if (mZoomProcessor.isVisible()) {
-                            mZoomProcessor.hideZoomUI();
-                        }
-                    }
                 }
                 return handled;
             }
@@ -309,8 +300,12 @@ public class PreviewOverlay extends View
         public boolean onScale(ScaleGestureDetector detector) {
             final float sf = detector.getScaleFactor();
             mCurrentRatio = (0.33f + mCurrentRatio) * sf * sf - 0.33f;
-            if (mCurrentRatio < mMinRatio) mCurrentRatio = mMinRatio;
-            if (mCurrentRatio > mMaxRatio) mCurrentRatio = mMaxRatio;
+            if (mCurrentRatio < mMinRatio) {
+                mCurrentRatio = mMinRatio;
+            }
+            if (mCurrentRatio > mMaxRatio) {
+                mCurrentRatio = mMaxRatio;
+            }
 
             // Only call the listener with a certain frequency. This is
             // necessary because these listeners will make repeated
@@ -331,10 +326,10 @@ public class PreviewOverlay extends View
 
         @Override
         public boolean onScaleBegin(ScaleGestureDetector detector) {
+            mZoomProcessor.showZoomUI();
             if (mZoomListener == null) {
                 return false;
             }
-            mVisible = true;
             if (mZoomListener != null) {
                 mZoomListener.onZoomStart();
             }
@@ -345,7 +340,7 @@ public class PreviewOverlay extends View
 
         @Override
         public void onScaleEnd(ScaleGestureDetector detector) {
-            mVisible = false;
+            mZoomProcessor.hideZoomUI();
             if (mZoomListener != null) {
                 mZoomListener.onZoomEnd();
             }
diff --git a/src/com/android/camera/ui/RenderOverlay.java b/src/com/android/camera/ui/RenderOverlay.java
deleted file mode 100644 (file)
index 8daf819..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2012 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.ui;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.camera.PreviewGestures;
-import com.android.camera.debug.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class RenderOverlay extends FrameLayout {
-
-    private static final Log.Tag TAG = new Log.Tag("RenderOverlay");
-    private PreviewGestures.SingleTapListener mTapListener;
-
-    interface Renderer {
-
-        public boolean handlesTouch();
-        public boolean onTouchEvent(MotionEvent evt);
-        public void setOverlay(RenderOverlay overlay);
-        public void layout(int left, int top, int right, int bottom);
-        public void draw(Canvas canvas);
-
-    }
-
-    private final RenderView mRenderView;
-    private final List<Renderer> mClients;
-    private PreviewGestures mGestures;
-    // reverse list of touch clients
-    private final List<Renderer> mTouchClients;
-    private final int[] mPosition = new int[2];
-
-    public RenderOverlay(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mRenderView = new RenderView(context);
-        addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT,
-                LayoutParams.MATCH_PARENT));
-        mClients = new ArrayList<Renderer>(10);
-        mTouchClients = new ArrayList<Renderer>(10);
-        setWillNotDraw(false);
-    }
-
-    public void setGestures(PreviewGestures gestures) {
-        mGestures = gestures;
-    }
-
-    public void addRenderer(Renderer renderer) {
-        mClients.add(renderer);
-        renderer.setOverlay(this);
-        if (renderer.handlesTouch()) {
-            mTouchClients.add(0, renderer);
-        }
-        renderer.layout(getLeft(), getTop(), getRight(), getBottom());
-    }
-
-    public void addRenderer(int pos, Renderer renderer) {
-        mClients.add(pos, renderer);
-        renderer.setOverlay(this);
-        renderer.layout(getLeft(), getTop(), getRight(), getBottom());
-    }
-
-    public void remove(Renderer renderer) {
-        mClients.remove(renderer);
-        renderer.setOverlay(null);
-    }
-
-    public int getClientSize() {
-        return mClients.size();
-    }
-
-    // TODO: Remove this when refactor is done. This is only here temporarily
-    // to keep pie working before it's replaced with bottom bar.
-    public void directTouchEventsToPie(MotionEvent ev) {
-        PieRenderer pie = null;
-        for (int i = 0; i < mClients.size(); i++) {
-            if (mClients.get(i) instanceof PieRenderer) {
-                pie = (PieRenderer) mClients.get(i);
-                break;
-            }
-        }
-        if (pie!= null && pie.isOpen()) {
-            pie.onTouchEvent(ev);
-        } else {
-            if (mTapListener != null && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mTapListener.onSingleTapUp(null, ((int) ev.getX()), ((int) ev.getY()));
-            }
-        }
-    }
-
-    // TODO: Remove this when refactor is done. This is only here temporarily
-    // to keep pie working before it's replaced with bottom bar.
-    public void clear() {
-        mGestures = null;
-        while (mClients.size() > 0) {
-            remove(mClients.get(0));
-        }
-        mTouchClients.clear();
-        mTapListener = null;
-    }
-
-    // TODO: Design a touch flow for each module to handle the leftover touch
-    // events from the app
-    public void setTapListener(PreviewGestures.SingleTapListener listener) {
-        mTapListener = listener;
-    }
-
-
-    // Only Gcam is using this right now, which is only using the pie
-    // menu for the capture progress.
-    // TODO: migrate all modes to PreviewOverlay.
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (mTapListener != null && ev.getActionMasked() == MotionEvent.ACTION_UP) {
-            mTapListener.onSingleTapUp(null, ((int) ev.getX()), ((int) ev.getY()));
-        }
-        return true;
-    }
-
-    public boolean directDispatchTouch(MotionEvent m, Renderer target) {
-        mRenderView.setTouchTarget(target);
-        boolean res = mRenderView.dispatchTouchEvent(m);
-        mRenderView.setTouchTarget(null);
-        return res;
-    }
-
-    private void adjustPosition() {
-        getLocationInWindow(mPosition);
-    }
-
-    public int getWindowPositionX() {
-        return mPosition[0];
-    }
-
-    public int getWindowPositionY() {
-        return mPosition[1];
-    }
-
-    public void update() {
-        mRenderView.invalidate();
-    }
-
-    private class RenderView extends View {
-
-        private Renderer mTouchTarget;
-
-        public RenderView(Context context) {
-            super(context);
-            setWillNotDraw(false);
-        }
-
-        public void setTouchTarget(Renderer target) {
-            mTouchTarget = target;
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent evt) {
-            if (mGestures == null) {
-                return false;
-            }
-
-            if (mTouchTarget != null) {
-                return mTouchTarget.onTouchEvent(evt);
-            }
-            if (mTouchClients != null) {
-                boolean res = false;
-                for (Renderer client : mTouchClients) {
-                    res |= client.onTouchEvent(evt);
-                }
-                return res;
-            }
-            return false;
-        }
-
-        @Override
-        public void onLayout(boolean changed, int left, int top, int right, int bottom) {
-            adjustPosition();
-            super.onLayout(changed, left,  top, right, bottom);
-            if (mClients == null) return;
-            for (Renderer renderer : mClients) {
-                renderer.layout(left, top, right, bottom);
-            }
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            super.draw(canvas);
-            if (mClients == null) return;
-            boolean redraw = false;
-            for (Renderer renderer : mClients) {
-                renderer.draw(canvas);
-                redraw = redraw || ((OverlayRenderer) renderer).isVisible();
-            }
-            if (redraw) {
-                invalidate();
-            }
-        }
-    }
-
-}
diff --git a/src/com/android/camera/ui/ZoomRenderer.java b/src/com/android/camera/ui/ZoomRenderer.java
deleted file mode 100644 (file)
index 2b997b2..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2012 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.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.view.ScaleGestureDetector;
-
-import com.android.camera.debug.Log;
-import com.android.camera2.R;
-
-// TODO: remove this; functionality has been moved to PreviewOverlay.
-@Deprecated
-public class ZoomRenderer extends OverlayRenderer
-        implements ScaleGestureDetector.OnScaleGestureListener {
-
-    private static final Log.Tag TAG = new Log.Tag("ZoomRenderer");
-
-    final private int mMinIndex = 0;
-    private int mMaxIndex;
-    // Discrete Zoom level [mMinIndex,mMaxIndex].
-    private int mCurrentIndex;
-    // Continuous Zoom level [0,1].
-    private float mCurrentFraction;
-    private double mFingerRadians;
-    private OnZoomChangedListener mListener;
-    private final ScaleGestureDetector mDetector;
-    private final Paint mPaint;
-    private int mCenterX;
-    private int mCenterY;
-    private float mOuterRadius;
-    private float mInnerRadius;
-    private final int mZoomStroke;
-
-    public interface OnZoomChangedListener {
-        void onZoomStart();
-        void onZoomEnd();
-        void onZoomValueChanged(int index);
-    }
-
-    public ZoomRenderer(Context ctx) {
-        Resources res = ctx.getResources();
-        mZoomStroke = res.getDimensionPixelSize(R.dimen.zoom_stroke);
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setColor(Color.WHITE);
-        mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeWidth(mZoomStroke);
-        mPaint.setStrokeCap(Paint.Cap.ROUND);
-        mDetector = new ScaleGestureDetector(ctx, this);
-        setVisible(false);
-    }
-
-    // Set maximum Zoom Index from Module.
-    public void setZoomMax(int zoomMaxIndex) {
-        mMaxIndex = zoomMaxIndex;
-    }
-
-    // Set current Zoom Index from Module.
-    public void setZoom(int index) {
-        mCurrentIndex = index;
-        mCurrentFraction = (float) index / (mMaxIndex - mMinIndex);
-    }
-
-    // Set Zoom Value to display from Module.
-    public void setZoomValue(int centiValue) {
-        // Do nothing.
-    }
-
-    public void setOnZoomChangeListener(OnZoomChangedListener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public void layout(int l, int t, int r, int b) {
-        super.layout(l, t, r, b);
-        mCenterX = (r - l) / 2;
-        mCenterY = (b - t) / 2;
-        // UI will extend from 20% to 80% of maximum inset circle.
-        float insetCircleRadius = Math.min(getWidth(), getHeight());
-        mInnerRadius = insetCircleRadius * 0.12f;
-        mOuterRadius = insetCircleRadius * 0.38f;
-    }
-
-    public boolean isScaling() {
-        return mDetector.isInProgress();
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        // Draw background.
-        mPaint.setAlpha(70);
-        canvas.drawLine(mCenterX + mInnerRadius * (float) Math.cos(mFingerRadians),
-                mCenterY - mInnerRadius * (float) Math.sin(mFingerRadians),
-                mCenterX + mOuterRadius * (float) Math.cos(mFingerRadians),
-                mCenterY - mOuterRadius * (float) Math.sin(mFingerRadians), mPaint);
-        canvas.drawLine(mCenterX - mInnerRadius * (float) Math.cos(mFingerRadians),
-                mCenterY + mInnerRadius * (float) Math.sin(mFingerRadians),
-                mCenterX - mOuterRadius * (float) Math.cos(mFingerRadians),
-                mCenterY + mOuterRadius * (float) Math.sin(mFingerRadians), mPaint);
-        // Draw Zoom progress.
-        mPaint.setAlpha(255);
-        float zoomRadius = mInnerRadius + mCurrentFraction * (mOuterRadius - mInnerRadius);
-        canvas.drawLine(mCenterX + mInnerRadius * (float) Math.cos(mFingerRadians),
-                mCenterY - mInnerRadius * (float) Math.sin(mFingerRadians),
-                mCenterX + zoomRadius * (float) Math.cos(mFingerRadians),
-                mCenterY - zoomRadius * (float) Math.sin(mFingerRadians), mPaint);
-        canvas.drawLine(mCenterX - mInnerRadius * (float) Math.cos(mFingerRadians),
-                mCenterY + mInnerRadius * (float) Math.sin(mFingerRadians),
-                mCenterX - zoomRadius * (float) Math.cos(mFingerRadians),
-                mCenterY + zoomRadius * (float) Math.sin(mFingerRadians), mPaint);
-    }
-
-    @Override
-    public boolean onScale(ScaleGestureDetector detector) {
-        final float sf = detector.getScaleFactor();
-        mCurrentFraction = (0.33f + mCurrentFraction) * sf * sf - 0.33f;
-        if (mCurrentFraction < 0.0f) mCurrentFraction = 0.0f;
-        if (mCurrentFraction > 1.0f) mCurrentFraction = 1.0f;
-        int newIndex = mMinIndex + (int) (mCurrentFraction * (mMaxIndex - mMinIndex));
-        if (mListener != null && newIndex != mCurrentIndex) {
-            mListener.onZoomValueChanged(newIndex);
-            mCurrentIndex = newIndex;
-        }
-        // mFingerRadians is currently constrained to [0,Pi/2].
-        // TODO: Get actual touch coordinates to enable full [0,Pi] range.
-        mFingerRadians = Math.atan2(detector.getCurrentSpanY(),detector.getCurrentSpanX());
-        return true;
-    }
-
-    @Override
-    public boolean onScaleBegin(ScaleGestureDetector detector) {
-        setVisible(true);
-        if (mListener != null) {
-            mListener.onZoomStart();
-        }
-        update();
-        return true;
-    }
-
-    @Override
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        setVisible(false);
-        if (mListener != null) {
-            mListener.onZoomEnd();
-        }
-    }
-
-}
index caa748f..db27471 100644 (file)
@@ -54,6 +54,7 @@ public class ApiHelper {
 
     public static final boolean HAS_HIDEYBARS = isKitKatOrHigher();
 
+    public static final boolean IS_NEXUS_4 = "mako".equalsIgnoreCase(Build.DEVICE);
     public static final boolean IS_NEXUS_5 = "LGE".equalsIgnoreCase(Build.MANUFACTURER)
             && "hammerhead".equalsIgnoreCase(Build.DEVICE);
     public static final boolean IS_NEXUS_6 = "motorola".equalsIgnoreCase(Build.MANUFACTURER)
index 627b6bf..3a7e355 100644 (file)
@@ -30,6 +30,7 @@ import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Matrix;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.camera2.CameraCharacteristics;
@@ -322,6 +323,7 @@ public class CameraUtil {
     }
 
     public static int nextPowerOf2(int n) {
+        // TODO: what happens if n is negative or already a power of 2?
         n -= 1;
         n |= n >>> 16;
         n |= n >>> 8;
@@ -337,6 +339,10 @@ public class CameraUtil {
         return (float) Math.sqrt(dx * dx + dy * dy);
     }
 
+    /**
+     * Clamps x to between min and max (inclusive on both ends, x = min --> min,
+     * x = max --> max).
+     */
     public static int clamp(int x, int min, int max) {
         if (x > max) {
             return max;
@@ -347,6 +353,10 @@ public class CameraUtil {
         return x;
     }
 
+    /**
+     * Clamps x to between min and max (inclusive on both ends, x = min --> min,
+     * x = max --> max).
+     */
     public static float clamp(float x, float min, float max) {
         if (x > max) {
             return max;
@@ -366,6 +376,31 @@ public class CameraUtil {
     }
 
     /**
+     * Given (nx, ny) \in [0, 1]^2, in the display's portrait coordinate system,
+     * returns normalized sensor coordinates \in [0, 1]^2 depending on how
+     * the sensor's orientation \in {0, 90, 180, 270}.
+     *
+     * <p>
+     * Returns null if sensorOrientation is not one of the above.
+     * </p>
+     */
+    public static PointF normalizedSensorCoordsForNormalizedDisplayCoords(
+        float nx, float ny, int sensorOrientation) {
+        switch (sensorOrientation) {
+        case 0:
+            return new PointF(nx, ny);
+        case 90:
+            return new PointF(ny, 1.0f - nx);
+        case 180:
+            return new PointF(1.0f - nx, 1.0f - ny);
+        case 270:
+            return new PointF(1.0f - ny, nx);
+        default:
+            return null;
+        }
+    }
+
+    /**
      * Given a size, return the largest size with the given aspectRatio that
      * maximally fits into the bounding rectangle of the original Size.
      *
index 371dcdb..7bf675e 100644 (file)
@@ -792,13 +792,23 @@ public class FilmstripView extends ViewGroup {
     }
 
     private ViewItem buildItemFromData(int dataID) {
+        if (mActivity.isDestroyed()) {
+            // Loading item data is call from multiple AsyncTasks and the
+            // activity may be finished when buildItemFromData is called.
+            Log.d(TAG, "Activity destroyed, don't load data");
+            return null;
+        }
         ImageData data = mDataAdapter.getImageData(dataID);
         if (data == null) {
             return null;
         }
 
-        int width = Math.round(mScale * getWidth());
-        int height = Math.round(mScale * getHeight());
+        // Always scale by fixed filmstrip scale, since we only show items when
+        // in filmstrip. Preloading images with a different scale and bounds
+        // interferes with caching.
+        int width = Math.round(FILM_STRIP_SCALE * getWidth());
+        int height = Math.round(FILM_STRIP_SCALE * getHeight());
+        Log.v(TAG, "suggesting item bounds: " + width + "x" + height);
         mDataAdapter.suggestViewSizeBound(width, height);
 
         data.prepare();