Multiple improvements and fixes to the mobile web app.  Tasks are now stored in local storage using the TaskProxyLocalStorage class, which is also used when refreshing a task offline in the TaskReadView.  I renamed the app to Cloud Tasks, and added a favicon and homepage icon.  Also fixed a bug where the "add task" button doesn't show up in the tablet view after editing a task.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10173 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactory.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactory.java
index f66c5c0..47da8f5 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactory.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactory.java
@@ -86,6 +86,11 @@
   TaskListView getTaskListView();
 
   /**
+   * Get the {@link TaskProxyLocalStorage} that stores tasks.
+   */
+  TaskProxyLocalStorage getTaskProxyLocalStorage();
+
+  /**
    * Get an implementation of {@link TaskEditView}.
    */
   TaskReadView getTaskReadView();
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImpl.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImpl.java
index 4dc6e9f..ec18f04 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImpl.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImpl.java
@@ -54,6 +54,7 @@
   private final MobileWebAppRequestFactory requestFactory;
   private MobileWebAppShell shell;
   private final Storage localStorage;
+  private final TaskProxyLocalStorage taskProxyLocalStorage;
   private TaskEditView taskEditView;
   private TaskListView taskListView;
   private final ActivityManager activityManager;
@@ -80,6 +81,7 @@
     } else {
       localStorage = null;
     }
