OSDN Git Service

Fix b/5317371 to make action-bar consistent with framework CAB.
authorYuli Huang <yuli@google.com>
Fri, 30 Sep 2011 18:05:06 +0000 (02:05 +0800)
committerYuli Huang <yuli@google.com>
Mon, 3 Oct 2011 16:26:14 +0000 (00:26 +0800)
1. Refactor to decouple ActionBar from FilterStackListener and
ActionBarListener to avoid FilterStack/Toolbar depending on ActionBar.
2. Recreate the action-bar on configuration changes and restore button
status and behaviors from the old action-bar.
3. Use framework CAB styles/dimensions to have consistent look and feel.

Change-Id: Ib7be0e0b8135f5f86af65b320f09b3d691464f54

18 files changed:
res/drawable/photoeditor_arrow_back.png [deleted file]
res/layout/photoeditor_actionbar.xml [new file with mode: 0644]
res/layout/photoeditor_main.xml
res/values-sw320dp-land/photoeditor_dimens.xml [new file with mode: 0755]
res/values-sw320dp-port/photoeditor_dimens.xml [new file with mode: 0755]
res/values-sw320dp/photoeditor_dimens.xml
res/values-sw600dp/photoeditor_dimens.xml
res/values-sw800dp/photoeditor_dimens.xml
res/values/photoeditor_strings.xml
res/values/photoeditor_styles.xml
src/com/android/gallery3d/photoeditor/ActionBar.java
src/com/android/gallery3d/photoeditor/FilterStack.java
src/com/android/gallery3d/photoeditor/LoadScreennailTask.java
src/com/android/gallery3d/photoeditor/PhotoEditor.java
src/com/android/gallery3d/photoeditor/SaveCopyTask.java
src/com/android/gallery3d/photoeditor/Toolbar.java
src/com/android/gallery3d/photoeditor/ToolbarIdleHandler.java [deleted file]
src/com/android/gallery3d/photoeditor/YesNoCancelDialogBuilder.java [new file with mode: 0644]

diff --git a/res/drawable/photoeditor_arrow_back.png b/res/drawable/photoeditor_arrow_back.png
deleted file mode 100644 (file)
index 897a1c1..0000000
Binary files a/res/drawable/photoeditor_arrow_back.png and /dev/null differ
diff --git a/res/layout/photoeditor_actionbar.xml b/res/layout/photoeditor_actionbar.xml
new file mode 100644 (file)
index 0000000..82ed41f
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.gallery3d.photoeditor.ActionBar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/action_bar"
+    style="@style/TopActionBar">
+
+    <LinearLayout style="@style/ActionBarLinearLayout">
+
+        <LinearLayout
+            android:id="@+id/action_bar_back"
+            style="@style/ActionBarBackLinearLayout">
+            <ImageView style="@style/ActionBarArrow"/>
+            <ImageView style="@style/ActionBarIcon"/>
+        </LinearLayout>
+
+        <TextView
+            android:id="@+id/action_bar_title"
+            style="@style/ActionBarTitle"
+            android:text="@string/edit"/>
+    </LinearLayout>
+
+    <LinearLayout style="@style/ActionBarLinearLayout" android:layout_alignParentRight="true">
+
+        <ImageButton
+            android:id="@+id/undo_button"
+            style="@style/ImageActionButton"
+            android:src="@drawable/photoeditor_undo"/>
+        <ImageButton
+            android:id="@+id/redo_button"
+            style="@style/ImageActionButton"
+            android:src="@drawable/photoeditor_redo"/>
+        <Button
+            android:id="@+id/save_button"
+            style="@style/TextActionButton"
+            android:text="@string/save"/>
+    </LinearLayout>
+</com.android.gallery3d.photoeditor.ActionBar>
index ed4841a..49597ed 100644 (file)
         </LinearLayout>
     </com.android.gallery3d.photoeditor.EffectsBar>
 
-    <com.android.gallery3d.photoeditor.ActionBar android:id="@+id/action_bar" style="@style/TopActionBar">
+    <include layout="@layout/photoeditor_actionbar"/>
 
-        <LinearLayout style="@style/ActionBarLinearLayout">
-
-            <LinearLayout
-                android:id="@+id/action_bar_back"
-                style="@style/ActionBarBackLinearLayout">
-                <ImageView style="@style/ActionBarArrow"/>
-                <ImageView style="@style/ActionBarIcon"/>
-            </LinearLayout>
-
-            <TextView
-                android:id="@+id/action_bar_title"
-                style="@style/ActionBarText"
-                android:text="@string/edit"/>
-        </LinearLayout>
-
-        <LinearLayout style="@style/ActionBarLinearLayout" android:layout_alignParentRight="true">
-
-            <ImageButton
-                android:id="@+id/undo_button"
-                style="@style/ImageActionButton"
-                android:src="@drawable/photoeditor_undo"/>
-            <ImageButton
-                android:id="@+id/redo_button"
-                style="@style/ImageActionButton"
-                android:src="@drawable/photoeditor_redo"/>
-            <Button
-                android:id="@+id/save_button"
-                style="@style/TextActionButton"
-                android:text="@string/save"/>
-        </LinearLayout>
-    </com.android.gallery3d.photoeditor.ActionBar>
 </com.android.gallery3d.photoeditor.Toolbar>
