Adding more ActivityController dependencies
ui.ControllableActivity added from Gmail.
One pane layout files brought in.
Minor changes to make everything work with the test activity.
Change-Id: I8cacd5f36394fe4a786590e2e381369a3afb5494
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6586ef6..d86afe0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,6 +24,8 @@
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!-- Allows access to EmailProvider (EAS/IMAP/POP3) -->
<uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
+ <uses-permission android:name="android.permission.NFC" />
+
<application
android:icon="@mipmap/ic_launcher_mail"
android:label="@string/app_name"
@@ -41,7 +43,10 @@
<activity android:name=".browse.ConversationViewActivity" />
<activity android:name=".browse.FolderItem" />
<activity
- android:name=".browse.ActionbarActivity"
+ android:name=".ui.ActionbarActivity"
+ android:uiOptions="splitActionBarWhenNarrow" />
+ <activity
+ android:name=".ui.MailActivity"
android:uiOptions="splitActionBarWhenNarrow" />
<provider
diff --git a/res/layout/actionbar_view.xml b/res/layout/actionbar_view.xml
index ffaa03b..332dd6d 100644
--- a/res/layout/actionbar_view.xml
+++ b/res/layout/actionbar_view.xml
@@ -20,7 +20,7 @@
The custom action bar view Gmail uses (containing drop down account spinner,
label, and subject).
-->
-<com.android.mail.MailActionBar xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.mail.ui.MailActionBar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:email="http://schemas.android.com/apk/res/com.android.mail"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -36,4 +36,4 @@
<include layout="@layout/actionbar_subject" />
-</com.android.mail.MailActionBar>
+</com.android.mail.ui.MailActionBar>
diff --git a/res/layout/layout_tests.xml b/res/layout/layout_tests.xml
index 4fb8c64..509f87a 100644
--- a/res/layout/layout_tests.xml
+++ b/res/layout/layout_tests.xml
@@ -45,5 +45,10 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:onClick="actionbarTest"/>
+ <Button
+ android:text="@string/test_application"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:onClick="unifiedMail"/>
</LinearLayout>
</ScrollView>
diff --git a/res/layout/one_pane_activity.xml b/res/layout/one_pane_activity.xml
new file mode 100644
index 0000000..0d80aef
--- /dev/null
+++ b/res/layout/one_pane_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/content_pane"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</RelativeLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d826869..e098ac8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -26,6 +26,7 @@
<string name="account_cache_provider">Account Cache Provider</string>
<string name="dummy_gmail_provider">Dummy Gmail Provider</string>
<string name="test_actionbar" translate="false">Test Actionbar Layout</string>
+ <string name="test_application" translate="false">Test Application</string>
<string name="app_name">Unified Email</string>
diff --git a/src/com/android/mail/ConversationListContext.java b/src/com/android/mail/ConversationListContext.java
index 550a20f..920a868 100644
--- a/src/com/android/mail/ConversationListContext.java
+++ b/src/com/android/mail/ConversationListContext.java
@@ -17,6 +17,15 @@
package com.android.mail;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.os.Bundle;
+import com.android.mail.providers.Account;
+import com.android.mail.providers.UIProvider;
+
+import java.util.ArrayList;
+
/**
* This class is supposed to have the same thing that the Gmail ConversationListContext
* contained. For now, it has no implementation at all. The goal is to bring over functionality
@@ -24,9 +33,104 @@
*
* Original purpose:
* An encapsulation over a request to a list of conversations and the various states around it.
- * This includes the label the user selected to view the list, or the search query for the
+ * This includes the folder the user selected to view the list, or the search query for the
* list, etc.
*/
public class ConversationListContext {
+ private static final String EXTRA_ACCOUNT = "account";
+ private static final String EXTRA_FOLDER = "folder";
+ private static final String EXTRA_SEARCH_QUERY = "query";
+ /**
+ * A matcher for data URI's that specify conversation list info.
+ */
+ private static final UriMatcher sUrlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ /**
+ * The account for whom we are showing a list
+ */
+ private final Account mAccount;
+ /**
+ * The folder whose conversations we are displaying, if any.
+ */
+ private final String mFolderName;
+
+ /**
+ * The search query whose results we are displaying, if any.
+ */
+ private final String mSearchQuery;
+
+ // Tokenized search terms for search queries.
+ private ArrayList<String> mSearchTerms;
+
+ static {
+ // Get the real authority here.
+ // TODO(viki): Get the real authority, and the real URI matcher!!!
+ sUrlMatcher.addURI(UIProvider.AUTHORITY, "account/*/folder/*", 0);
+ }
+
+ /**
+ * De-serializes a context from a bundle.
+ */
+ public static ConversationListContext forBundle(Bundle bundle) {
+ // The account is created here as a new object. This is probably not the best thing to do.
+ // We should probably be reading an account instance from our controller.
+ Account account = bundle.getParcelable(EXTRA_ACCOUNT);
+ return new ConversationListContext(
+ account,
+ bundle.getString(EXTRA_SEARCH_QUERY),
+ bundle.getString(EXTRA_FOLDER));
+ }
+
+ /**
+ * Resolves an intent and builds an appropriate context for it.
+ */
+ public static ConversationListContext forIntent
+ (Context context, Account callerAccount, Intent intent) {
+ Account account = callerAccount;
+ String folder = null;
+ String action = intent.getAction();
+ // TODO(viki): Implement the other intents: Intent.SEARCH, Intent.PROVIDER_CHANGED.
+ if (Intent.ACTION_VIEW.equals(action) && intent.getData() != null) {
+ // TODO(viki): Look through the URI to find the account and the folder.
+ }
+ if (folder == null) {
+ folder = intent.getStringExtra(EXTRA_FOLDER);
+ }
+ return forFolder(context, account, folder);
+ }
+
+ /**
+ * Builds a context for a view to a Gmail folder. Note that folder may be null, in which case
+ * the context defaults to a view of the inbox.
+ */
+ private static ConversationListContext forFolder(
+ Context context, Account account, String folder) {
+ // Mock stuff for now.
+ return new ConversationListContext(account, null, folder);
+ }
+
+ /**
+ * Internal constructor
+ *
+ * To create a class, use the static {@link #forIntent} or {@link #forBundle(Bundle)} method.
+ * @param account
+ * @param searchQuery
+ * @param folder
+ */
+ private ConversationListContext(Account account, String searchQuery, String folder) {
+ mAccount = account;
+ mSearchQuery = searchQuery;
+ mFolderName = folder;
+ }
+
+ /**
+ * Serializes the context to a bundle.
+ */
+ public Bundle toBundle() {
+ Bundle result = new Bundle();
+ result.putParcelable(EXTRA_ACCOUNT, mAccount);
+ result.putString(EXTRA_SEARCH_QUERY, mSearchQuery);
+ result.putString(EXTRA_FOLDER, mFolderName);
+ return result;
+ }
}
diff --git a/src/com/android/mail/UnifiedEmail.java b/src/com/android/mail/UnifiedEmail.java
index 5c0c8e0..e25155e 100644
--- a/src/com/android/mail/UnifiedEmail.java
+++ b/src/com/android/mail/UnifiedEmail.java
@@ -18,9 +18,10 @@
package com.android.mail;
import com.android.mail.browse.ConversationListActivity;
-import com.android.mail.browse.ActionbarActivity;
import com.android.mail.browse.FolderItem;
import com.android.mail.compose.ComposeActivity;
+import com.android.mail.ui.ActionbarActivity;
+import com.android.mail.ui.MailActivity;
import android.app.Activity;
import android.content.ComponentName;
@@ -56,4 +57,12 @@
public void actionbarTest(View v){
startActivityWithClass(ActionbarActivity.class);
}
+
+ /**
+ * Try out the real mail activity now.
+ * @param v
+ */
+ public void unifiedMail(View v){
+ startActivityWithClass(MailActivity.class);
+ }
}
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 8938ec2..537d154 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -47,12 +47,13 @@
import android.view.View;
import com.android.mail.R;
-import com.android.mail.ViewMode;
import com.android.mail.browse.ConversationItemViewModel.SenderFragment;
import com.android.mail.perf.Timer;
import com.android.mail.providers.Address;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.UIProvider.ConversationColumns;
+import com.android.mail.ui.ConversationSelectionSet;
+import com.android.mail.ui.ViewMode;
import com.android.mail.utils.Utils;
public class ConversationItemView extends View {
diff --git a/src/com/android/mail/browse/ConversationItemViewCoordinates.java b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
index c411d46..8f77354 100644
--- a/src/com/android/mail/browse/ConversationItemViewCoordinates.java
+++ b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
@@ -27,7 +27,7 @@
import android.view.ViewParent;
import android.widget.TextView;
import com.android.mail.R;
-import com.android.mail.ViewMode;
+import com.android.mail.ui.ViewMode;
/**
* Represents the coordinates of elements inside a CanvasConversationHeaderView
diff --git a/src/com/android/mail/browse/ConversationListActivity.java b/src/com/android/mail/browse/ConversationListActivity.java
index 08b899a..85fcb51 100644
--- a/src/com/android/mail/browse/ConversationListActivity.java
+++ b/src/com/android/mail/browse/ConversationListActivity.java
@@ -41,14 +41,15 @@
import android.widget.AdapterView.OnItemSelectedListener;
import com.android.mail.R;
-import com.android.mail.ViewMode;
+import com.android.mail.browse.ConversationCursor.ConversationListener;
import com.android.mail.compose.ComposeActivity;
import com.android.mail.providers.Account;
import com.android.mail.providers.AccountCacheProvider;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.UIProvider;
-import com.android.mail.browse.ConversationCursor.ConversationListener;
-import com.android.mail.browse.ConversationSelectionSet.ConversationSetObserver;
+import com.android.mail.ui.ConversationSelectionSet;
+import com.android.mail.ui.ConversationSetObserver;
+import com.android.mail.ui.ViewMode;
import java.util.ArrayList;
@@ -203,12 +204,12 @@
}
@Override
- public void onSetEmpty(ConversationSelectionSet set) {
+ public void onSetEmpty() {
mSelectedConversationsActionMenu = null;
}
@Override
- public void onSetBecomeUnempty(ConversationSelectionSet set) {
+ public void onSetPopulated(ConversationSelectionSet set) {
mSelectedConversationsActionMenu = new SelectedConversationsActionMenu(this,
mBatchConversations);
mSelectedConversationsActionMenu.activate();
diff --git a/src/com/android/mail/browse/ConversationSelectionSet.java b/src/com/android/mail/browse/ConversationSelectionSet.java
deleted file mode 100644
index 413f17d..0000000
--- a/src/com/android/mail/browse/ConversationSelectionSet.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2012 Google Inc.
- * Licensed to 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.mail.browse;
-
-import com.android.mail.providers.Conversation;
-import com.android.mail.providers.UIProvider;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
-
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-
-
-/**
- * A simple thread-safe wrapper over a set of conversations representing a selection set (e.g.
- * in a conversation list). This class dispatches changes when the set goes empty, and when it
- * becomes unempty.
- *
- * For simplicity, this class <b>does not allow modifications</b> to the collection in observers
- * when responding to change events.
- *
- * In the Unified codebase, this is called ui.ConversationSelectionSet
- */
-public class ConversationSelectionSet implements Parcelable {
- private final HashMap<Long, Conversation> mInternalMap =
- new HashMap<Long, Conversation>();
-
- /**
- * Whether or not change notifications should be suppressed. This is used for batch updates.
- */
- private boolean mLockedForChanges = false;
-
- @VisibleForTesting
- final ArrayList<ConversationSetObserver> mObservers = new ArrayList<ConversationSetObserver>();
-
- /**
- * Observers which can register on changes on this set.
- * Observers are required to ensure that no modification is done to the set when handling
- * events.
- *
- * In the Unified codebase, this is a separate interface in ui.ConversationSetObserver
- */
- public interface ConversationSetObserver {
- /**
- * Handle when the selection set becomes empty. The observer should not make any
- * modifications to the set while handling this event.
- */
- void onSetEmpty(ConversationSelectionSet set);
-
- /**
- * Handle when the selection set becomes unempty. The observer should not make any
- * modifications to the set while handling this event.
- */
- void onSetBecomeUnempty(ConversationSelectionSet set);
-
- /**
- * Handle when the selection set gets an element added or removed. The observer should not
- * make any modifications to the set while handling this event.
- *
- * The set makes no guarantees that this method will get dispatched for each addition or
- * removal for batch operations such as {@link #clear}, but will guarantee that it will
- * get called at least once.
- */
- void onSetChanged(ConversationSelectionSet set);
- }
-
- /**
- * Registers an observer to listen for interesting changes on this set.
- * @param observer the observer to register.
- */
- public synchronized void addObserver(ConversationSetObserver observer) {
- mObservers.add(observer);
- }
-
- /**
- * Unregisters an observer for change events.
- * @param observer the observer to unregister.
- */
- public synchronized void removeObserver(ConversationSetObserver observer) {
- mObservers.remove(observer);
- }
-
- private synchronized void dispatchOnChange(ArrayList<ConversationSetObserver> observers) {
- // Copy observers so that they may unregister themselves as listeners on event handling.
- for (ConversationSetObserver observer : observers) {
- observer.onSetChanged(this);
- }
- }
-
- private synchronized void dispatchOnEmpty(ArrayList<ConversationSetObserver> observers) {
- for (ConversationSetObserver observer : observers) {
- observer.onSetEmpty(this);
- }
- }
-
- private synchronized void dispatchOnBecomeUnempty(
- ArrayList<ConversationSetObserver> observers) {
- for (ConversationSetObserver observer : observers) {
- observer.onSetBecomeUnempty(this);
- }
- }
-
- /** @see java.util.HashMap#get */
- public synchronized Conversation get(Long id) {
- return mInternalMap.get(id);
- }
-
- /** @see java.util.HashMap#put */
- public synchronized void put(Long id, Conversation info) {
- boolean initiallyEmpty = mInternalMap.isEmpty();
- mInternalMap.put(id, info);
-
- if (mLockedForChanges) {
- return;
- }
-
- ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
- dispatchOnChange(observersCopy);
- if (initiallyEmpty) {
- dispatchOnBecomeUnempty(observersCopy);
- }
- }
-
- /** @see java.util.HashMap#remove */
- public synchronized void remove(Long id) {
- boolean initiallyNotEmpty = !mInternalMap.isEmpty();
- mInternalMap.remove(id);
-
- if (mLockedForChanges) {
- return;
- }
-
- ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
- dispatchOnChange(observersCopy);
- if (mInternalMap.isEmpty() && initiallyNotEmpty) {
- dispatchOnEmpty(observersCopy);
- }
- }
-
- /** @see java.util.HashMap#isEmpty */
- public synchronized boolean isEmpty() {
- return mInternalMap.isEmpty();
- }
-
- /** @see java.util.HashMap#size */
- public synchronized int size() {
- return mInternalMap.size();
- }
-
- /** @see java.util.HashMap#keySet */
- public synchronized Collection<Long> keySet() {
- return mInternalMap.keySet();
- }
-
- /** @see java.util.HashMap#values */
- public synchronized Collection<Conversation> values() {
- return mInternalMap.values();
- }
-
- /** @see java.util.HashMap#containsKey */
- public synchronized boolean containsKey(Long key) {
- return mInternalMap.containsKey(key);
- }
-
- /** @see java.util.HashMap#clear */
- public synchronized void clear() {
- boolean initiallyNotEmpty = !mInternalMap.isEmpty();
- mInternalMap.clear();
-
- if (mLockedForChanges) {
- return;
- }
-
- if (mInternalMap.isEmpty() && initiallyNotEmpty) {
- ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
- dispatchOnChange(observersCopy);
- dispatchOnEmpty(observersCopy);
- }
- }
-
- /**
- * Iterates through a cursor of conversations and ensures that the current set is present
- * within the result set denoted by the cursor. Any conversations not foun in the result set
- * is removed from the collection.
- */
- public synchronized void validateAgainstCursor(Cursor cursor) {
- if (isEmpty()) {
- return;
- }
-
- if (cursor == null) {
- clear();
- return;
- }
-
- // Get the current position of the cursor, so it can be reset
- int currentPosition = cursor.getPosition();
- if (currentPosition != -1) {
- // Validate batch selection across all conversations, not just the most
- // recently loaded set. See bug 2405138 (Batch selection cleared when
- // additional conversations are loaded on demand).
- cursor.moveToPosition(-1);
- }
-
- // The query has run, but we have been in the list
- // Make sure that the list of selected conversations
- // contains only items that are in the result set
- ArrayList<Long> selectedConversationsToToggle = new ArrayList<Long>(keySet());
-
- // Go through the list of what we think is selected,
- // if any of the conversations are not present,
- // untoggle them from the list
- //
- // Only continue going through the list while we are unsure
- // if a conversation is selected. If we don't stop when
- // there are no more items in the selectedConversationsToToggle
- // collection, this will force the whole collection list to be loaded
- // If we believe that there is at least one conversation selected,
- // we need to keep looking to make sure that the conversation is still
- // present
- while (!selectedConversationsToToggle.isEmpty() && cursor.moveToNext()) {
- selectedConversationsToToggle.remove(cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN));
- }
-
- boolean changed = false;
-
- // Now toggle the conversations that are incorrectly still selected
- mLockedForChanges = true;
- for (long conversationId : selectedConversationsToToggle) {
- // deselectConversation doesn't update the view, which is OK here, as
- // the view, if visible, will be removed
- remove(conversationId);
- changed = true;
- }
- mLockedForChanges = false;
-
- ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
- if (changed) {
- dispatchOnChange(observersCopy);
- }
-
- if (isEmpty()) {
- dispatchOnEmpty(observersCopy);
- }
-
- cursor.moveToPosition(currentPosition);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- Conversation[] values = values().toArray(new Conversation[size()]);
- dest.writeParcelableArray(values, flags);
- }
-
- public static final Parcelable.Creator<ConversationSelectionSet> CREATOR =
- new Parcelable.Creator<ConversationSelectionSet>() {
-
- @Override
- public ConversationSelectionSet createFromParcel(Parcel source) {
- ConversationSelectionSet result = new ConversationSelectionSet();
- Parcelable[] conversations = source.readParcelableArray(
- Conversation.class.getClassLoader());
- for (Parcelable parceled : conversations) {
- Conversation conversation = (Conversation) parceled;
- result.put(conversation.id, conversation);
- }
- return result;
- }
-
- @Override
- public ConversationSelectionSet[] newArray(int size) {
- return new ConversationSelectionSet[size];
- }
- };
-
- /**
- * Toggles the given conversation in the selection set.
- */
- public synchronized void toggle(Conversation conversation) {
- long conversationId = conversation.id;
- if (containsKey(conversationId)) {
- remove(conversationId);
- } else {
- put(conversationId, conversation);
- }
- }
-}
diff --git a/src/com/android/mail/browse/SelectedConversationsActionMenu.java b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
index ca0b492..4be1b00 100644
--- a/src/com/android/mail/browse/SelectedConversationsActionMenu.java
+++ b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
@@ -18,8 +18,9 @@
package com.android.mail.browse;
import com.android.mail.R;
-import com.android.mail.browse.ConversationSelectionSet.ConversationSetObserver;
import com.android.mail.providers.Conversation;
+import com.android.mail.ui.ConversationSelectionSet;
+import com.android.mail.ui.ConversationSetObserver;
import com.android.mail.utils.LogUtils;
import com.google.common.annotations.VisibleForTesting;
@@ -124,12 +125,12 @@
}
@Override
- public void onSetBecomeUnempty(ConversationSelectionSet set) {
+ public void onSetPopulated(ConversationSelectionSet set) {
// Noop. This object can only exist while the set is non-empty.
}
@Override
- public void onSetEmpty(ConversationSelectionSet set) {
+ public void onSetEmpty() {
destroy();
}
@@ -185,7 +186,6 @@
*/
public void destroy() {
deactivate();
-
mSelectionSet.removeObserver(this);
mSelectionSet.clear();
}
diff --git a/src/com/android/mail/compose/AttachmentsView.java b/src/com/android/mail/compose/AttachmentsView.java
index 54b4c7f..2b9098f 100644
--- a/src/com/android/mail/compose/AttachmentsView.java
+++ b/src/com/android/mail/compose/AttachmentsView.java
@@ -81,6 +81,7 @@
new AttachmentComposeView(getContext(), attachment);
attachmentView.addDeleteListener(new OnClickListener() {
+ @Override
public void onClick(View v) {
deleteAttachment(attachmentView, attachment);
}
diff --git a/src/com/android/mail/providers/Account.java b/src/com/android/mail/providers/Account.java
index c7b635e..8a54d13 100644
--- a/src/com/android/mail/providers/Account.java
+++ b/src/com/android/mail/providers/Account.java
@@ -18,9 +18,10 @@
import android.database.Cursor;
import android.os.Parcel;
+import android.os.Parcelable;
-public class Account extends android.accounts.Account {
+public class Account extends android.accounts.Account implements Parcelable {
/**
* The version of the UI provider schema from which this account provider
* will return results.
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index c411a6f..ebccce2 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -17,18 +17,26 @@
package com.android.mail.ui;
+
+import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
+import android.app.ActionBar.LayoutParams;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
import android.view.ActionMode;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.widget.LinearLayout;
-import com.android.mail.ViewMode;
+import com.android.mail.ConversationListContext;
+import com.android.mail.R;
+import com.android.mail.providers.Account;
import com.android.mail.providers.Folder;
/**
@@ -45,9 +53,18 @@
* In the Gmail codebase, this was called BaseActivityController</p>
*/
public abstract class AbstractActivityController implements ActivityController {
- protected final MailActivity mActivity;
- protected final ViewMode mViewMode;
+ private static final String SAVED_CONVERSATION = "saved-conversation";
+ private static final String SAVED_CONVERSATION_POSITION = "saved-conv-pos";
+ private static final String SAVED_CONVERSATIONS = "saved-conversations";
+ // Keys for serialization of various information in Bundles.
+ private static final String SAVED_LIST_CONTEXT = "saved-list-context";
+ private Account mAccount;
+ private ActionBarView mActionBarView;
+
+ protected final RestrictedActivity mActivity;
+ private ConversationSelectionSet mBatchConversations = new ConversationSelectionSet();
protected final Context mContext;
+ protected final ViewMode mViewMode;
public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
mActivity = activity;
@@ -56,10 +73,147 @@
}
@Override
+ public void clearSubject() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void dispatchTouchEvent(MotionEvent ev) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void doneChangingFolders(FolderOperations folderOperations) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void enterSearchMode() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void exitSearchMode() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public ConversationSelectionSet getBatchConversations() {
+ // TODO(viki): Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getCurrentAccount() {
+ // TODO(viki): Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public ConversationListContext getCurrentListContext() {
+ // TODO(viki): Auto-generated method stub
+ return null;
+ }
+
+ @Override
public String getHelpContext() {
return "Mail";
}
+ @Override
+ public String getUnshownSubject(String subject) {
+ // Calculate how much of the subject is shown, and return the remaining.
+ return null;
+ }
+
+ @Override
+ public void handleConversationLoadError() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void handleSearchRequested() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ /**
+ * Initialize the action bar. This is not visible to OnePaneController and TwoPaneController so
+ * they cannot override this behavior.
+ */
+ private void initCustomActionBarView() {
+ ActionBar actionBar = mActivity.getActionBar();
+ mActionBarView = (MailActionBar) LayoutInflater.from(mContext).inflate(
+ R.layout.actionbar_view, null);
+
+ if (actionBar != null && mActionBarView != null) {
+ // Why have a different variable for the same thing? We should apply the same actions
+ // on mActionBarView instead.
+ // mSubjectDisplayer = (ConversationSubjectDisplayer) mActionBarView;
+ mActionBarView.initialize(mActivity, this, mViewMode, actionBar);
+ actionBar.setCustomView((LinearLayout) mActionBarView, new ActionBar.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM
+ | ActionBar.DISPLAY_SHOW_TITLE);
+ }
+ }
+
+ /**
+ * Returns whether the conversation list fragment is visible or not. Different layouts will have
+ * their own notion on the visibility of fragments, so this method needs to be overriden.
+ * @return
+ */
+ protected abstract boolean isConversationListVisible();
+
+ @Override
+ public boolean navigateToAccount(String account) {
+ // TODO(viki): Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void navigateToFolder(String folderCanonicalName) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ // TODO(viki): Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public void onConversationListVisibilityChanged(boolean visible) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
/**
* By default, doing nothing is right. A two-pane controller will need to
* override this.
@@ -71,71 +225,13 @@
}
@Override
- public void onLabelChanged(Folder label, long conversationId, boolean added) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void doneChangingLabels(FolderOperations labelOperations) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void handleSearchRequested() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onStartBulkOperation() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onEndBulkOperation() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onStartDragMode() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onStopDragMode() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void setSubject(String subject) {
- // Do something useful with the subject. This requires changing the
- // conversation view's
- // subject text.
- }
-
- @Override
- public String getUnshownSubject(String subject) {
- // Calculate how much of the subject is shown, and return the remaining.
- return null;
- }
-
- @Override
- public void onConversationListVisibilityChanged(boolean visible) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
public void onCreate(Bundle savedState) {
// Initialize the action bar view.
+ initCustomActionBarView();
final Intent intent = mActivity.getIntent();
+ // TODO(viki) Choose an account here.
+
// Allow shortcut keys to function for the ActionBar and menus.
mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
final Context context = mActivity.getApplicationContext();
@@ -144,48 +240,10 @@
restoreState(savedState);
}
- /**
- * Restore the state from the previous bundle.
- * @param savedState
- */
- protected void restoreState(Bundle savedState) {
- // Do nothing here.
- }
-
@Override
- public void onSetEmpty(ConversationSelectionSet set) {
+ public Dialog onCreateDialog(int id, Bundle bundle) {
// TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onSetPopulated(ConversationSelectionSet set) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onSetChanged(ConversationSelectionSet set) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onResume() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onPause() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onStop() {
- // TODO(viki): Auto-generated method stub
-
+ return null;
}
@Override
@@ -195,31 +253,31 @@
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
+ public void onEndBulkOperation() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO(viki): Auto-generated method stub
return false;
}
@Override
+ public void onFolderChanged(Folder folder, long conversationId, boolean added) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO(viki): Auto-generated method stub
return false;
}
@Override
- public void onWindowFocusChanged(boolean hasFocus) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public boolean onBackPressed() {
- // TODO(viki): Auto-generated method stub
- return false;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
+ public void onPause() {
// TODO(viki): Auto-generated method stub
}
@@ -231,67 +289,159 @@
}
@Override
- public void onActionModeStarted(ActionMode mode) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onActionModeFinished(ActionMode mode) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public ConversationSelectionSet getBatchConversations() {
- // TODO(viki): Auto-generated method stub
- return null;
- }
-
- @Override
- public void handleConversationLoadError() {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public void dispatchTouchEvent(MotionEvent ev) {
- // TODO(viki): Auto-generated method stub
-
- }
-
- @Override
- public Dialog onCreateDialog(int id, Bundle bundle) {
- // TODO(viki): Auto-generated method stub
- return null;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
+ public boolean onPrepareOptionsMenu(Menu menu) {
// TODO(viki): Auto-generated method stub
return false;
}
@Override
+ public void onResume() {
+ // TODO(viki): Auto-generated method stub
+ mBatchConversations.addObserver(this);
+ if (mActionBarView != null) {
+ mActionBarView.onResume();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
public void onSearchRequested() {
// TODO(viki): Auto-generated method stub
}
@Override
+ public void onSetChanged(ConversationSelectionSet set) {
+ // We don't care about changes to the set. Ignore.
+ }
+
+ @Override
+ public void onSetEmpty() {
+ }
+
+ @Override
+ public void onSetPopulated(ConversationSelectionSet set) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onStartBulkOperation() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onStartDragMode() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onStop() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void onStopDragMode() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
public void onViewModeChanged(ViewMode mode) {
// TODO(viki): Auto-generated method stub
}
@Override
- public void clearSubject() {
+ public void onWindowFocusChanged(boolean hasFocus) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void reloadSearch(String string) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ /**
+ * @param savedState
+ */
+ protected void restoreListContext(Bundle savedState) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ /**
+ * Restore the state of selected conversations. This needs to be done after the correct mode
+ * is set and the action bar is fully initialized. If not, several key pieces of state
+ * information will be missing, and the split views may not be initialized correctly.
+ * @param savedState
+ */
+ private void restoreSelectedConversations(Bundle savedState) {
+ if (savedState != null){
+ // Restore the view mode? This looks wrong.
+ mBatchConversations = savedState.getParcelable(SAVED_CONVERSATIONS);
+ if (mBatchConversations.isEmpty()) {
+ onSetPopulated(mBatchConversations);
+ onConversationVisibilityChanged(isConversationListVisible());
+ } else {
+ onSetEmpty();
+ }
+ } else {
+ onSetEmpty();
+ }
+ }
+
+ /**
+ * Restore the state from the previous bundle. Subclasses should call this method from the
+ * parent class, since it performs important UI initialization.
+ * @param savedState
+ */
+ protected void restoreState(Bundle savedState) {
+ if (savedState != null) {
+ restoreListContext(savedState);
+ // Attach the menu handler here.
+ } else {
+ final Intent intent = mActivity.getIntent();
+ // TODO(viki): Show the list context from Intent
+ // showConversationList(ConversationListContext.forIntent(mContext, mAccount, intent));
+ }
+
+ // Set the correct mode based on the current context
+
+ // And restore the state of selected conversations
+ restoreSelectedConversations(savedState);
+ }
+
+ @Override
+ public void setSubject(String subject) {
+ // Do something useful with the subject. This requires changing the
+ // conversation view's subject text.
+ }
+
+ @Override
+ public void showFolderList() {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void startActionBarStatusCursorLoader(String account) {
+ // TODO(viki): Auto-generated method stub
+
+ }
+
+ @Override
+ public void stopActionBarStatusCursorLoader(String account) {
// TODO(viki): Auto-generated method stub
}
diff --git a/src/com/android/mail/ui/AbstractMailActivity.java b/src/com/android/mail/ui/AbstractMailActivity.java
index 6215c57..9ede81b 100644
--- a/src/com/android/mail/ui/AbstractMailActivity.java
+++ b/src/com/android/mail/ui/AbstractMailActivity.java
@@ -27,8 +27,7 @@
import android.view.WindowManager;
import com.android.mail.R;
-import com.android.mail.RestrictedActivity;
-import com.android.mail.UiHandler;
+import com.android.mail.ui.ViewMode.Mode;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@@ -46,7 +45,7 @@
*
*/
public abstract class AbstractMailActivity extends Activity
- implements HelpCallback, RestrictedActivity {
+ implements ControllableActivity {
private NfcAdapter mNfcAdapter; // final after onCreate
private NdefMessage mForegroundNdef;
@@ -89,11 +88,6 @@
}
@Override
- public Context getApplicationContext() {
- return this;
- }
-
- @Override
protected void onStart() {
super.onStart();
@@ -183,4 +177,14 @@
new byte[0], recordBytes);
return new NdefMessage(new NdefRecord[] { mailto });
}
+
+ @Override
+ public ConversationSelectionSet getBatchConversations() {
+ return new ConversationSelectionSet();
+ }
+
+ @Override
+ public Mode getViewMode() {
+ return ViewMode.UNKNOWN;
+ }
}
diff --git a/src/com/android/mail/ActionBarView.java b/src/com/android/mail/ui/ActionBarView.java
similarity index 95%
rename from src/com/android/mail/ActionBarView.java
rename to src/com/android/mail/ui/ActionBarView.java
index 2f8ab32..8c24d02 100644
--- a/src/com/android/mail/ActionBarView.java
+++ b/src/com/android/mail/ui/ActionBarView.java
@@ -15,7 +15,7 @@
* limitations under the License.
*******************************************************************************/
-package com.android.mail;
+package com.android.mail.ui;
import android.app.ActionBar;
import android.os.Bundle;
@@ -54,9 +54,9 @@
*/
SEARCH_RESULTS,
/**
- * Viewing a list of labels
+ * Viewing a list of folders
*/
- LABEL,
+ FOLDER,
/**
* Viewing a conversation from search results
*/
@@ -118,10 +118,10 @@
void updateActionBar(String[] mAccountNames, String currentAccount);
/**
- * Update the label that the user is currently viewing??
- * @param label
+ * Update the folder that the user is currently viewing??
+ * @param folder
*/
- void setLabel(String label);
+ void setFolder(String folder);
/**
* Returns the menu ID for the menu in this actionbar.
diff --git a/src/com/android/mail/browse/ActionbarActivity.java b/src/com/android/mail/ui/ActionbarActivity.java
similarity index 92%
rename from src/com/android/mail/browse/ActionbarActivity.java
rename to src/com/android/mail/ui/ActionbarActivity.java
index bf767bf..39079ac 100644
--- a/src/com/android/mail/browse/ActionbarActivity.java
+++ b/src/com/android/mail/ui/ActionbarActivity.java
@@ -15,15 +15,12 @@
* limitations under the License.
*******************************************************************************/
-package com.android.mail.browse;
+package com.android.mail.ui;
import com.android.mail.ConversationListContext;
-import com.android.mail.MailActionBar;
-import com.android.mail.ActionBarView.Mode;
-import com.android.mail.MailActionBar.Callback;
-import com.android.mail.RestrictedActivity;
import com.android.mail.R;
-import com.android.mail.ViewMode;
+import com.android.mail.ui.ActionBarView.Mode;
+import com.android.mail.ui.MailActionBar.Callback;
import android.app.ActionBar;
import android.app.Activity;
@@ -111,7 +108,7 @@
}
public void testLabelMode(View v){
- changeMode(Mode.LABEL);
+ changeMode(Mode.FOLDER);
}
@Override
@@ -136,12 +133,12 @@
}
@Override
- public void navigateToLabel(String labelCanonicalName) {
+ public void navigateToFolder(String folderCanonicalName) {
// TODO(viki): Auto-generated method stub
}
@Override
- public void showLabelList() {
+ public void showFolderList() {
// TODO(viki): Auto-generated method stub
}
diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java
index bd96d8b..e417ba7 100644
--- a/src/com/android/mail/ui/ActivityController.java
+++ b/src/com/android/mail/ui/ActivityController.java
@@ -26,7 +26,7 @@
import android.view.MenuItem;
import android.view.MotionEvent;
-import com.android.mail.ViewMode.ModeChangeListener;
+import com.android.mail.ui.ViewMode.ModeChangeListener;
/**
* An Activity controller knows how to combine views and listeners into a functioning activity.
@@ -34,7 +34,7 @@
* or respond to user action.
*/
public interface ActivityController extends MenuCallback, LayoutListener, SubjectDisplayChanger,
- ConversationSetObserver, ModeChangeListener {
+ ConversationSetObserver, ModeChangeListener, MailActionBar.Callback {
// As far as possible, the methods here that correspond to Activity lifecycle have the same name
// as their counterpart in the Activity lifecycle.
@@ -78,9 +78,12 @@
void onActivityResult(int requestCode, int resultCode, Intent data);
/**
- * Called by the Mail activity when the back button is pressed.
+ * Called by the Mail activity when the back button is pressed. Returning true consumes the
+ * event and disallows the calling method from trying to handle the back button any other way.
+ *
* @see android.app.Activity#onBackPressed()
- * @return
+ * @return true if the back press was handled and the event was consumed. Return false if the
+ * event was not consumed.
*/
boolean onBackPressed();
diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java
new file mode 100644
index 0000000..8d4a0a8
--- /dev/null
+++ b/src/com/android/mail/ui/ControllableActivity.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (C) 2012 Google Inc.
+ * Licensed to 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.mail.ui;
+
+import com.android.mail.ui.ViewMode.Mode;
+
+/**
+ * A controllable activity is an Activity that has a Controller attached. This activity must be
+ * able to attach the various view fragments and delegate the method calls between them.
+ */
+public interface ControllableActivity extends HelpCallback, RestrictedActivity {
+ /**
+ * Returns the conversations selected by the user for performing a batch action like archive,
+ * delete, etc.
+ * @return conversations selected
+ */
+ ConversationSelectionSet getBatchConversations();
+
+ /**
+ * Returns the mode that the activity is currently in.
+ * @see com.android.mail.ui.ViewMode.Mode
+ * @return the mode the activity is currently in.
+ */
+ Mode getViewMode();
+
+}
diff --git a/src/com/android/mail/ui/ControllerFactory.java b/src/com/android/mail/ui/ControllerFactory.java
index d89d6b6..6b025ff 100644
--- a/src/com/android/mail/ui/ControllerFactory.java
+++ b/src/com/android/mail/ui/ControllerFactory.java
@@ -17,7 +17,6 @@
package com.android.mail.ui;
-import com.android.mail.ViewMode;
/**
* Creates the appropriate {@link ActivityController} to control {@link MailActivity}.
diff --git a/src/com/android/mail/ui/ConversationSelectionSet.java b/src/com/android/mail/ui/ConversationSelectionSet.java
index d498591..3e4484e 100644
--- a/src/com/android/mail/ui/ConversationSelectionSet.java
+++ b/src/com/android/mail/ui/ConversationSelectionSet.java
@@ -23,14 +23,10 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.mail.ConversationInfo;
-import com.android.mail.providers.Folder;
-
+import com.android.mail.providers.Conversation;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.Map;
/**
* A simple thread-safe wrapper over a set of conversations representing a
@@ -40,8 +36,29 @@
* responding to change events.
*/
public class ConversationSelectionSet implements Parcelable {
- private final HashMap<Long, ConversationInfo> mInternalMap =
- new HashMap<Long, ConversationInfo>();
+ public static final Parcelable.Creator<ConversationSelectionSet> CREATOR =
+ new Parcelable.Creator<ConversationSelectionSet>() {
+
+ @Override
+ public ConversationSelectionSet createFromParcel(Parcel source) {
+ ConversationSelectionSet result = new ConversationSelectionSet();
+ Parcelable[] conversations = source.readParcelableArray(
+ Conversation.class.getClassLoader());
+ for (Parcelable parceled : conversations) {
+ Conversation conversation = (Conversation) parceled;
+ result.put(conversation.id, conversation);
+ }
+ return result;
+ }
+
+ @Override
+ public ConversationSelectionSet[] newArray(int size) {
+ return new ConversationSelectionSet[size];
+ }
+ };
+
+ private final HashMap<Long, Conversation> mInternalMap =
+ new HashMap<Long, Conversation>();
@VisibleForTesting
final ArrayList<ConversationSetObserver> mObservers = new ArrayList<ConversationSetObserver>();
@@ -55,13 +72,33 @@
mObservers.add(observer);
}
- /**
- * Unregisters an observer for change events.
- *
- * @param observer the observer to unregister.
- */
- public synchronized void removeObserver(ConversationSetObserver observer) {
- mObservers.remove(observer);
+ /** @see java.util.HashMap#clear */
+ public synchronized void clear() {
+ boolean initiallyNotEmpty = !mInternalMap.isEmpty();
+ mInternalMap.clear();
+
+ if (mInternalMap.isEmpty() && initiallyNotEmpty) {
+ ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
+ dispatchOnChange(observersCopy);
+ dispatchOnEmpty(observersCopy);
+ }
+ }
+
+ /** @see java.util.HashMap#containsKey */
+ private synchronized boolean containsKey(Long key) {
+ return mInternalMap.containsKey(key);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private synchronized void dispatchOnBecomeUnempty(
+ ArrayList<ConversationSetObserver> observers) {
+ for (ConversationSetObserver observer : observers) {
+ observer.onSetPopulated(this);
+ }
}
private synchronized void dispatchOnChange(ArrayList<ConversationSetObserver> observers) {
@@ -74,24 +111,25 @@
private synchronized void dispatchOnEmpty(ArrayList<ConversationSetObserver> observers) {
for (ConversationSetObserver observer : observers) {
- observer.onSetEmpty(this);
- }
- }
-
- private synchronized void dispatchOnBecomeUnempty(
- ArrayList<ConversationSetObserver> observers) {
- for (ConversationSetObserver observer : observers) {
- observer.onSetPopulated(this);
+ observer.onSetEmpty();
}
}
/** @see java.util.HashMap#get */
- private synchronized ConversationInfo get(Long id) {
+ private synchronized Conversation get(Long id) {
return mInternalMap.get(id);
}
+ /**
+ * Is this conversation set empty?
+ * @return true if the conversation selection set is empty. False otherwise.
+ */
+ public synchronized boolean isEmpty() {
+ return mInternalMap.isEmpty();
+ }
+
/** @see java.util.HashMap#put */
- private synchronized void put(Long id, ConversationInfo info) {
+ private synchronized void put(Long id, Conversation info) {
boolean initiallyEmpty = mInternalMap.isEmpty();
mInternalMap.put(id, info);
@@ -114,99 +152,42 @@
}
}
+ /**
+ * Unregisters an observer for change events.
+ *
+ * @param observer the observer to unregister.
+ */
+ public synchronized void removeObserver(ConversationSetObserver observer) {
+ mObservers.remove(observer);
+ }
+
/** @see java.util.HashMap#size */
- private synchronized int size() {
+ public synchronized int size() {
return mInternalMap.size();
}
- /** @see java.util.HashMap#values */
- private synchronized Collection<ConversationInfo> values() {
- return mInternalMap.values();
- }
-
- /** @see java.util.HashMap#containsKey */
- private synchronized boolean containsKey(Long key) {
- return mInternalMap.containsKey(key);
- }
-
- /** @see java.util.HashMap#clear */
- private synchronized void clear() {
- boolean initiallyNotEmpty = !mInternalMap.isEmpty();
- mInternalMap.clear();
-
- if (mInternalMap.isEmpty() && initiallyNotEmpty) {
- ArrayList<ConversationSetObserver> observersCopy = Lists.newArrayList(mObservers);
- dispatchOnChange(observersCopy);
- dispatchOnEmpty(observersCopy);
- }
- }
-
/**
- * Determines the set of labels associated with the selected conversations.
- * If the set of labels differ between the conversations, it will return the
- * set by the first one in the iterator order.
- *
- * @return the set of labels associated with the selection set.
+ * Toggle a checkmark for the given conversation.
+ * @param conversation
*/
- public synchronized Map<String, Folder> getFolders() {
- if (mInternalMap.isEmpty()) {
- return Collections.emptyMap();
+ public void toggle(Conversation conversation) {
+ long conversationId = conversation.id;
+ if (containsKey(conversationId)) {
+ remove(conversationId);
} else {
- return mInternalMap.values().iterator().next().getFolders();
+ put(conversationId, conversation);
}
+
}
- @Override
- public int describeContents() {
- return 0;
+ /** @see java.util.HashMap#values */
+ public synchronized Collection<Conversation> values() {
+ return mInternalMap.values();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- ConversationInfo[] values = values().toArray(new ConversationInfo[size()]);
+ Conversation[] values = values().toArray(new Conversation[size()]);
dest.writeParcelableArray(values, flags);
}
-
- public static final Parcelable.Creator<ConversationSelectionSet> CREATOR =
- new Parcelable.Creator<ConversationSelectionSet>() {
-
- @Override
- public ConversationSelectionSet createFromParcel(Parcel source) {
- ConversationSelectionSet result = new ConversationSelectionSet();
- Parcelable[] conversations = source.readParcelableArray(
- ConversationInfo.class.getClassLoader());
- for (Parcelable parceled : conversations) {
- ConversationInfo conversation = (ConversationInfo) parceled;
- result.put(conversation.getConversationId(), conversation);
- }
- return result;
- }
-
- @Override
- public ConversationSelectionSet[] newArray(int size) {
- return new ConversationSelectionSet[size];
- }
- };
-
- /**
- * Toggles the given conversation in the selection set.
- */
- public synchronized void toggle(long conversationId, long maxMessageId,
- Map<String, Folder> folders) {
- if (containsKey(conversationId)) {
- remove(conversationId);
- } else {
- put(conversationId, new ConversationInfo(conversationId, maxMessageId, folders));
- }
- }
-
- /**
- * Updates the label a given conversation in the set may have.
- */
- public synchronized void onLabelChanged(Folder folders, long conversationId, boolean added) {
- if (containsKey(conversationId)) {
- ConversationInfo info = get(conversationId);
- info.updateFolder(folders, added);
- }
- }
}
diff --git a/src/com/android/mail/ui/ConversationSetObserver.java b/src/com/android/mail/ui/ConversationSetObserver.java
index 7599fbf..bd85e03 100644
--- a/src/com/android/mail/ui/ConversationSetObserver.java
+++ b/src/com/android/mail/ui/ConversationSetObserver.java
@@ -29,10 +29,9 @@
// implementation, the observers can wreck the selection set unknowingly!!
/**
- * Called when the selection set becomes empty. The observer should not make any
- * modifications to the set while handling this event.
+ * Called when the selection set becomes empty.
*/
- void onSetEmpty(ConversationSelectionSet set);
+ void onSetEmpty();
/**
* Handle when the selection set is populated with some items. The observer should not make any
diff --git a/src/com/android/mail/MailActionBar.java b/src/com/android/mail/ui/MailActionBar.java
similarity index 77%
rename from src/com/android/mail/MailActionBar.java
rename to src/com/android/mail/ui/MailActionBar.java
index 8aeae2a..fe7cf70 100644
--- a/src/com/android/mail/MailActionBar.java
+++ b/src/com/android/mail/ui/MailActionBar.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package com.android.mail;
+package com.android.mail.ui;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
@@ -30,8 +30,15 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.mail.R;
+import com.android.mail.AccountRecentLabelSpinner;
+import com.android.mail.AccountSpinnerAdapter;
+import com.android.mail.ConversationListContext;
+
/**
* View to manage the various states of the Gmail Action Bar
+ *
+ * TODO(viki): Include ConversatinSubjectDisplayer here as well.
*/
public class MailActionBar extends LinearLayout implements ActionBarView, OnNavigationListener {
/**
@@ -68,7 +75,7 @@
*/
boolean navigateToAccount(final String account);
- void navigateToLabel(final String labelCanonicalName);
+ void navigateToFolder(final String folderCanonicalName);
/**
* Invoked when the user is already viewing search results
@@ -77,29 +84,29 @@
*/
void reloadSearch(String string);
- void showLabelList();
+ void showFolderList();
void startActionBarStatusCursorLoader(String account);
void stopActionBarStatusCursorLoader(String account);
}
+ private String[] mAccountNames;
private ActionBar mActionBar;
protected RestrictedActivity mActivity;
+ private Callback mCallback;
+ protected View mFolderView;
private Mode mMode;
- protected AccountRecentLabelSpinner mSpinnerView;
- SpinnerAdapter mSpinner;
+
+ private MenuItem mRefreshItem;
+
private MenuItem mSearch;
+ SpinnerAdapter mSpinner;
+ protected AccountRecentLabelSpinner mSpinnerView;
// TODO(viki): This is a SnippetTextView in the Gmail source code. Resolve.
private TextView mSubjectView;
- protected View mLabelView;
- private Callback mCallback;
- private String[] mAccountNames;
-
- private MenuItem mRefreshItem;
-
public MailActionBar(Context context) {
this(context, null);
}
@@ -114,9 +121,6 @@
mMode = Mode.NORMAL;
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#createOptionsMenu(android.view.Menu)
- */
@Override
public boolean createOptionsMenu(Menu menu) {
// If the mode is valid, then set the initial menu
@@ -129,17 +133,11 @@
return true;
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#getMode()
- */
@Override
public Mode getMode() {
return mMode;
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#getOptionsMenuId()
- */
@Override
public int getOptionsMenuId() {
switch (mMode){
@@ -147,7 +145,7 @@
// Fallthrough
case SEARCH_RESULTS:
return R.menu.conversation_list_menu;
- case LABEL:
+ case FOLDER:
return R.menu.label_list_menu;
case SEARCH_RESULTS_CONVERSATION:
return R.menu.conversation_actions;
@@ -157,27 +155,18 @@
return 0;
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#handleRestore(android.os.Bundle)
- */
@Override
public void handleRestore(Bundle savedInstanceState) {
// TODO(viki): Auto-generated method stub
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#handleSaveInstanceState(android.os.Bundle)
- */
@Override
public void handleSaveInstanceState(Bundle outState) {
// TODO(viki): Auto-generated method stub
}
- /*
- * @see com.android.mail.ActionBarView#initialize(com.android.mail.RestrictedActivity, com.android.mail.MailActionBar.Callback, com.android.mail.ViewMode, android.app.ActionBar)
- */
@Override
public void initialize(RestrictedActivity activity, Callback callback, ViewMode viewMode,
ActionBar actionBar) {
@@ -194,43 +183,38 @@
mActionBar.setListNavigationCallbacks(mSpinner, this);
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#onPause()
- */
+ @Override
+ public boolean onNavigationItemSelected(int itemPosition, long itemId) {
+ // Don't do anything. Toast on the action.
+ Toast.makeText(getContext(), "Selected item " + itemPosition, Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
@Override
public void onPause() {
// TODO(viki): Auto-generated method stub
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#onResume()
- */
@Override
public void onResume() {
// TODO(viki): Auto-generated method stub
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#onStatusResult(java.lang.String, int)
- */
@Override
public void onStatusResult(String account, int status) {
- // Update the inbox label if required
+ // Update the inbox folder if required
mCallback.stopActionBarStatusCursorLoader(account);
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#prepareOptionsMenu()
- */
@Override
public boolean prepareOptionsMenu(Menu menu) {
if (mSubjectView != null){
mSubjectView.setVisibility(GONE);
}
- if (mLabelView != null){
- mLabelView.setVisibility(GONE);
+ if (mFolderView != null){
+ mFolderView.setVisibility(GONE);
}
switch (mMode){
@@ -255,15 +239,12 @@
if (mSearch != null) {
mSearch.collapseActionView();
}
- case LABEL:
+ case FOLDER:
break;
}
return false;
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#setActionBarIconHome()
- */
@Override
public void removeBackButton() {
if (mActionBar == null) {
@@ -276,9 +257,6 @@
mActivity.getActionBar().setHomeButtonEnabled(false);
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#setActionBarIconBack()
- */
@Override
public void setBackButton() {
if (mActionBar == null){
@@ -291,41 +269,18 @@
mActivity.getActionBar().setHomeButtonEnabled(true);
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#setLabel(java.lang.String)
- */
@Override
- public void setLabel(String label) {
+ public void setFolder(String folder) {
// TODO(viki): Add this functionality to change the label.
-// if (mAdvancedSearchWrapper != null) {
-// mAdvancedSearchWrapper.setLabel(label);
-// }
-// super.setLabel(label);
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#setMode(com.android.mail.ActionBarView.Mode)
- */
@Override
public boolean setMode(Mode mode) {
mMode = mode;
return true;
}
- /* (non-Javadoc)
- * @see com.android.mail.ActionBarView#updateActionBar(java.lang.String[], java.lang.String)
- */
@Override
public void updateActionBar(String[] accounts, String currentAccount) {
}
-
- /* (non-Javadoc)
- * @see android.app.ActionBar.OnNavigationListener#onNavigationItemSelected(int, long)
- */
- @Override
- public boolean onNavigationItemSelected(int itemPosition, long itemId) {
- // Don't do anything. Toast on the action.
- Toast.makeText(getContext(), "Selected item " + itemPosition, Toast.LENGTH_SHORT).show();
- return false;
- }
}
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index a8a9c44..a3e3b96 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -26,7 +26,6 @@
import android.view.MenuItem;
import android.view.MotionEvent;
-import com.android.mail.ViewMode;
/**
* This is the root activity container that holds the left navigation fragment
@@ -34,6 +33,10 @@
* conversation list or a conversation view).
*/
public class MailActivity extends AbstractMailActivity {
+ // TODO(viki) This class lacks: Conversation Position tracking
+ // TODO(viki) This class lacks: What's New dialog
+ // TODO(viki) This class lacks: Sync Window Upgrade dialog
+
/**
* The activity controller to which we delegate most Activity lifecycle events.
*/
@@ -43,6 +46,13 @@
*/
private ViewMode mViewMode;
+ /**
+ * A clean launch is when the activity is not resumed. We want to show a "What's New" dialog
+ * on a clean launch: when the user started the Activity by tapping on the icon: not when he
+ * selected "Up" from compose, not when he resumed the activity, etc.
+ */
+ private boolean mLaunchedCleanly = false;
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO(viki): Why is there a check for null only in in this method?
@@ -54,6 +64,11 @@
}
@Override
+ public ConversationSelectionSet getBatchConversations() {
+ return mController.getBatchConversations();
+ }
+
+ @Override
public void onActionModeFinished(ActionMode mode) {
super.onActionModeFinished(mode);
mController.onActionModeFinished(mode);
@@ -93,12 +108,14 @@
// activity launched directly from a send-to intent. (in that case the
// action is null.)
if (savedState == null && intent.getAction() != null) {
+ mLaunchedCleanly = true;
}
}
@Override
public Dialog onCreateDialog(int id, Bundle bundle) {
Dialog dialog = mController.onCreateDialog(id, bundle);
+ // TODO(viki): Handle what's new and the sync window upgrade dialog here.
return dialog == null ? super.onCreateDialog(id, bundle) : dialog;
}
@@ -144,7 +161,6 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
-
mController.onSaveInstanceState(outState);
}
@@ -157,8 +173,9 @@
@Override
protected void onStart() {
super.onStart();
-
- // TODO(viki): Show a "what's new screen"
+ if (mLaunchedCleanly) {
+ // TODO(viki): Show a "what's new screen"
+ }
}
@Override
diff --git a/src/com/android/mail/ui/MenuCallback.java b/src/com/android/mail/ui/MenuCallback.java
index 13f9cf0..f4a535e 100644
--- a/src/com/android/mail/ui/MenuCallback.java
+++ b/src/com/android/mail/ui/MenuCallback.java
@@ -33,12 +33,12 @@
/**
* Invoked whenever a label is added or removed.
*/
- void onLabelChanged(Folder label, long conversationId, boolean added);
+ void onFolderChanged(Folder label, long conversationId, boolean added);
/**
* Invoked once all the modifications on the labels have been performed.
*/
- void doneChangingLabels(FolderOperations labelOperations);
+ void doneChangingFolders(FolderOperations labelOperations);
/**
* Invoked when the user requests search mode
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index ce29ef4..c096b5d 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -17,7 +17,10 @@
package com.android.mail.ui;
-import com.android.mail.ViewMode;
+import android.os.Bundle;
+import android.view.Window;
+
+import com.android.mail.R;
/**
* Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
@@ -25,8 +28,7 @@
*/
// Called OnePaneActivityController in Gmail.
-public class OnePaneController extends AbstractActivityController {
-
+public final class OnePaneController extends AbstractActivityController {
/**
* @param activity
* @param viewMode
@@ -36,4 +38,19 @@
// TODO(viki): Auto-generated constructor stub
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ // Request opaque actionbar
+ mActivity.getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
+ // Set 1-pane content view.
+ mActivity.setContentView(R.layout.one_pane_activity);
+
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected boolean isConversationListVisible() {
+ // TODO(viki): Auto-generated method stub
+ return false;
+ }
}
diff --git a/src/com/android/mail/RestrictedActivity.java b/src/com/android/mail/ui/RestrictedActivity.java
similarity index 99%
rename from src/com/android/mail/RestrictedActivity.java
rename to src/com/android/mail/ui/RestrictedActivity.java
index 2596fbc..2aec8ce 100644
--- a/src/com/android/mail/RestrictedActivity.java
+++ b/src/com/android/mail/ui/RestrictedActivity.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package com.android.mail;
+package com.android.mail.ui;
import android.app.ActionBar;
import android.app.Activity;
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 5e8d85d..56510ee 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -17,7 +17,6 @@
package com.android.mail.ui;
-import com.android.mail.ViewMode;
/**
* Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
@@ -25,7 +24,7 @@
*/
// Called OnePaneActivityController in Gmail.
-public class TwoPaneController extends AbstractActivityController {
+public final class TwoPaneController extends AbstractActivityController {
/**
* @param activity
@@ -36,4 +35,10 @@
// TODO(viki): Auto-generated constructor stub
}
+ @Override
+ protected boolean isConversationListVisible() {
+ // TODO(viki): Auto-generated method stub
+ return false;
+ }
+
}
diff --git a/src/com/android/mail/UiHandler.java b/src/com/android/mail/ui/UiHandler.java
similarity index 98%
rename from src/com/android/mail/UiHandler.java
rename to src/com/android/mail/ui/UiHandler.java
index 696e1e9..447718a 100644
--- a/src/com/android/mail/UiHandler.java
+++ b/src/com/android/mail/ui/UiHandler.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package com.android.mail;
+package com.android.mail.ui;
import android.app.Activity;
import android.app.FragmentTransaction;
diff --git a/src/com/android/mail/ViewMode.java b/src/com/android/mail/ui/ViewMode.java
similarity index 79%
rename from src/com/android/mail/ViewMode.java
rename to src/com/android/mail/ui/ViewMode.java
index c3631f8..01b10be 100644
--- a/src/com/android/mail/ViewMode.java
+++ b/src/com/android/mail/ui/ViewMode.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package com.android.mail;
+package com.android.mail.ui;
import com.android.mail.utils.Utils;
import com.google.common.collect.Lists;
@@ -37,19 +37,52 @@
// Do not change the order of the values in the enum. The ordinal position is used in a
// saved instance bundle. When adding values, add them to the end of the enum.
- enum Mode {
+ /**
+ * All possible modes that a Mail activity can be in.
+ */
+ public static enum Mode {
+ /**
+ * Uncertain mode. The mode has not been initialized.
+ */
MODE_UNKNOWN,
- MODE_LABEL_LIST,
+ /**
+ * Mode when showing a list of folders.
+ */
+ MODE_FOLDER_LIST,
+ /**
+ * Mode when showing a list of conversations
+ */
MODE_CONVERSATION_LIST,
+ /**
+ * Mode when showing a single conversation.
+ */
MODE_CONVERSATION,
+ /**
+ * Mode when showing results from user search.
+ */
MODE_SEARCH_RESULTS,
}
// Handy names for external users of this class.
+ /**
+ * Uncertain mode. The mode has not been initialized.
+ */
public static Mode UNKNOWN = Mode.MODE_UNKNOWN;
- public static Mode LABEL_LIST = Mode.MODE_LABEL_LIST;
+ /**
+ * Mode when showing a list of folders.
+ */
+ public static Mode FOLDER_LIST = Mode.MODE_FOLDER_LIST;
+ /**
+ * Mode when showing a list of conversations
+ */
public static Mode CONVERSATION_LIST = Mode.MODE_CONVERSATION_LIST;
+ /**
+ * Mode when showing a single conversation.
+ */
public static Mode CONVERSATION = Mode.MODE_CONVERSATION;
+ /**
+ * Mode when showing results from user search.
+ */
public static Mode SEARCH_RESULTS = Mode.MODE_SEARCH_RESULTS;
private Mode mMode = UNKNOWN;
@@ -77,11 +110,11 @@
}
/**
- * Requests a transition of the mode to show the label list as the prominent view.
+ * Requests a transition of the mode to show the folder list as the prominent view.
* @return Whether or not a change occured.
*/
- public boolean transitionToLabelListMode() {
- return setModeInternal(LABEL_LIST);
+ public boolean transitionToFolderListMode() {
+ return setModeInternal(FOLDER_LIST);
}
/**
@@ -120,8 +153,8 @@
return mMode == CONVERSATION_LIST;
}
- public boolean isLabelListMode() {
- return mMode == LABEL_LIST;
+ public boolean isFolderListMode() {
+ return mMode == FOLDER_LIST;
}
public void handleSaveInstanceState(Bundle outState) {