+    taskProxyLocalStorage = new TaskProxyLocalStorage(localStorage);
 
     /*
      * ActivityMapper determines an Activity to run for a particular place.
@@ -137,6 +139,10 @@
     return taskListView;
   }
 
+  public TaskProxyLocalStorage getTaskProxyLocalStorage() {
+    return taskProxyLocalStorage;
+  }
+
   public TaskReadView getTaskReadView() {
     if (taskReadView == null) {
       taskReadView = createTaskReadView();
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImplMobile.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImplMobile.java
index b235b67..f593c09 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImplMobile.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/ClientFactoryImplMobile.java
@@ -34,7 +34,7 @@
   @Override
   protected MobileWebAppShell createShell() {
     return new MobileWebAppShellMobile(orientationHelper, getTaskListView(),
-        getTaskEditView(), getTaskReadView());
+        getTaskEditView(), getTaskReadView(), getPlaceController());
   }
 
   @Override
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/TaskProxyLocalStorage.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/TaskProxyLocalStorage.java
new file mode 100644
index 0000000..f3e8f73
--- /dev/null
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/TaskProxyLocalStorage.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * 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.google.gwt.sample.mobilewebapp.client;
+
+import com.google.gwt.sample.mobilewebapp.shared.TaskProxy;
+import com.google.gwt.sample.mobilewebapp.shared.TaskProxyImpl;
+import com.google.gwt.storage.client.Storage;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages the storage and retrieval of local tasks.
+ */
+public class TaskProxyLocalStorage {
+
+  private static final String TASKLIST_SAVE_KEY = "TASKLIST";
+  private static final String TASKSEP = "&&";
+  private static final String FIELDSEP = "@@";
+  private static final String FIELDEMPTY = "***";
+
+  /**
+   * Convert a task proxy list into a string.
+   */
+  private static String getStringFromTaskProxy(List<TaskProxy> list) {
+    StringBuilder sb = new StringBuilder();
+    for (TaskProxy proxy : list) {
+      sb.append(proxy.getDueDate() != null ? proxy.getDueDate().getTime() : FIELDEMPTY);
+      sb.append(FIELDSEP);
+      sb.append(proxy.getId() != null ? proxy.getId() : "");
+      sb.append(FIELDSEP);
+      String name = proxy.getName();
+      sb.append(name != null && name.length() > 0 ? proxy.getName() : FIELDEMPTY);
+      sb.append(FIELDSEP);
+      String notes = proxy.getNotes();
+      sb.append(notes != null && notes.length() > 0 ? proxy.getNotes() : FIELDEMPTY);
+      sb.append(TASKSEP);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Parse a task proxy list from a string.
+   */
+  private static List<TaskProxy> getTaskProxyFromString(String taskProxyList) {
+    ArrayList<TaskProxy> list = new ArrayList<TaskProxy>(0);
+    if (taskProxyList == null) {
+      return list;
+    }
+    // taskproxy1&&taskproxy2&&taskproxy3&&...
+    String taskProxyStrings[] = taskProxyList.split(TASKSEP);
+    for (String taskProxyString : taskProxyStrings) {
+      if (taskProxyString == null) {
+        continue;
+      }
+      // date@@id@@name@@notes
+      String taskProxyStringData[] = taskProxyString.split(FIELDSEP);
+      if (taskProxyStringData.length >= 4) {
+        // collect the fields
+        String dateString = taskProxyStringData[0];
+        String idString = taskProxyStringData[1];
+        String nameString = taskProxyStringData[2];
+        if (FIELDEMPTY.equals(nameString)) {
+          nameString = null;
+        }
+        String notesString = taskProxyStringData[3];
+        if (FIELDEMPTY.equals(notesString)) {
+          notesString = null;
+        }
+        // parse the numerical fields
+        Date dueDate = null;
+        try {
+          dueDate = new Date(Long.parseLong(dateString));
+        } catch (NumberFormatException nfe) {
+        }
+        Long idLong = 0L;
+        try {
+          idLong = Long.parseLong(idString);
+        } catch (NumberFormatException nfe) {
+        }
+        // create and populate the TaskProxy
+        TaskProxyImpl taskProxy = new TaskProxyImpl();
+        taskProxy.setDueDate(dueDate);
+        taskProxy.setId(idLong);
+        taskProxy.setName(nameString);
+        taskProxy.setNotes(notesString);
+        list.add(taskProxy);
+      }
+    }
+    return list;
+  }
+
+  private final Storage storage;
+  private List<TaskProxy> tasks;
+  private Map<Long, TaskProxy> taskMap;
+
+  public TaskProxyLocalStorage(Storage storage) {
+    this.storage = storage;
+  }
+
+  /**
+   * Get a task by its ID.
+   * 
+   * @param id the task id
+   * @return the task, or null if it isn't in local storage
+   */
+  public TaskProxy getTask(Long id) {
+    // Create the map of tasks.
+    if (taskMap == null) {
+      taskMap = new HashMap<Long, TaskProxy>();
+      for (TaskProxy task : getTasks()) {
+        taskMap.put(task.getId(), task);
+      }
+    }
+
+    return taskMap.get(id);
+  }
+
+  /**
+   * Get a list of all tasks in local storage.
+   */
+  public List<TaskProxy> getTasks() {
+    if (tasks == null) {
+      // Load the saved task list from storage
+      if (storage != null) { // if storage is supported
+        String taskString = storage.getItem(TASKLIST_SAVE_KEY);
+        tasks = getTaskProxyFromString(taskString);
+      } else {
+        tasks = new ArrayList<TaskProxy>();
+      }
+    }
+
+    return tasks;
+  }
+
+  /**
+   * Save a list of tasks to local storage.
+   */
+  public void setTasks(List<TaskProxy> tasks) {
+    this.tasks = tasks;
+
+    // Save the response to storage
+    if (storage != null) { // if storage is supported
+      String responseString = getStringFromTaskProxy(tasks);
+      storage.setItem(TASKLIST_SAVE_KEY, responseString);
+    }
+  }
+}
\ No newline at end of file
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskEditActivity.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskEditActivity.java
index 50ce3d6..93e32d5 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskEditActivity.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskEditActivity.java
@@ -98,6 +98,47 @@
   }
 
   @Override
+  public void editTask() {
+    // Load the existing task.
+    final TaskEditView editView = clientFactory.getTaskEditView();
+  
+    if (taskId == null) {
+      isEditing = false;
+      editView.setEditing(false);
+      TaskRequest request = clientFactory.getRequestFactory().taskRequest();
+      editTask = request.create(TaskProxy.class);
+      editView.getEditorDriver().edit(editTask, request);
+    } else {
+      editView.setLocked(true);
+      clientFactory.getRequestFactory().taskRequest().findTask(this.taskId).fire(
+          new Receiver<TaskProxy>() {
+            @Override
+            public void onSuccess(TaskProxy response) {
+              // Early exit if this activity has already been cancelled.
+              if (isDead) {
+                return;
+              }
+  
+              // Task not found.
+              if (response == null) {
+                Window.alert("The task with id '" + taskId + "' could not be found."
+                    + " Please select a different task from the task list.");
+                doCancelTask();
+                return;
+              }
+  
+              // Show the task.
+              editTask = response;
+              editView.getEditorDriver().edit(response,
+                  clientFactory.getRequestFactory().taskRequest());
+              editView.setLocked(false);
+            }
+          });
+    }
+    container.setWidget(editView);
+  }
+
+  @Override
   public void onCancel() {
     // Ignore all incoming responses to the requests from this activity.
     isDead = true;
@@ -165,6 +206,11 @@
       isEditing = true;
       editView.setEditing(true);
 
+      // Try to load the task from local storage.
+      if (readOnlyTask == null) {
+        readOnlyTask = clientFactory.getTaskProxyLocalStorage().getTask(taskId);
+      }
+
       if (readOnlyTask == null) {
         // Load the existing task.
         clientFactory.getRequestFactory().taskRequest().findTask(this.taskId).fire(
@@ -262,45 +308,4 @@
     // Return to the task list.
     clientFactory.getPlaceController().goTo(new TaskListPlace(true));
   }
-
-  @Override
-  public void editTask() {
-    // Load the existing task.
-    final TaskEditView editView = clientFactory.getTaskEditView();
-
-    if (taskId == null) {
-      isEditing = false;
-      editView.setEditing(false);
-      TaskRequest request = clientFactory.getRequestFactory().taskRequest();
-      editTask = request.create(TaskProxy.class);
-      editView.getEditorDriver().edit(editTask, request);
-    } else {
-      editView.setLocked(true);
-      clientFactory.getRequestFactory().taskRequest().findTask(this.taskId).fire(
-          new Receiver<TaskProxy>() {
-            @Override
-            public void onSuccess(TaskProxy response) {
-              // Early exit if this activity has already been cancelled.
-              if (isDead) {
-                return;
-              }
-
-              // Task not found.
-              if (response == null) {
-                Window.alert("The task with id '" + taskId + "' could not be found."
-                    + " Please select a different task from the task list.");
-                doCancelTask();
-                return;
-              }
-
-              // Show the task.
-              editTask = response;
-              editView.getEditorDriver().edit(response,
-                  clientFactory.getRequestFactory().taskRequest());
-              editView.setLocked(false);
-            }
-          });
-    }
-    container.setWidget(editView);
-  }
 }
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskListActivity.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskListActivity.java
index 039b48f..ebd9af0 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskListActivity.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskListActivity.java
@@ -25,16 +25,13 @@
 import com.google.gwt.sample.mobilewebapp.client.place.TaskEditPlace;
 import com.google.gwt.sample.mobilewebapp.client.place.TaskListPlace;
 import com.google.gwt.sample.mobilewebapp.shared.TaskProxy;
-import com.google.gwt.sample.mobilewebapp.shared.TaskProxyImpl;
 import com.google.gwt.storage.client.Storage;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.AcceptsOneWidget;
 import com.google.web.bindery.requestfactory.shared.Receiver;
 import com.google.web.bindery.requestfactory.shared.ServerFailure;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -96,86 +93,19 @@
     void onTaskListUpdated(TaskListUpdateEvent event);
   }
 
-  private static final String TASKLIST_SAVE_KEY = "TASKLIST";
-  private static final String TASKSEP = "&&";
-  private static final String FIELDSEP = "@@";
-  private static final String FIELDEMPTY = "***";
-
   /**
    * The delay in milliseconds between calls to refresh the task list.
    */
   private static final int REFRESH_DELAY = 5000;
 
   /**
-   * Convert a task proxy list into a string.
+   * The handler that handlers add button clicks.
    */
-  private static String getStringFromTaskProxy(List<TaskProxy> list) {
-    StringBuilder sb = new StringBuilder();
-    for (TaskProxy proxy : list) {
-      sb.append(proxy.getDueDate() != null ? proxy.getDueDate().getTime() : FIELDEMPTY);
-      sb.append(FIELDSEP);
-      sb.append(proxy.getId() != null ? proxy.getId() : "");
-      sb.append(FIELDSEP);
-      String name = proxy.getName();
-      sb.append(name != null && name.length() > 0 ? proxy.getName() : FIELDEMPTY);
-      sb.append(FIELDSEP);
-      String notes = proxy.getNotes();
-      sb.append(notes != null && notes.length() > 0 ? proxy.getNotes() : FIELDEMPTY);
-      sb.append(TASKSEP);
+  private final ClickHandler addButtonHandler = new ClickHandler() {
+    public void onClick(ClickEvent event) {
+      clientFactory.getPlaceController().goTo(TaskEditPlace.getTaskCreatePlace());
     }
-    return sb.toString();
-  }
-
-  /**
-   * Parse a task proxy list from a string.
-   */
-  private static List<TaskProxy> getTaskProxyFromString(String taskProxyList) {
-    ArrayList<TaskProxy> list = new ArrayList<TaskProxy>(0);
-    if (taskProxyList == null) {
-      return list;
-    }
-    // taskproxy1&&taskproxy2&&taskproxy3&&...
-    String taskProxyStrings[] = taskProxyList.split(TASKSEP);
-    for (String taskProxyString : taskProxyStrings) {
-      if (taskProxyString == null) {
-        continue;
-      }
-      // date@@id@@name@@notes
-      String taskProxyStringData[] = taskProxyString.split(FIELDSEP);
-      if (taskProxyStringData.length >= 4) {
-        // collect the fields
-        String dateString = taskProxyStringData[0];
-        String idString = taskProxyStringData[1];
-        String nameString = taskProxyStringData[2];
-        if (FIELDEMPTY.equals(nameString)) {
-          nameString = null;
-        }
-        String notesString = taskProxyStringData[3];
-        if (FIELDEMPTY.equals(notesString)) {
-          notesString = null;
-        }
-        // parse the numerical fields
-        Date dueDate = null;
-        try {
-          dueDate = new Date(Long.parseLong(dateString));
-        } catch (NumberFormatException nfe) {
-        }
-        Long idLong = 0L;
-        try {
-          idLong = Long.parseLong(idString);
-        } catch (NumberFormatException nfe) {
-        }
-        // create and populate the TaskProxy
-        TaskProxyImpl taskProxy = new TaskProxyImpl();
-        taskProxy.setDueDate(dueDate);
-        taskProxy.setId(idLong);
-        taskProxy.setName(nameString);
-        taskProxy.setNotes(notesString);
-        list.add(taskProxy);
-      }
-    }
-    return list;
-  }
+  };
 
   private final Storage storage;
 
@@ -214,6 +144,10 @@
     this.storage = clientFactory.getLocalStorageIfSupported();
   }
 
+  public ClickHandler getAddButtonHandler() {
+    return addButtonHandler;
+  }
+
   @Override
   public void onCancel() {
     killActivity();
@@ -232,11 +166,7 @@
 
   public void start(AcceptsOneWidget container, EventBus eventBus) {
     // Add a handler to the 'add' button in the shell.
-    clientFactory.getShell().setAddButtonHandler(new ClickHandler() {
-      public void onClick(ClickEvent event) {
-        clientFactory.getPlaceController().goTo(TaskEditPlace.getTaskCreatePlace());
-      }
-    });
+    clientFactory.getShell().setAddButtonHandler(addButtonHandler);
 
     // Set the presenter on the view.
     final TaskListView view = clientFactory.getTaskListView();
@@ -257,13 +187,8 @@
     };
 
     // Load the saved task list from storage
-    if (storage != null) { // if storage is supported
-      String taskString = storage.getItem(TASKLIST_SAVE_KEY);
-      if (taskString != null) {
-        List<TaskProxy> list = getTaskProxyFromString(taskString);
-        setTasks(list);
-      }
-    }
+    List<TaskProxy> list = clientFactory.getTaskProxyLocalStorage().getTasks();
+    setTasks(list);
 
     // Request the task list now.
     refreshTaskList();
@@ -302,16 +227,12 @@
 
             // Display the tasks in the view.
             if (response == null) {
-              setTasks(Collections.<TaskProxy> emptyList());
-            } else {
-              setTasks(response);
-
-              // save the response to storage
-              if (storage != null) { // if storage is supported
-                String responseString = getStringFromTaskProxy(response);
-                storage.setItem(TASKLIST_SAVE_KEY, responseString);
-              }
+              response = Collections.<TaskProxy> emptyList();
             }
+            setTasks(response);
+
+            // save the response to storage
+            clientFactory.getTaskProxyLocalStorage().setTasks(response);
 
             // Restart the timer.
             refreshTimer.schedule(REFRESH_DELAY);
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskReadView.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskReadView.java
index 2553774..d6c6a66 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskReadView.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/activity/TaskReadView.java
@@ -20,6 +20,9 @@
 import com.google.gwt.sample.mobilewebapp.shared.TaskProxy;
 import com.google.gwt.user.client.ui.IsWidget;
 
+/**
+ * A readonly view of a task.
+ */
 public interface TaskReadView extends IsWidget, Editor<TaskProxy> {
 
   /**
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.java
index 0d92b32..403049b 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.java
@@ -27,7 +27,6 @@
 import com.google.gwt.place.shared.PlaceChangeEvent;
 import com.google.gwt.place.shared.PlaceController;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
-import com.google.gwt.sample.mobilewebapp.client.ClientFactory;
 import com.google.gwt.sample.mobilewebapp.client.MobileWebAppShell;
 import com.google.gwt.sample.mobilewebapp.client.activity.TaskEditView;
 import com.google.gwt.sample.mobilewebapp.client.activity.TaskListActivity;
@@ -234,7 +233,7 @@
     selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
       public void onSelectionChange(SelectionChangeEvent event) {
         MainMenuItem selected = selectionModel.getSelectedObject();
-        if (selected != null) {
+        if (selected != null && !selected.mapsToPlace(placeController.getWhere())) {
           placeController.goTo(selected.getPlace());
         }
       }
@@ -437,10 +436,18 @@
 
     // Update the pie chart.
     pieChart.clearSlices();
-    pieChart.addSlice(pastDue, CssColor.make(255, 100, 100));
-    pieChart.addSlice(dueSoon, CssColor.make(255, 200, 100));
-    pieChart.addSlice(onTime, CssColor.make(100, 255, 100));
-    pieChart.addSlice(noDate, CssColor.make(200, 200, 200));
+    if (pastDue > 0) {
+      pieChart.addSlice(pastDue, CssColor.make(255, 100, 100));
+    }
+    if (dueSoon > 0) {
+      pieChart.addSlice(dueSoon, CssColor.make(255, 200, 100));
+    }
+    if (onTime > 0) {
+      pieChart.addSlice(onTime, CssColor.make(100, 255, 100));
+    }
+    if (noDate > 0) {
+      pieChart.addSlice(noDate, CssColor.make(200, 200, 200));
+    }
     pieChart.redraw();
   }
 }
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.ui.xml b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.ui.xml
index 401920e..1c89f95 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.ui.xml
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/desktop/MobileWebAppShellDesktop.ui.xml
@@ -63,11 +63,11 @@
           <tr>
             <td>
               <div
-                class="{style.headerWord}">Task</div>
+                class="{style.headerWord}">Cloud</div>
             </td>
             <td>
               <div
-                class="{style.headerWord}">Manager</div>
+                class="{style.headerWord}">Tasks</div>
             </td>
           </tr>
         </table>
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.java
index 281e2a6..f46f412 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.java
@@ -19,13 +19,15 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.sample.mobilewebapp.client.ClientFactory;
+import com.google.gwt.place.shared.PlaceController;
 import com.google.gwt.sample.mobilewebapp.client.MobileWebAppShell;
 import com.google.gwt.sample.mobilewebapp.client.activity.TaskEditView;
 import com.google.gwt.sample.mobilewebapp.client.activity.TaskListView;
 import com.google.gwt.sample.mobilewebapp.client.activity.TaskReadView;
+import com.google.gwt.sample.mobilewebapp.client.place.TaskListPlace;
 import com.google.gwt.sample.mobilewebapp.client.ui.OrientationHelper;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
@@ -106,11 +108,9 @@
 
   /**
    * Construct a new {@link MobileWebAppShellMobile}.
-   * 
-   * @param clientFactory the {@link ClientFactory} of shared resources
    */
   public MobileWebAppShellMobile(OrientationHelper orientationHelper, TaskListView taskListView,
-      TaskEditView taskEditView, TaskReadView taskReadView) {
+      TaskEditView taskEditView, TaskReadView taskReadView, final PlaceController placeController) {
 
     initWidget(uiBinder.createAndBindUi(this));
 
@@ -140,6 +140,14 @@
         onShiftToLandscape();
       }
     });
+
+    // Return the the task list place when the title is clicked.
+    titleBar.addDomHandler(new ClickHandler() {      
+      @Override
+      public void onClick(ClickEvent event) {
+        placeController.goTo(new TaskListPlace(false));
+      }
+    }, ClickEvent.getType());
   }
 
   /**
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.ui.xml b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.ui.xml
index f25a9b3..dc59a6a 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.ui.xml
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/mobile/MobileWebAppShellMobile.ui.xml
@@ -41,7 +41,7 @@
         ui:field='titleBar'
         addStyleNames="{style.header}">
         <div
-          ui:field='titleElem'>Task List</div>
+          ui:field='titleElem'>Cloud Tasks</div>
       </g:HTMLPanel>
     </g:layer>
 
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.java b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.java
index 4827eee..de685d5 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.java
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.java
@@ -16,11 +16,11 @@
 package com.google.gwt.sample.mobilewebapp.client.tablet;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.place.shared.PlaceController;
-import com.google.gwt.sample.mobilewebapp.client.ClientFactory;
 import com.google.gwt.sample.mobilewebapp.client.MobileWebAppShell;
 import com.google.gwt.sample.mobilewebapp.client.Provider;
 import com.google.gwt.sample.mobilewebapp.client.activity.TaskListActivity;
@@ -34,6 +34,7 @@
 import com.google.gwt.user.client.ui.DeckLayoutPanel;
 import com.google.gwt.user.client.ui.DockLayoutPanel;
 import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.ResizeComposite;
 import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.Widget;
@@ -85,6 +86,12 @@
   SimplePanel taskListContainer;
 
   /**
+   * The label containing the application title.
+   */
+  @UiField
+  Label titleLabel;
+
+  /**
    * A reference to the handler for the add button.
    */
   private HandlerRegistration addButtonHandler;