diff --git a/res/values-sw320dp-land/photoeditor_dimens.xml b/res/values-sw320dp-land/photoeditor_dimens.xml
new file mode 100755 (executable)
index 0000000..844c928
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="action_bar_icon_padding_vertical">4dp</dimen>
+    <dimen name="action_button_padding_vertical">4dp</dimen>
+</resources>
diff --git a/res/values-sw320dp-port/photoeditor_dimens.xml b/res/values-sw320dp-port/photoeditor_dimens.xml
new file mode 100755 (executable)
index 0000000..85779d6
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="action_bar_icon_padding_vertical">8dp</dimen>
+    <dimen name="action_button_padding_vertical">8dp</dimen>
+</resources>
index c0f523f..5d894a6 100755 (executable)
     <dimen name="effect_label_text_size">11sp</dimen>
     <dimen name="effect_label_width">98dp</dimen>
     <dimen name="effect_icon_size">72dp</dimen>
-    <dimen name="effect_padding_left_right">2dp</dimen>
+    <dimen name="effect_padding_horizontal">2dp</dimen>
     <dimen name="effects_container_padding">12dp</dimen>
-    <dimen name="action_bar_height">40dp</dimen>
-    <dimen name="action_bar_text_size">17sp</dimen>
-    <dimen name="action_bar_arrow_padding_top">12dp</dimen>
-    <dimen name="action_bar_arrow_padding_bottom">12dp</dimen>
-    <dimen name="action_bar_arrow_padding_left">3dp</dimen>
-    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
-    <dimen name="action_bar_icon_padding_top">4dp</dimen>
-    <dimen name="action_bar_icon_padding_bottom">4dp</dimen>
+    <dimen name="action_bar_arrow_margin_left">3dp</dimen>
+    <dimen name="action_bar_arrow_margin_right">-3dp</dimen>
     <dimen name="action_bar_icon_padding_left">0dp</dimen>
-    <dimen name="action_bar_icon_padding_right">6dp</dimen>
-    <dimen name="action_button_padding_top">5dp</dimen>
-    <dimen name="action_button_padding_bottom">5dp</dimen>
-    <dimen name="action_button_padding_left">15dp</dimen>
-    <dimen name="action_button_padding_right">15dp</dimen>
+    <dimen name="action_bar_icon_padding_right">5dp</dimen>
+    <dimen name="action_button_padding_horizontal">13dp</dimen>
     <dimen name="effect_tool_panel_padding">12dp</dimen>
     <dimen name="seekbar_width">290dp</dimen>
     <dimen name="seekbar_height">29dp</dimen>
index 413347d..2b9cf6f 100755 (executable)
     <dimen name="effect_label_text_size">14sp</dimen>
     <dimen name="effect_label_width">120dp</dimen>
     <dimen name="effect_icon_size">90dp</dimen>
-    <dimen name="effect_padding_left_right">2dp</dimen>
+    <dimen name="effect_padding_horizontal">2dp</dimen>
     <dimen name="effects_container_padding">12dp</dimen>
-    <dimen name="action_bar_height">56dp</dimen>
-    <dimen name="action_bar_text_size">18sp</dimen>
-    <dimen name="action_bar_arrow_padding_top">20dp</dimen>
-    <dimen name="action_bar_arrow_padding_bottom">20dp</dimen>
-    <dimen name="action_bar_arrow_padding_left">4dp</dimen>
-    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
-    <dimen name="action_bar_icon_padding_top">7dp</dimen>
-    <dimen name="action_bar_icon_padding_bottom">7dp</dimen>
+    <dimen name="action_bar_arrow_margin_left">3dp</dimen>
+    <dimen name="action_bar_arrow_margin_right">-3dp</dimen>
+    <dimen name="action_bar_icon_padding_vertical">4dp</dimen>
     <dimen name="action_bar_icon_padding_left">0dp</dimen>
-    <dimen name="action_bar_icon_padding_right">8dp</dimen>
-    <dimen name="action_button_padding_top">9dp</dimen>
-    <dimen name="action_button_padding_bottom">9dp</dimen>
-    <dimen name="action_button_padding_left">22dp</dimen>
-    <dimen name="action_button_padding_right">22dp</dimen>
+    <dimen name="action_bar_icon_padding_right">5dp</dimen>
+    <dimen name="action_button_padding_vertical">8dp</dimen>
+    <dimen name="action_button_padding_horizontal">22dp</dimen>
     <dimen name="effect_tool_panel_padding">16dp</dimen>
     <dimen name="seekbar_width">560dp</dimen>
     <dimen name="seekbar_height">33dp</dimen>