@@ -113,7 +120,7 @@
    * @param clientFactory the {@link ClientFactory} of shared resources
    */
   public MobileWebAppShellTablet(EventBus bus, OrientationHelper orientationHelper,
-      PlaceController placeController, Provider<TaskListActivity> taskListActivityProvider,
+      final PlaceController placeController, Provider<TaskListActivity> taskListActivityProvider,
       TaskListView taskListView) {
     this.bus = bus;
 
@@ -144,6 +151,14 @@
         onShiftToLandscape();
       }
     });
+
+    // Go to the task list place when the title is clicked.
+    titleLabel.addClickHandler(new ClickHandler() {      
+      @Override
+      public void onClick(ClickEvent event) {
+        placeController.goTo(new TaskListPlace(false));
+      }
+    });
   }
 
   /**
@@ -177,6 +192,11 @@
   public void setWidget(IsWidget content) {
     contentContainer.setWidget((content == null) ? contentEmptyMessage : content);
 
+    // If the content is null and we are in landscape mode, show the add button.
+    if (content == null && taskListActivity != null) {
+      setAddButtonHandler(taskListActivity.getAddButtonHandler());
+    }
+
     // Do not animate the first time we show a widget.
     if (firstContentWidget) {
       firstContentWidget = false;
diff --git a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.ui.xml b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.ui.xml
index a29c961..b15b9dc 100644
--- a/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.ui.xml
+++ b/samples/mobilewebapp/src/main/com/google/gwt/sample/mobilewebapp/client/tablet/MobileWebAppShellTablet.ui.xml
@@ -59,8 +59,9 @@
               align="right"
               valign="middle">
               <g:Label
+                ui:field="titleLabel"
                 addStyleNames="{style.headerText}">
-                App Name
+                Cloud Tasks
               </g:Label>
             </td>
           </tr>
diff --git a/samples/mobilewebapp/war/MobileWebApp.html b/samples/mobilewebapp/war/MobileWebApp.html
index d7efd8c..1d33a65 100644
--- a/samples/mobilewebapp/war/MobileWebApp.html
+++ b/samples/mobilewebapp/war/MobileWebApp.html
@@ -22,10 +22,15 @@
     <!--                                                               -->
     <link type="text/css" rel="stylesheet" href="MobileWebApp.css">
 
-    <!--                                           -->
-    <!-- Any title is fine                         -->
-    <!--                                           -->
-    <title>Web Application Starter Project</title>
+    <!-- Specify the shortcut icon. -->
+    <link rel="shortcut icon" href="favicon.ico" />
+
+    <!-- Specify the bookmark icon used for homepage shortcuts. -->
+    <link rel="apple-touch-icon" href="app_icon.png"/>
+    <link rel="apple-touch-icon-precomposed" href="app_icon.png"/>
+
+    <!-- Specify the title in the browser tab. -->
+    <title>Cloud Tasks</title>
     
     <!--                                           -->
     <!-- This script loads your compiled module.   -->
diff --git a/samples/mobilewebapp/war/app_icon.png b/samples/mobilewebapp/war/app_icon.png
new file mode 100644
index 0000000..a4fa692
--- /dev/null
+++ b/samples/mobilewebapp/war/app_icon.png
Binary files differ
diff --git a/samples/mobilewebapp/war/favicon.ico b/samples/mobilewebapp/war/favicon.ico
new file mode 100644
index 0000000..9b6e9fd
--- /dev/null
+++ b/samples/mobilewebapp/war/favicon.ico
Binary files differ