index 3b054ce..9dab922 100755 (executable)
     <dimen name="effect_label_text_size">15sp</dimen>
     <dimen name="effect_label_width">138dp</dimen>
     <dimen name="effect_icon_size">100dp</dimen>
-    <dimen name="effect_padding_left_right">3dp</dimen>
+    <dimen name="effect_padding_horizontal">3dp</dimen>
     <dimen name="effects_container_padding">18dp</dimen>
-    <dimen name="action_bar_height">56dp</dimen>
-    <dimen name="action_bar_text_size">19sp</dimen>
-    <dimen name="action_bar_arrow_padding_top">20dp</dimen>
-    <dimen name="action_bar_arrow_padding_bottom">20dp</dimen>
-    <dimen name="action_bar_arrow_padding_left">4dp</dimen>
-    <dimen name="action_bar_arrow_padding_right">0dp</dimen>
-    <dimen name="action_bar_icon_padding_top">7dp</dimen>
-    <dimen name="action_bar_icon_padding_bottom">7dp</dimen>
+    <dimen name="action_bar_arrow_margin_left">3dp</dimen>
+    <dimen name="action_bar_arrow_margin_right">-3dp</dimen>
+    <dimen name="action_bar_icon_padding_vertical">4dp</dimen>
     <dimen name="action_bar_icon_padding_left">0dp</dimen>
-    <dimen name="action_bar_icon_padding_right">11dp</dimen>
-    <dimen name="action_button_padding_top">9dp</dimen>
-    <dimen name="action_button_padding_bottom">9dp</dimen>
-    <dimen name="action_button_padding_left">28dp</dimen>
-    <dimen name="action_button_padding_right">28dp</dimen>
+    <dimen name="action_bar_icon_padding_right">5dp</dimen>
+    <dimen name="action_button_padding_vertical">8dp</dimen>
+    <dimen name="action_button_padding_horizontal">28dp</dimen>
     <dimen name="effect_tool_panel_padding">18dp</dimen>
     <dimen name="seekbar_width">560dp</dimen>
     <dimen name="seekbar_height">35dp</dimen>
index 4a5d346..b282a45 100644 (file)
@@ -40,7 +40,7 @@
     <string name="no">No</string>
 
     <!-- Text button in the action bar for the user to save edited photo. [CHAR LIMIT=8] -->
-    <string name="save">Save</string>
+    <string name="save">SAVE</string>
 
     <!-- Name for the photo effect that auto fixes exposure. [CHAR LIMIT=15] -->
     <string name="autofix">Auto-fix</string>
index 42b91d1..fe5ad1a 100644 (file)
@@ -37,8 +37,8 @@
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_gravity">center_vertical</item>
-        <item name="android:paddingLeft">@dimen/effect_padding_left_right</item>
-        <item name="android:paddingRight">@dimen/effect_padding_left_right</item>
+        <item name="android:paddingLeft">@dimen/effect_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/effect_padding_horizontal</item>
         <item name="android:orientation">vertical</item>
     </style>
     <style name="EffectsContainer">
     </style>
     <style name="ActionBar">
         <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">@dimen/action_bar_height</item>
+        <item name="android:layout_height">?android:attr/actionBarSize</item>
     </style>
     <style name="ActionBarLinearLayout">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">fill_parent</item>
-        <item name="android:layout_centerVertical">true</item>
         <item name="android:orientation">horizontal</item>
     </style>
     <style name="ActionBarBackLinearLayout" parent="@style/ActionBarLinearLayout">
         <item name="android:scaleType">centerInside</item>
         <item name="android:adjustViewBounds">true</item>
     </style>
-    <style name="ActionBarArrow" parent="@style/ActionBarImageView">
-        <item name="android:paddingTop">@dimen/action_bar_arrow_padding_top</item>
-        <item name="android:paddingBottom">@dimen/action_bar_arrow_padding_bottom</item>
-        <item name="android:paddingLeft">@dimen/action_bar_arrow_padding_left</item>
-        <item name="android:paddingRight">@dimen/action_bar_arrow_padding_right</item>
-        <item name="android:src">@drawable/photoeditor_arrow_back</item>
+    <style name="ActionBarArrow">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:layout_marginLeft">@dimen/action_bar_arrow_margin_left</item>
+        <item name="android:layout_marginRight">@dimen/action_bar_arrow_margin_right</item>
+        <item name="android:src">?android:attr/homeAsUpIndicator</item>
     </style>
     <style name="ActionBarIcon" parent="@style/ActionBarImageView">
-        <item name="android:paddingTop">@dimen/action_bar_icon_padding_top</item>
-        <item name="android:paddingBottom">@dimen/action_bar_icon_padding_bottom</item>
+        <item name="android:paddingTop">@dimen/action_bar_icon_padding_vertical</item>
+        <item name="android:paddingBottom">@dimen/action_bar_icon_padding_vertical</item>
         <item name="android:paddingLeft">@dimen/action_bar_icon_padding_left</item>
         <item name="android:paddingRight">@dimen/action_bar_icon_padding_right</item>
         <item name="android:src">@mipmap/ic_launcher_gallery</item>
     </style>
-    <style name="ActionBarText">
+    <style name="ActionBarTitle" parent="android:TextAppearance.Holo.Widget.ActionBar.Title">
         <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_gravity">center</item>
-        <item name="android:textSize">@dimen/action_bar_text_size</item>
-        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:gravity">center</item>
     </style>
     <style name="ImageActionButton" parent="@style/ActionBarImageView">
-        <item name="android:paddingTop">@dimen/action_button_padding_top</item>
-        <item name="android:paddingBottom">@dimen/action_button_padding_bottom</item>
-        <item name="android:paddingLeft">@dimen/action_button_padding_left</item>
-        <item name="android:paddingRight">@dimen/action_button_padding_right</item>
+        <item name="android:paddingTop">@dimen/action_button_padding_vertical</item>
+        <item name="android:paddingBottom">@dimen/action_button_padding_vertical</item>
+        <item name="android:paddingLeft">@dimen/action_button_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/action_button_padding_horizontal</item>
         <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
-    <style name="TextActionButton" parent="@style/ActionBarText">
-        <item name="android:paddingLeft">@dimen/action_button_padding_left</item>
-        <item name="android:paddingRight">@dimen/action_button_padding_right</item>
+    <style name="TextActionButton" parent="android:TextAppearance.Holo.Widget.ActionBar.Menu">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingLeft">@dimen/action_button_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/action_button_padding_horizontal</item>
         <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
     <style name="EffectsMenuActionButton" parent="@style/ImageActionButton">
index 32eb792..5a423b9 100644 (file)
 package com.android.gallery3d.photoeditor;
 
 import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.RelativeLayout;
 
 import com.android.gallery3d.R;
 
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
 /**
- * Action bar that contains buttons such as undo, redo, save, etc. and listens to stack changes for
- * enabling/disabling buttons.
+ * Action bar that contains buttons such as undo, redo, save, etc.
  */
-public class ActionBar extends RelativeLayout implements FilterStack.StackListener {
-
-    /**
-     * Listener of action button clicked.
-     */
-    public interface ActionBarListener {
-
-        void onUndo();
+public class ActionBar extends RelativeLayout {
 
-        void onRedo();
-
-        void onSave();
-    }
-
-    private static final int ENABLE_BUTTON = 1;
     private static final float ENABLED_ALPHA = 1;
     private static final float DISABLED_ALPHA = 0.47f;
 
-    private final Handler handler;
-    private View undo;
-    private View redo;
-    private View save;
+    private final HashMap<Integer, Runnable> buttonRunnables = new HashMap<Integer, Runnable>();
+    private final HashSet<Integer> changedButtons = new HashSet<Integer>();
 
     public ActionBar(Context context, AttributeSet attrs) {
         super(context, attrs);
-
-        handler = new Handler() {
-
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case ENABLE_BUTTON:
-                        boolean canUndo = (msg.arg1 > 0);
-                        boolean canRedo = (msg.arg2 > 0);
-                        enableButton(undo, canUndo);
-                        enableButton(redo, canRedo);
-                        enableButton(save, canUndo);
-                        break;
-                }
-            }
-        };
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        // Show action-bar title only when there's still room for it; otherwise, hide it.
+        // Show the action-bar title only when there's still room for it; otherwise, hide it.
         int width = 0;
         for (int i = 0; i < getChildCount(); i++) {
             width += getChildAt(i).getWidth();
@@ -84,67 +54,58 @@ public class ActionBar extends RelativeLayout implements FilterStack.StackListen
         findViewById(R.id.action_bar_title).setVisibility(((width > r - l)) ? INVISIBLE: VISIBLE);
     }
 
-    /**
-     * Initializes with a non-null ActionBarListener.
-     */
-    public void initialize(final ActionBarListener listener) {
-        undo = findViewById(R.id.undo_button);
-        undo.setOnClickListener(new OnClickListener() {
-
-            @Override
-            public void onClick(View v) {
-                if (isEnabled()) {
-                    listener.onUndo();
-                }
-            }
-        });
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
 
-        redo = findViewById(R.id.redo_button);
-        redo.setOnClickListener(new OnClickListener() {
+        enableButton(R.id.undo_button, false);
+        enableButton(R.id.redo_button, false);
+        enableButton(R.id.save_button, false);
+    }
 
-            @Override
-            public void onClick(View v) {
-                if (isEnabled()) {
-                    listener.onRedo();
-                }
-            }
-        });
+    /**
+     * Restores the passed action-bar.
+     *
+     * @return the passed parameter.
+     */
+    public ActionBar restore(ActionBar actionBar) {
+        // Restores by runnables and enabled status of buttons that have been changed.
+        for (Entry<Integer, Runnable> entry : buttonRunnables.entrySet()) {
+            actionBar.setRunnable(entry.getKey(), entry.getValue());
+        }
+        for (int buttonId : changedButtons) {
+            actionBar.enableButton(buttonId, isButtonEnabled(buttonId));
+        }
+        return actionBar;
+    }
 
-        save = findViewById(R.id.save_button);
-        save.setOnClickListener(new OnClickListener() {
+    public void setRunnable(int buttonId, final Runnable r) {
+        findViewById(buttonId).setOnClickListener(new OnClickListener() {
 
             @Override
             public void onClick(View v) {
                 if (isEnabled()) {
-                    listener.onSave();
+                    r.run();
                 }
             }
         });
-
-        resetButtons();
+        buttonRunnables.put(buttonId, r);
     }
 
-    public void resetButtons() {
-        // Disable buttons immediately instead of waiting for ENABLE_BUTTON messages which may
-        // happen some time later after stack changes.
-        enableButton(undo, false);
-        enableButton(redo, false);
-        enableButton(save, false);
+    public void clickButton(int buttonId) {
+        findViewById(buttonId).performClick();
     }
 
-    public void disableSave() {
-        enableButton(save, false);
+    public boolean isButtonEnabled(int buttonId) {
+        return findViewById(buttonId).isEnabled();
     }
 
-    private void enableButton(View button, boolean enabled) {
+    public void enableButton(int buttonId, boolean enabled) {
+        View button = findViewById(buttonId);
         button.setEnabled(enabled);
         button.setAlpha(enabled ? ENABLED_ALPHA : DISABLED_ALPHA);
-    }
 
-    @Override
-    public void onStackChanged(boolean canUndo, boolean canRedo) {
-        // Listens to stack changes that may come from the worker thread; send messages to enable
-        // buttons only in the UI thread.
-        handler.sendMessage(handler.obtainMessage(ENABLE_BUTTON, canUndo ? 1 : 0, canRedo ? 1 : 0));
+        // Track buttons whose enabled status has been updated.
+        changedButtons.add(buttonId);
     }
 }
index 3b35aa3..5b8d0d7 100644 (file)
@@ -113,7 +113,7 @@ public class FilterStack {
     }
 
     private void callbackDone(final OnDoneCallback callback) {
-        // Call back in UI thread to report done.
+        // GL thread calls back to report UI thread the task is done.
         photoView.post(new Runnable() {
 
             @Override
@@ -124,7 +124,16 @@ public class FilterStack {
     }
 
     private void stackChanged() {
-        stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty());
+        // GL thread calls back to report UI thread the stack is changed.
+        final boolean canUndo = !appliedStack.empty();
+        final boolean canRedo = !redoStack.empty();
+        photoView.post(new Runnable() {
+
+            @Override
+            public void run() {
+                stackListener.onStackChanged(canUndo, canRedo);
+            }
+        });
     }
 
     public void saveBitmap(final OnDoneBitmapCallback callback) {
index 914d50b..9ac85af 100644 (file)
@@ -35,7 +35,7 @@ public class LoadScreennailTask extends AsyncTask<Uri, Void, Bitmap> {
      */
     public interface Callback {
 
-        void onComplete(Bitmap bitmap);
+        void onComplete(Bitmap result);
     }
 
     private static final int SCREENNAIL_WIDTH = 1280;
@@ -61,12 +61,12 @@ public class LoadScreennailTask extends AsyncTask<Uri, Void, Bitmap> {
     }
 
     @Override
-    protected void onPostExecute(Bitmap bitmap) {
-        if (bitmap == null) {
+    protected void onPostExecute(Bitmap result) {
+        if (result == null) {
             Toast toast = Toast.makeText(context, R.string.loading_failure, Toast.LENGTH_SHORT);
             toast.setGravity(Gravity.CENTER, 0, 0);
             toast.show();
         }
-        callback.onComplete(bitmap);
+        callback.onComplete(result);
     }
 }
index c7ec48f..e36071e 100644 (file)
 package com.android.gallery3d.photoeditor;
 
 import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.gallery3d.R;
 
 /**
- * Main activity of the photo editor.
+ * Main activity of the photo editor that opens a photo and prepares tools for photo editing.
  */
 public class PhotoEditor extends Activity {
 
     private Uri uri;
     private FilterStack filterStack;
-    private Toolbar toolbar;
-    private View backButton;
+    private ActionBar actionBar;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -44,79 +45,167 @@ public class PhotoEditor extends Activity {
         Intent intent = getIntent();
         uri = Intent.ACTION_EDIT.equalsIgnoreCase(intent.getAction()) ? intent.getData() : null;
 
-        final ActionBar actionBar = (ActionBar) findViewById(R.id.action_bar);
-        filterStack = new FilterStack((PhotoView) findViewById(R.id.photo_view), actionBar);
-        toolbar = (Toolbar) findViewById(R.id.toolbar);
-        toolbar.initialize(filterStack);
+        actionBar = (ActionBar) findViewById(R.id.action_bar);
+        filterStack = new FilterStack((PhotoView) findViewById(R.id.photo_view),
+                new FilterStack.StackListener() {
 
-        final EffectsBar effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
-        backButton = findViewById(R.id.action_bar_back);
-        backButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onStackChanged(boolean canUndo, boolean canRedo) {
+                        actionBar.enableButton(R.id.undo_button, canUndo);
+                        actionBar.enableButton(R.id.redo_button, canRedo);
+                        actionBar.enableButton(R.id.save_button, canUndo);
+                    }
+        });
+
+        EffectsBar effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
+        effectsBar.initialize(filterStack);
+
+        actionBar.setRunnable(R.id.undo_button, createUndoRedoRunnable(true, effectsBar));
+        actionBar.setRunnable(R.id.redo_button, createUndoRedoRunnable(false, effectsBar));
+        actionBar.setRunnable(R.id.save_button, createSaveRunnable(effectsBar));
+        actionBar.setRunnable(R.id.action_bar_back, createBackRunnable(effectsBar));
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+        actionBar = actionBar.restore((ActionBar) recreateView(
+                actionBar, inflater, R.layout.photoeditor_actionbar));
+    }
+
+    /**
+     * Recreates the view by inflating the given layout id.
+     */
+    private View recreateView(View view, LayoutInflater inflater, int layoutId) {
+        ViewGroup parent = (ViewGroup) view.getParent();
+        int index = parent.indexOfChild(view);
+        parent.removeViewAt(index);
+
+        View recreated = inflater.inflate(layoutId, parent, false);
+        parent.addView(recreated, index);
+        return recreated;
+    }
+
+    private SpinnerProgressDialog createProgressDialog() {
+        return SpinnerProgressDialog.show((ViewGroup) findViewById(R.id.toolbar));
+    }
+
+    private void openPhoto() {
+        final SpinnerProgressDialog progressDialog = createProgressDialog();
+        LoadScreennailTask.Callback callback = new LoadScreennailTask.Callback() {
 
             @Override
-            public void onClick(View v) {
-                if (actionBar.isEnabled()) {
-                    // Exit effects or go back to the previous activity on pressing back button.
-                    if (!effectsBar.exit(null)) {
-                        tryRun(new Runnable() {
+            public void onComplete(final Bitmap result) {
+                filterStack.setPhotoSource(result, new OnDoneCallback() {
+
+                    @Override
+                    public void onDone() {
+                        progressDialog.dismiss();
+                    }
+                });
+            }
+        };
+        new LoadScreennailTask(this, callback).execute(uri);
+    }
+
+    private Runnable createUndoRedoRunnable(final boolean undo, final EffectsBar effectsBar) {
+        return new Runnable() {
+
+            @Override
+            public void run() {
+                effectsBar.exit(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        final SpinnerProgressDialog progressDialog = createProgressDialog();
+                        OnDoneCallback callback = new OnDoneCallback() {
 
                             @Override
-                            public void run() {
-                                finish();
+                            public void onDone() {
+                                progressDialog.dismiss();
+                            }
+                        };
+                        if (undo) {
+                            filterStack.undo(callback);
+                        } else {
+                            filterStack.redo(callback);
+                        }
+                    }
+                });
+            }
+        };
+    }
+
+    private Runnable createSaveRunnable(final EffectsBar effectsBar) {
+        return new Runnable() {
+
+            @Override
+            public void run() {
+                effectsBar.exit(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        final SpinnerProgressDialog progressDialog = createProgressDialog();
+                        filterStack.saveBitmap(new OnDoneBitmapCallback() {
+
+                            @Override
+                            public void onDone(Bitmap bitmap) {
+                                SaveCopyTask.Callback callback = new SaveCopyTask.Callback() {
+
+                                    @Override
+                                    public void onComplete(Uri result) {
+                                        progressDialog.dismiss();
+                                        actionBar.enableButton(R.id.save_button, (result == null));
+                                    }
+                                };
+                                new SaveCopyTask(PhotoEditor.this, uri, callback).execute(bitmap);
                             }
                         });
                     }
-                }
+                });
             }
-        });
+        };
     }
 
-    private void tryRun(final Runnable runnable) {
-        if (findViewById(R.id.save_button).isEnabled()) {
-            // Pop-up a dialog before executing the runnable to save unsaved photo.
-            AlertDialog.Builder builder = new AlertDialog.Builder(this)
-                    .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
-
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            toolbar.savePhoto(new Runnable() {
-
-                                @Override
-                                public void run() {
-                                    runnable.run();
-                                }
-                            });
-                        }
-                    })
-                    .setNeutralButton(R.string.no, new DialogInterface.OnClickListener() {
+    private Runnable createBackRunnable(final EffectsBar effectsBar) {
+        return new Runnable() {
 
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            runnable.run();
-                        }
-                    })
-                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void run() {
+                // Exit effects or go back to the previous activity on pressing back button.
+                if (!effectsBar.exit(null)) {
+                    // Pop-up a dialog to save unsaved photo.
+                    if (actionBar.isButtonEnabled(R.id.save_button)) {
+                        new YesNoCancelDialogBuilder(PhotoEditor.this, new Runnable() {
 
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            // no-op
-                        }
-                    });
-            builder.setMessage(R.string.save_photo).show();
-            return;
-        }
+                            @Override
+                            public void run() {
+                                actionBar.clickButton(R.id.save_button);
+                            }
+                        }, new Runnable() {
 
-        runnable.run();
+                            @Override
+                            public void run() {
+                                finish();
+                            }
+                        }, R.string.save_photo).show();
+                    } else {
+                        finish();
+                    }
+                }
+            }
+        };
     }
 
     @Override
     public void onBackPressed() {
-        backButton.performClick();
+        actionBar.clickButton(R.id.action_bar_back);
     }
 
     @Override
     protected void onPause() {
-        // TODO: Close running spinner progress dialogs as all pending operations will be paused.
+        // TODO: Close running progress dialogs as all pending operations will be paused.
         super.onPause();
         filterStack.onPause();
     }
@@ -125,6 +214,6 @@ public class PhotoEditor extends Activity {
     protected void onResume() {
         super.onResume();
         filterStack.onResume();
-        toolbar.openPhoto(uri);
+        openPhoto();
     }
 }
index d467563..bedd416 100644 (file)
@@ -45,7 +45,7 @@ public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
      */
     public interface Callback {
 
-        void onComplete(Uri uri);
+        void onComplete(Uri result);
     }
 
     private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
index 72f243b..45ec016 100644 (file)
 package com.android.gallery3d.photoeditor;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
 import android.widget.RelativeLayout;
 
 import com.android.gallery3d.R;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
- * Toolbar that contains all tools and handles all operations for editing photo.
+ * Toolbar that contains all tools and controls their idle/awake behaviors from UI thread.
  */
 public class Toolbar extends RelativeLayout {
 
     private final ToolbarIdleHandler idleHandler;
-    private FilterStack filterStack;
-    private EffectsBar effectsBar;
-    private ActionBar actionBar;
-    private Uri sourceUri;
 
     public Toolbar(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         idleHandler = new ToolbarIdleHandler(context);
         setOnHierarchyChangeListener(idleHandler);
+        idleHandler.killIdle();
     }
 
     @Override
@@ -49,111 +52,61 @@ public class Toolbar extends RelativeLayout {
         return super.dispatchTouchEvent(ev);
     }
 
-    public void initialize(FilterStack filterStack) {
-        this.filterStack = filterStack;
-        effectsBar = (EffectsBar) findViewById(R.id.effects_bar);
-        effectsBar.initialize(filterStack);
-        actionBar = (ActionBar) findViewById(R.id.action_bar);
-        actionBar.initialize(createActionBarListener());
-        idleHandler.killIdle();
-    }
-
-    private ActionBar.ActionBarListener createActionBarListener() {
-        actionBar = (ActionBar) findViewById(R.id.action_bar);
-        return new ActionBar.ActionBarListener() {
-
-            @Override
-            public void onUndo() {
-                effectsBar.exit(new Runnable() {
-
-                    @Override
-                    public void run() {
-                        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(
-                                Toolbar.this);
-                        filterStack.undo(new OnDoneCallback() {
-
-                            @Override
-                            public void onDone() {
-                                progressDialog.dismiss();
+    private static class ToolbarIdleHandler implements OnHierarchyChangeListener {
+
+        private static final int MAKE_IDLE = 1;
+        private static final int TIMEOUT_IDLE = 8000;
+
+        private final List<View> childViews = new ArrayList<View>();
+        private final Handler mainHandler;
+        private final Animation fadeIn;
+        private final Animation fadeOut;
+        private boolean idle;
+
+        public ToolbarIdleHandler(Context context) {
+            mainHandler = new Handler() {
+
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case MAKE_IDLE:
+                            if (!idle) {
+                                idle = true;
+                                for (View view : childViews) {
+                                    view.startAnimation(fadeOut);
+                                }
                             }
-                        });
+                            break;
                     }
-                });
+                }
+            };
+
+            fadeIn = AnimationUtils.loadAnimation(context, R.anim.photoeditor_fade_in);
+            fadeOut = AnimationUtils.loadAnimation(context, R.anim.photoeditor_fade_out);
+        }
+
+        public void killIdle() {
+            mainHandler.removeMessages(MAKE_IDLE);
+            if (idle) {
+                idle = false;
+                for (View view : childViews) {
+                    view.startAnimation(fadeIn);
+                }
             }
-
-            @Override
-            public void onRedo() {
-                effectsBar.exit(new Runnable() {
-
-                    @Override
-                    public void run() {
-                        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(
-                                Toolbar.this);
-                        filterStack.redo(new OnDoneCallback() {
-
-                            @Override
-                            public void onDone() {
-                                progressDialog.dismiss();
-                            }
-                        });
-                    }
-                });
+            mainHandler.sendEmptyMessageDelayed(MAKE_IDLE, TIMEOUT_IDLE);
+        }
+
+        @Override
+        public void onChildViewAdded(View parent, View child) {
+            // All child views, except photo-view, will fade out on inactivity timeout.
+            if (child.getId() != R.id.photo_view) {
+                childViews.add(child);
             }
+        }
 
-            @Override
-            public void onSave() {
-                effectsBar.exit(new Runnable() {
-
-                    @Override
-                    public void run() {
-                        savePhoto(null);
-                    }
-                });
-            }
-        };
-    }
-
-    public void openPhoto(Uri uri) {
-        sourceUri = uri;
-
-        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(this);
-        new LoadScreennailTask(getContext(), new LoadScreennailTask.Callback() {
-
-            @Override
-            public void onComplete(final Bitmap bitmap) {
-                filterStack.setPhotoSource(bitmap, new OnDoneCallback() {
-
-                    @Override
-                    public void onDone() {
-                        progressDialog.dismiss();
-                    }
-                });
-            }
-        }).execute(sourceUri);
-    }
-
-    /**
-     * Saves photo and executes runnable (if provided) after saving done.
-     */
-    public void savePhoto(final Runnable runnable) {
-        final SpinnerProgressDialog progressDialog = SpinnerProgressDialog.show(this);
-        filterStack.saveBitmap(new OnDoneBitmapCallback() {
-
-            @Override
-            public void onDone(Bitmap bitmap) {
-                new SaveCopyTask(getContext(), sourceUri, new SaveCopyTask.Callback() {
-
-                    @Override
-                    public void onComplete(Uri uri) {
-                        // TODO: Handle saving failure.
-                        progressDialog.dismiss();
-                        actionBar.disableSave();
-                        if (runnable != null) {
-                            runnable.run();
-                        }
-                    }
-                }).execute(bitmap);
-            }
-        });
+        @Override
+        public void onChildViewRemoved(View parent, View child) {
+            childViews.remove(child);
+        }
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/ToolbarIdleHandler.java b/src/com/android/gallery3d/photoeditor/ToolbarIdleHandler.java
deleted file mode 100644 (file)
index 5cef566..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.photoeditor;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.view.View;
-import android.view.ViewGroup.OnHierarchyChangeListener;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-
-import com.android.gallery3d.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Handler that controls idle/awake behaviors of toolbar's child views from UI thread.
- */
-class ToolbarIdleHandler implements OnHierarchyChangeListener {
-
-    private static final int MAKE_IDLE = 1;
-    private static final int TIMEOUT_IDLE = 8000;
-
-    private final List<View> childViews = new ArrayList<View>();
-    private final Handler mainHandler;
-    private final Animation fadeIn;
-    private final Animation fadeOut;
-    private boolean idle;
-
-    public ToolbarIdleHandler(Context context) {
-        mainHandler = new Handler() {
-
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case MAKE_IDLE:
-                        if (!idle) {
-                            idle = true;
-                            for (View view : childViews) {
-                                view.startAnimation(fadeOut);
-                            }
-                        }
-                        break;
-                }
-            }
-        };
-
-        fadeIn = AnimationUtils.loadAnimation(context, R.anim.photoeditor_fade_in);
-        fadeOut = AnimationUtils.loadAnimation(context, R.anim.photoeditor_fade_out);
-    }
-
-    public void killIdle() {
-        mainHandler.removeMessages(MAKE_IDLE);
-        if (idle) {
-            idle = false;
-            for (View view : childViews) {
-                view.startAnimation(fadeIn);
-            }
-        }
-        mainHandler.sendEmptyMessageDelayed(MAKE_IDLE, TIMEOUT_IDLE);
-    }
-
-    @Override
-    public void onChildViewAdded(View parent, View child) {
-        // All child views, except photo-view, will fade out on inactivity timeout.
-        if (child.getId() != R.id.photo_view) {
-            childViews.add(child);
-        }
-    }
-
-    @Override
-    public void onChildViewRemoved(View parent, View child) {
-        childViews.remove(child);
-    }
-}
diff --git a/src/com/android/gallery3d/photoeditor/YesNoCancelDialogBuilder.java b/src/com/android/gallery3d/photoeditor/YesNoCancelDialogBuilder.java
new file mode 100644 (file)
index 0000000..fcc99c0
--- /dev/null
@@ -0,0 +1,39 @@
+package com.android.gallery3d.photoeditor;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+import com.android.gallery3d.R;
+
+/**
+ * Alert dialog builder that builds a simple Yes/No/Cancel dialog.
+ */
+public class YesNoCancelDialogBuilder extends AlertDialog.Builder {
+
+    public YesNoCancelDialogBuilder(Context context, final Runnable yes, final Runnable no,
+            int messageId) {
+        super(context);
+        setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                yes.run();
+            }
+        })
+        .setNeutralButton(R.string.no, new DialogInterface.OnClickListener() {
+
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                no.run();
+            }
+        })
+        .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // no-op
+            }
+        }).setMessage(messageId);
+    }
+}