New account spinner

- Don't use the action bar spinner.
  Instead use a custom view to show the current account.
  (It's not a spinner; now we show the dropdown list by ourselves,
  which gives us more detailed control.)

- Also show the current mailbox name/unread count with the account name.

- Removed the mailbox info loader in ABC.
  Instead, now the AccountSelectorAdapter loader loads the current
  account/mailbox name, and the unread count as extras.

- Now ABC.Callback.onMailboxSelected passed an account ID as well
  as a mailbox ID.

- There's new code paths that can cause the "fragment transaction in
  onLoadFinished" exception.  We need to fix this properly, but
  for now we just use commitAllowingStateLoss().

Bug 4948352

Change-Id: I73bb8b7530f8328ca1c86382ac0a54086ad061d7
This commit is contained in:
Makoto Onuki 2011-06-26 17:15:12 -07:00
parent ad6c48b2d3
commit 4689cb7100
16 changed files with 561 additions and 365 deletions

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
TextView for the collapsed account spinner.
TODO: Need to widen maxWidth dynamically when the mailbox name isn't shown
on the action bar.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="@dimen/action_bar_account_name_max_width"
style="@style/action_bar_account_name"
/>

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/action_bar_mailbox_name_left_margin"
android:orientation="horizontal"
>
<TextView
android:id="@+id/mailbox_name"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:maxWidth="@dimen/action_bar_mailbox_name_max_width"
android:textSize="18dp"
android:textColor="@color/text_ternary_color"
android:singleLine="true"
android:ellipsize="end"
android:gravity="left|center"
/>
<TextView
android:id="@+id/unread_count"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginLeft="16dp"
style="@style/action_bar_unread_count"
android:gravity="center"
/>
</LinearLayout>

View File

@ -26,8 +26,8 @@
android:layout_height="0dip" android:layout_height="0dip"
> >
<include <include
android:id="@+id/current_mailbox_container" android:id="@+id/account_spinner"
layout="@layout/action_bar_current_mailbox" layout="@layout/action_bar_spinner"
android:visibility="gone" android:visibility="gone"
/> />
<include <include

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/account_spinner_width"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="horizontal"
style="@android:style/Widget.Holo.Spinner"
>
<LinearLayout
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="vertical"
>
<TextView
android:id="@+id/spinner_line_1"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:textSize="16dip"
android:textColor="@color/text_primary_color"
android:singleLine="true"
android:ellipsize="end"
android:gravity="left|center_vertical"
/>
<TextView
android:id="@+id/spinner_line_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12dip"
android:textColor="@color/text_primary_color"
android:singleLine="true"
android:ellipsize="end"
android:gravity="left|center_vertical"
/>
</LinearLayout>
<TextView
android:id="@+id/spinner_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dip"
android:textSize="16dip"
android:textColor="@color/text_primary_color"
android:singleLine="true"
android:ellipsize="none"
android:gravity="center"
/>
</LinearLayout>

View File

@ -17,6 +17,8 @@
<!-- <!--
Not a RelativeLayout - we want to hide @id/email_address at runtime and then @id/display_name Not a RelativeLayout - we want to hide @id/email_address at runtime and then @id/display_name
should be center-vertical should be center-vertical
Popup width is set at runtime from @dimen/account_spinner_dropdown_width
--> -->
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
@ -25,12 +27,11 @@
android:orientation="horizontal" android:orientation="horizontal"
> >
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dip"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:maxWidth="@dimen/account_spinner_dropdown_account_name_max_width"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center|left" android:gravity="center|left"
> >
@ -38,14 +39,12 @@
android:id="@+id/display_name" android:id="@+id/display_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxWidth="@dimen/account_spinner_dropdown_account_name_max_width"
style="@style/action_bar_account_name" style="@style/action_bar_account_name"
/> />
<TextView <TextView
android:id="@+id/email_address" android:id="@+id/email_address"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxWidth="@dimen/account_spinner_dropdown_account_name_max_width"
style="@style/action_bar_account_name_secondary" style="@style/action_bar_account_name_secondary"
/> />
</LinearLayout> </LinearLayout>

View File

@ -53,7 +53,8 @@
<dimen name="setup_item_inset_left">0dip</dimen> <dimen name="setup_item_inset_left">0dip</dimen>
<dimen name="setup_item_inset_right">0dip</dimen> <dimen name="setup_item_inset_right">0dip</dimen>
<dimen name="account_spinner_dropdown_account_name_max_width">352dip</dimen> <dimen name="account_spinner_width">250dip</dimen>
<dimen name="account_spinner_dropdown_width">300dip</dimen>
<!-- MessageListItem --> <!-- MessageListItem -->
<dimen name="senders_font_size">14sp</dimen> <dimen name="senders_font_size">14sp</dimen>

View File

@ -1153,6 +1153,9 @@ save attachment.</string>
<!-- The hint used in the search box when searching a single mailbox [CHAR LIMIT=35] --> <!-- The hint used in the search box when searching a single mailbox [CHAR LIMIT=35] -->
<string name="search_mailbox_hint">Search <xliff:g example="Inbox">%1$s</xliff:g></string> <string name="search_mailbox_hint">Search <xliff:g example="Inbox">%1$s</xliff:g></string>
<!-- Title shown on the action bar on the mailbox list screen. [CHAR LIMIT=16] -->
<string name="action_bar_mailbox_list_title">Folders</string>
<!-- STOPSHIP Temporary UI DO NOT TRANSLATE--> <!-- STOPSHIP Temporary UI DO NOT TRANSLATE-->
<!-- In Mailbox setings, label for email check frequency selector --> <!-- In Mailbox setings, label for email check frequency selector -->
<string name="mailbox_options_check_frequency_label">Folder check frequency</string> <string name="mailbox_options_check_frequency_label">Folder check frequency</string>

View File

@ -71,10 +71,7 @@ public class FolderProperties {
return sInstance; return sInstance;
} }
/** public String getCombinedMailboxName(long mailboxId) {
* Lookup names of localized special mailboxes
*/
private String getDisplayName(int type, long mailboxId) {
// Special combined mailboxes // Special combined mailboxes
int resId = 0; int resId = 0;
@ -91,13 +88,21 @@ public class FolderProperties {
if (resId != 0) { if (resId != 0) {
return mContext.getString(resId); return mContext.getString(resId);
} }
if (type < mSpecialMailbox.length) {
return mSpecialMailbox[type];
}
return null; return null;
} }
/**
* Lookup names of localized special mailboxes
*/
private String getDisplayName(int type, long mailboxId) {
String name = getCombinedMailboxName(mailboxId);
if ((name == null) && (type < mSpecialMailbox.length)) {
name = mSpecialMailbox[type];
}
return name;
}
/** /**
* Return the display name for a mailbox for UI. For normal mailboxes, it just returns * Return the display name for a mailbox for UI. For normal mailboxes, it just returns
* {@code originalDisplayName}, but for special mailboxes (such as combined mailboxes) it * {@code originalDisplayName}, but for special mailboxes (such as combined mailboxes) it

View File

@ -24,12 +24,14 @@ import com.android.email.data.ClosingMatrixCursor;
import com.android.email.data.ThrottlingCursorLoader; import com.android.email.data.ThrottlingCursorLoader;
import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.MailboxColumns; import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.Utility; import com.android.emailcommon.utility.Utility;
import java.util.ArrayList; import java.util.ArrayList;
import android.content.ContentResolver;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Loader; import android.content.Loader;
@ -50,10 +52,16 @@ import android.widget.TextView;
public class AccountSelectorAdapter extends CursorAdapter { public class AccountSelectorAdapter extends CursorAdapter {
/** meta data column for an message count (unread or total, depending on row) */ /** meta data column for an message count (unread or total, depending on row) */
private static final String MESSAGE_COUNT = "unreadCount"; private static final String MESSAGE_COUNT = "unreadCount";
/** meta data column for the row type; used for display purposes */ /** meta data column for the row type; used for display purposes */
private static final String ROW_TYPE = "rowType"; private static final String ROW_TYPE = "rowType";
/** meta data position of the currently selected account in the drop-down list */ /** meta data position of the currently selected account in the drop-down list */
private static final String ACCOUNT_POSITION = "accountPosition"; private static final String ACCOUNT_POSITION = "accountPosition";
/** "account id" virtual column name for the matrix cursor */
private static final String ACCOUNT_ID = "accountId";
private static final int ROW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; private static final int ROW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final int ROW_TYPE_MAILBOX = 0; private static final int ROW_TYPE_MAILBOX = 0;
@ -77,6 +85,7 @@ public class AccountSelectorAdapter extends CursorAdapter {
Account.EMAIL_ADDRESS, Account.EMAIL_ADDRESS,
MESSAGE_COUNT, MESSAGE_COUNT,
ACCOUNT_POSITION, ACCOUNT_POSITION,
ACCOUNT_ID,
}; };
/** Sort order. Show the default account first. */ /** Sort order. Show the default account first. */
@ -92,8 +101,8 @@ public class AccountSelectorAdapter extends CursorAdapter {
* @param context a context * @param context a context
* @param accountId the ID of the currently viewed account * @param accountId the ID of the currently viewed account
*/ */
public static Loader<Cursor> createLoader(Context context, long accountId) { public static Loader<Cursor> createLoader(Context context, long accountId, long mailboxId) {
return new AccountsLoader(context, accountId, UiUtilities.useTwoPane(context)); return new AccountsLoader(context, accountId, mailboxId, UiUtilities.useTwoPane(context));
} }
public AccountSelectorAdapter(Context context) { public AccountSelectorAdapter(Context context) {
@ -103,22 +112,8 @@ public class AccountSelectorAdapter extends CursorAdapter {
} }
/** /**
* Invoked when the action bar needs the view of the text in the bar itself. The default * {@inheritDoc}
* is to show just the display name of the cursor at the given position. *
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (!isAccountItem(position)) {
// asked to show a recent mailbox; instead, show the account associated w/ the mailbox
int newPosition = getAccountPosition(position);
if (newPosition != UNKNOWN_POSITION) {
position = newPosition;
}
}
return super.getView(position, convertView, parent);
}
/**
* The account selector view can contain one of four types of row data: * The account selector view can contain one of four types of row data:
* <ol> * <ol>
* <li>headers</li> * <li>headers</li>
@ -132,26 +127,27 @@ public class AccountSelectorAdapter extends CursorAdapter {
* mailboxes display an unread count; whereas "show all folders" does not. To determine * mailboxes display an unread count; whereas "show all folders" does not. To determine
* if a particular row is "show all folders" verify that a) it's not an account row and * if a particular row is "show all folders" verify that a) it's not an account row and
* b) it's ID is {@link Mailbox#NO_MAILBOX}. * b) it's ID is {@link Mailbox#NO_MAILBOX}.
*
* TODO Use recycled views. ({@link #getViewTypeCount} and {@link #getItemViewType})
*/ */
@Override @Override
public View getDropDownView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
Cursor c = getCursor(); Cursor c = getCursor();
c.moveToPosition(position); c.moveToPosition(position);
View view; View view;
if (c.getInt(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) { if (c.getInt(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) {
view = mInflater.inflate(R.layout.account_selector_dropdown_header, parent, false); view = mInflater.inflate(R.layout.action_bar_spinner_dropdown_header, parent, false);
final TextView displayNameView = (TextView) view.findViewById(R.id.display_name); final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
final String displayName = getDisplayName(c); final String displayName = getDisplayName(c);
displayNameView.setText(displayName); displayNameView.setText(displayName);
} else { } else {
view = mInflater.inflate(R.layout.account_selector_dropdown, parent, false); view = mInflater.inflate(R.layout.action_bar_spinner_dropdown, parent, false);
final TextView displayNameView = (TextView) view.findViewById(R.id.display_name); final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address); final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address);
final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count); final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count);
final String displayName = getDisplayName(position); final String displayName = getDisplayName(c);
final String emailAddress = getAccountEmailAddress(position); final String emailAddress = getAccountEmailAddress(c);
displayNameView.setText(displayName); displayNameView.setText(displayName);
@ -163,12 +159,12 @@ public class AccountSelectorAdapter extends CursorAdapter {
emailAddressView.setText(emailAddress); emailAddressView.setText(emailAddress);
} }
boolean isAccount = isAccountItem(position); boolean isAccount = isAccountItem(c);
long id = getId(c); long id = getId(c);
if (isAccount || id != Mailbox.NO_MAILBOX) { if (isAccount || id != Mailbox.NO_MAILBOX) {
unreadCountView.setVisibility(View.VISIBLE); unreadCountView.setVisibility(View.VISIBLE);
unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext, unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext,
getAccountUnreadCount(position), true)); getAccountUnreadCount(c), true));
} else { } else {
unreadCountView.setVisibility(View.INVISIBLE); unreadCountView.setVisibility(View.INVISIBLE);
} }
@ -177,14 +173,13 @@ public class AccountSelectorAdapter extends CursorAdapter {
} }
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
TextView textView = (TextView) view.findViewById(R.id.display_name); return null; // we don't reuse views. This method never gets called.
textView.setText(getDisplayName(cursor));
} }
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public void bindView(View view, Context context, Cursor cursor) {
return mInflater.inflate(R.layout.account_selector, parent, false); // we don't reuse views. This method never gets called.
} }
@Override @Override
@ -209,6 +204,10 @@ public class AccountSelectorAdapter extends CursorAdapter {
public boolean isAccountItem(int position) { public boolean isAccountItem(int position) {
Cursor c = getCursor(); Cursor c = getCursor();
c.moveToPosition(position); c.moveToPosition(position);
return isAccountItem(c);
}
public boolean isAccountItem(Cursor c) {
return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_ACCOUNT); return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_ACCOUNT);
} }
@ -218,24 +217,8 @@ public class AccountSelectorAdapter extends CursorAdapter {
return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_MAILBOX); return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_MAILBOX);
} }
private String getDisplayName(int position) { private int getAccountUnreadCount(Cursor c) {
final Cursor c = getCursor(); return getMessageCount(c);
return c.moveToPosition(position) ? getDisplayName(c) : null;
}
private String getAccountEmailAddress(int position) {
final Cursor c = getCursor();
return c.moveToPosition(position) ? getAccountEmailAddress(c) : null;
}
private int getAccountUnreadCount(int position) {
final Cursor c = getCursor();
return c.moveToPosition(position) ? getMessageCount(c) : 0;
}
int getAccountPosition(int position) {
final Cursor c = getCursor();
return c.moveToPosition(position) ? getAccountPosition(c) : UNKNOWN_POSITION;
} }
/** /**
@ -245,6 +228,24 @@ public class AccountSelectorAdapter extends CursorAdapter {
return c.getLong(c.getColumnIndex(EmailContent.RECORD_ID)); return c.getLong(c.getColumnIndex(EmailContent.RECORD_ID));
} }
/**
* @return ID of the account / mailbox for a row
*/
public long getId(int position) {
final Cursor c = getCursor();
return c.moveToPosition(position) ? getId(c) : Account.NO_ACCOUNT;
}
/**
* @return ID of the account for a row
*/
public long getAccountId(int position) {
final Cursor c = getCursor();
return c.moveToPosition(position)
? c.getLong(c.getColumnIndex(ACCOUNT_ID))
: Account.NO_ACCOUNT;
}
/** Returns the account name extracted from the given cursor. */ /** Returns the account name extracted from the given cursor. */
static String getDisplayName(Cursor cursor) { static String getDisplayName(Cursor cursor) {
return cursor.getString(cursor.getColumnIndex(Account.DISPLAY_NAME)); return cursor.getString(cursor.getColumnIndex(Account.DISPLAY_NAME));
@ -263,9 +264,13 @@ public class AccountSelectorAdapter extends CursorAdapter {
return cursor.getInt(cursor.getColumnIndex(MESSAGE_COUNT)); return cursor.getInt(cursor.getColumnIndex(MESSAGE_COUNT));
} }
/** Returns the account position extracted from the given cursor. */ private static String sCombinedViewDisplayName;
private static int getAccountPosition(Cursor cursor) { private static String getCombinedViewDisplayName(Context c) {
return cursor.getInt(cursor.getColumnIndex(ACCOUNT_POSITION)); if (sCombinedViewDisplayName == null) {
sCombinedViewDisplayName = c.getResources().getString(
R.string.mailbox_list_account_selector_combined_view);
}
return sCombinedViewDisplayName;
} }
/** /**
@ -278,16 +283,18 @@ public class AccountSelectorAdapter extends CursorAdapter {
static class AccountsLoader extends ThrottlingCursorLoader { static class AccountsLoader extends ThrottlingCursorLoader {
private final Context mContext; private final Context mContext;
private final long mAccountId; private final long mAccountId;
private final long mMailboxId;
private final boolean mUseTwoPane; // Injectable for test private final boolean mUseTwoPane; // Injectable for test
private final FolderProperties mFolderProperties; private final FolderProperties mFolderProperties;
@VisibleForTesting @VisibleForTesting
AccountsLoader(Context context, long accountId, boolean useTwoPane) { AccountsLoader(Context context, long accountId, long mailboxId, boolean useTwoPane) {
// Super class loads a regular account cursor, but we replace it in loadInBackground(). // Super class loads a regular account cursor, but we replace it in loadInBackground().
super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, super(context, Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null,
ORDER_BY); ORDER_BY);
mContext = context; mContext = context;
mAccountId = accountId; mAccountId = accountId;
mMailboxId = mailboxId;
mFolderProperties = FolderProperties.getInstance(mContext); mFolderProperties = FolderProperties.getInstance(mContext);
mUseTwoPane = useTwoPane; mUseTwoPane = useTwoPane;
} }
@ -300,17 +307,19 @@ public class AccountSelectorAdapter extends CursorAdapter {
= new CursorWithExtras(ADAPTER_PROJECTION, accountsCursor); = new CursorWithExtras(ADAPTER_PROJECTION, accountsCursor);
final int accountPosition = addAccountsToCursor(resultCursor, accountsCursor); final int accountPosition = addAccountsToCursor(resultCursor, accountsCursor);
addRecentsToCursor(resultCursor, accountPosition); addRecentsToCursor(resultCursor, accountPosition);
return Utility.CloseTraceCursorWrapper.get(resultCursor);
resultCursor.setAccountMailboxInfo(getContext(), mAccountId, mMailboxId);
return resultCursor;
} }
/** Adds the account list [with extra meta data] to the given matrix cursor */ /** Adds the account list [with extra meta data] to the given matrix cursor */
private int addAccountsToCursor(CursorWithExtras matrixCursor, Cursor accountCursor) { private int addAccountsToCursor(CursorWithExtras matrixCursor, Cursor accountCursor) {
int accountPosition = UNKNOWN_POSITION; int accountPosition = UNKNOWN_POSITION;
accountCursor.moveToPosition(-1); accountCursor.moveToPosition(-1);
// Add a header for the accounts // Add a header for the accounts
String header = addHeaderRow(matrixCursor, mContext.getString(
mContext.getString(R.string.mailbox_list_account_selector_account_header); R.string.mailbox_list_account_selector_account_header));
addRow(matrixCursor, ROW_TYPE_HEADER, 0L, header, null, 0, UNKNOWN_POSITION);
matrixCursor.mAccountCount = accountCursor.getCount(); matrixCursor.mAccountCount = accountCursor.getCount();
int totalUnread = 0; int totalUnread = 0;
@ -323,7 +332,7 @@ public class AccountSelectorAdapter extends CursorAdapter {
final String name = getDisplayName(accountCursor); final String name = getDisplayName(accountCursor);
final String emailAddress = getAccountEmailAddress(accountCursor); final String emailAddress = getAccountEmailAddress(accountCursor);
addRow(matrixCursor, ROW_TYPE_ACCOUNT, accountId, name, emailAddress, unread, addRow(matrixCursor, ROW_TYPE_ACCOUNT, accountId, name, emailAddress, unread,
UNKNOWN_POSITION); UNKNOWN_POSITION, accountId);
totalUnread += unread; totalUnread += unread;
if (accountId == mAccountId) { if (accountId == mAccountId) {
accountPosition = currentPosition; accountPosition = currentPosition;
@ -333,12 +342,12 @@ public class AccountSelectorAdapter extends CursorAdapter {
// Add "combined view" if more than one account exists // Add "combined view" if more than one account exists
final int countAccounts = accountCursor.getCount(); final int countAccounts = accountCursor.getCount();
if (countAccounts > 1) { if (countAccounts > 1) {
final String name = mContext.getResources().getString(
R.string.mailbox_list_account_selector_combined_view);
final String accountCount = mContext.getResources().getQuantityString( final String accountCount = mContext.getResources().getQuantityString(
R.plurals.number_of_accounts, countAccounts, countAccounts); R.plurals.number_of_accounts, countAccounts, countAccounts);
addRow(matrixCursor, ROW_TYPE_ACCOUNT, Account.ACCOUNT_ID_COMBINED_VIEW, addRow(matrixCursor, ROW_TYPE_ACCOUNT, Account.ACCOUNT_ID_COMBINED_VIEW,
name, accountCount, totalUnread, UNKNOWN_POSITION); getCombinedViewDisplayName(mContext),
accountCount, totalUnread, UNKNOWN_POSITION,
Account.ACCOUNT_ID_COMBINED_VIEW);
// Increment the account count for the combined account. // Increment the account count for the combined account.
matrixCursor.mAccountCount++; matrixCursor.mAccountCount++;
@ -373,9 +382,8 @@ public class AccountSelectorAdapter extends CursorAdapter {
if (!mUseTwoPane) { if (!mUseTwoPane) {
// "Recent mailboxes" header // "Recent mailboxes" header
String mailboxHeader = mContext.getString( addHeaderRow(matrixCursor, mContext.getString(
R.string.mailbox_list_account_selector_mailbox_header_fmt, emailAddress); R.string.mailbox_list_account_selector_mailbox_header_fmt, emailAddress));
addRow(matrixCursor, ROW_TYPE_HEADER, 0L, mailboxHeader, null, 0, UNKNOWN_POSITION);
} }
if (recentCount > 0) { if (recentCount > 0) {
@ -389,7 +397,7 @@ public class AccountSelectorAdapter extends CursorAdapter {
String name = mContext.getString( String name = mContext.getString(
R.string.mailbox_list_account_selector_show_all_folders); R.string.mailbox_list_account_selector_show_all_folders);
addRow(matrixCursor, ROW_TYPE_MAILBOX, Mailbox.NO_MAILBOX, name, null, 0, addRow(matrixCursor, ROW_TYPE_MAILBOX, Mailbox.NO_MAILBOX, name, null, 0,
accountPosition); accountPosition, mAccountId);
} }
} }
@ -408,33 +416,114 @@ public class AccountSelectorAdapter extends CursorAdapter {
} }
addRow(matrixCursor, ROW_TYPE_MAILBOX, mailboxId, addRow(matrixCursor, ROW_TYPE_MAILBOX, mailboxId,
mFolderProperties.getDisplayName(c), null, mFolderProperties.getDisplayName(c), null,
mFolderProperties.getMessageCount(c), accountPosition); mFolderProperties.getMessageCount(c), accountPosition, mAccountId);
}
private void addHeaderRow(MatrixCursor cursor, String name) {
addRow(cursor, ROW_TYPE_HEADER, 0L, name, null, 0, UNKNOWN_POSITION,
Account.NO_ACCOUNT);
} }
/** Adds a row to the given cursor */ /** Adds a row to the given cursor */
private void addRow(MatrixCursor cursor, int rowType, long id, String name, private void addRow(MatrixCursor cursor, int rowType, long id, String name,
String emailAddress, int messageCount, int listPosition) { String emailAddress, int messageCount, int listPosition, long accountId) {
cursor.newRow() cursor.newRow()
.add(rowType) .add(rowType)
.add(id) .add(id)
.add(name) .add(name)
.add(emailAddress) .add(emailAddress)
.add(messageCount) .add(messageCount)
.add(listPosition); .add(listPosition)
.add(accountId);
} }
} }
/** Cursor with some extra meta data. */ /** Cursor with some extra meta data. */
static class CursorWithExtras extends ClosingMatrixCursor { static class CursorWithExtras extends ClosingMatrixCursor {
/** Number of account elements, including the combined account row. */ /** Number of account elements, including the combined account row. */
private int mAccountCount; private int mAccountCount;
/** Number of recent mailbox elements */ /** Number of recent mailbox elements */
private int mRecentCount; private int mRecentCount;
private boolean mAccountExists;
/**
* Account ID that's loaded.
*/
private long mAccountId;
private String mAccountDisplayName;
/**
* Mailbox ID that's loaded.
*/
private long mMailboxId;
private String mMailboxDisplayName;
private int mMailboxMessageCount;
private CursorWithExtras(String[] columnNames, Cursor innerCursor) { private CursorWithExtras(String[] columnNames, Cursor innerCursor) {
super(columnNames, innerCursor); super(columnNames, innerCursor);
} }
private static final String[] ACCOUNT_INFO_PROJECTION = new String[] {
AccountColumns.DISPLAY_NAME,
};
private static final String[] MAILBOX_INFO_PROJECTION = new String[] {
MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE,
MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT
};
/**
* Set the current account/mailbox info.
*/
private void setAccountMailboxInfo(Context context, long accountId, long mailboxId) {
mAccountId = accountId;
mMailboxId = mailboxId;
// Get account info
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
// We need to treat ACCOUNT_ID_COMBINED_VIEW specially...
mAccountExists = true;
mAccountDisplayName = getCombinedViewDisplayName(context);
mMailboxDisplayName = FolderProperties.getInstance(context)
.getCombinedMailboxName(mMailboxId);
// TODO Would be nicer to show message count for combined mailboxes too..
mMailboxMessageCount = 0;
return;
}
mAccountDisplayName = Utility.getFirstRowString(context,
ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
ACCOUNT_INFO_PROJECTION, null, null, null, 0, null);
if (mAccountDisplayName == null) {
// Account gone!
mAccountExists = false;
return;
}
mAccountExists = true;
// If mailbox not specified, done.
if (mMailboxId == Mailbox.NO_MAILBOX) {
return;
}
// Get mailbox info
final ContentResolver r = context.getContentResolver();
final Cursor mailboxCursor = r.query(
ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
MAILBOX_INFO_PROJECTION, null, null, null);
try {
if (mailboxCursor.moveToFirst()) {
final FolderProperties fp = FolderProperties.getInstance(context);
mMailboxDisplayName = fp.getDisplayName(mailboxCursor);
mMailboxMessageCount = fp.getMessageCount(mailboxCursor);
}
} finally {
mailboxCursor.close();
}
}
/** /**
* Returns the cursor position of the item with the given ID. Or {@link #UNKNOWN_POSITION} * Returns the cursor position of the item with the given ID. Or {@link #UNKNOWN_POSITION}
* if the given ID does not exist. * if the given ID does not exist.
@ -456,5 +545,32 @@ public class AccountSelectorAdapter extends CursorAdapter {
public int getRecentMailboxCount() { public int getRecentMailboxCount() {
return mRecentCount; return mRecentCount;
} }
public long getAccountId() {
return mAccountId;
}
public String getAccountDisplayName() {
return mAccountDisplayName;
}
public long getMailboxId() {
return mMailboxId;
}
public String getMailboxDisplayName() {
return mMailboxDisplayName;
}
public int getMailboxMessageCount() {
return mMailboxMessageCount;
}
/**
* @return {@code true} if the specified accuont exists.
*/
public boolean accountExists() {
return mAccountExists;
}
} }
} }

View File

@ -16,30 +16,29 @@
package com.android.email.activity; package com.android.email.activity;
import com.android.email.R;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.Utility;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.LoaderManager; import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks; import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Loader; import android.content.Loader;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListPopupWindow;
import android.widget.ListView;
import android.widget.SearchView; import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import com.android.email.FolderProperties;
import com.android.email.R;
import com.android.email.data.ThrottlingCursorLoader;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.Mailbox;
/** /**
* Manages the account name and the custom view part on the action bar. * Manages the account name and the custom view part on the action bar.
* *
@ -68,25 +67,29 @@ public class ActionBarController {
private static final int LOADER_ID_ACCOUNT_LIST private static final int LOADER_ID_ACCOUNT_LIST
= EmailActivity.ACTION_BAR_CONTROLLER_LOADER_ID_BASE + 0; = EmailActivity.ACTION_BAR_CONTROLLER_LOADER_ID_BASE + 0;
private static final int LOADER_ID_MAILBOX
= EmailActivity.ACTION_BAR_CONTROLLER_LOADER_ID_BASE + 1;
private final Context mContext; private final Context mContext;
private final LoaderManager mLoaderManager; private final LoaderManager mLoaderManager;
private final ActionBar mActionBar; private final ActionBar mActionBar;
/** "Folders" label shown with account name on 1-pane mailbox list */
private final String mAllFoldersLabel;
private final View mActionBarCustomView; private final View mActionBarCustomView;
private final View mMailboxNameContainer; private final View mAccountSpinner;
private final TextView mMailboxNameView; private final TextView mAccountSpinnerLine1View;
private final TextView mUnreadCountView; private final TextView mAccountSpinnerLine2View;
private final TextView mAccountSpinnerCountView;
private final View mSearchContainer; private final View mSearchContainer;
private final SearchView mSearchView; private final SearchView mSearchView;
private final ActionBarNavigationCallback mActionBarNavigationCallback = private final AccountDropdownPopup mAccountDropdown;
new ActionBarNavigationCallback();
private final AccountSelectorAdapter mAccountsSelectorAdapter; private final AccountSelectorAdapter mAccountsSelectorAdapter;
private AccountSelectorAdapter.CursorWithExtras mAccountCursor;
private AccountSelectorAdapter.CursorWithExtras mCursor;
/** The current account ID; used to determine if the account has changed. */ /** The current account ID; used to determine if the account has changed. */
private long mLastAccountIdForDirtyCheck = Account.NO_ACCOUNT; private long mLastAccountIdForDirtyCheck = Account.NO_ACCOUNT;
@ -103,6 +106,20 @@ public class ActionBarController {
} }
public interface Callback { public interface Callback {
/** Values for {@link #getTitleMode}. Show only account name */
public static final int TITLE_MODE_ACCOUNT_NAME_ONLY = 0;
/** Show the current account name with "Folders" */
public static final int TITLE_MODE_ACCOUNT_WITH_ALL_FOLDERS_LABEL = 1;
/** Show the current account name and the current mailbox name */
public static final int TITLE_MODE_ACCOUNT_WITH_MAILBOX = 2;
/**
* Show the current message subject. Actual subject is obtained via
* {@link #getMessageSubject()}.
*
* NOT IMPLEMENTED YET
*/
public static final int TITLE_MODE_MESSAGE_SUBJECT = 3;
/** @return true if an account is selected. */ /** @return true if an account is selected. */
public boolean isAccountSelected(); public boolean isAccountSelected();
@ -118,8 +135,13 @@ public class ActionBarController {
*/ */
public long getMailboxId(); public long getMailboxId();
/** @return true if the current mailbox name should be shown. */ /**
public boolean shouldShowMailboxName(); * @return constants such as {@link #TITLE_MODE_ACCOUNT_NAME_ONLY}.
*/
public int getTitleMode();
/** @see #TITLE_MODE_MESSAGE_SUBJECT */
public String getMessageSubject();
/** @return the "UP" arrow should be shown. */ /** @return the "UP" arrow should be shown. */
public boolean shouldShowUp(); public boolean shouldShowUp();
@ -132,10 +154,12 @@ public class ActionBarController {
/** /**
* Invoked when a recent mailbox is selected on the account spinner. * Invoked when a recent mailbox is selected on the account spinner.
*
* @param accountId ID of the selected account, or {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
* @param mailboxId The ID of the selected mailbox, or {@link Mailbox#NO_MAILBOX} if the * @param mailboxId The ID of the selected mailbox, or {@link Mailbox#NO_MAILBOX} if the
* special option "show all mailboxes" was selected. * special option "show all mailboxes" was selected.
*/ */
public void onMailboxSelected(long mailboxId); public void onMailboxSelected(long accountId, long mailboxId);
/** Called when no accounts are found in the database. */ /** Called when no accounts are found in the database. */
public void onNoAccountsFound(); public void onNoAccountsFound();
@ -159,42 +183,58 @@ public class ActionBarController {
mLoaderManager = loaderManager; mLoaderManager = loaderManager;
mActionBar = actionBar; mActionBar = actionBar;
mCallback = callback; mCallback = callback;
mAllFoldersLabel = mContext.getResources().getString(
R.string.action_bar_mailbox_list_title);
mAccountsSelectorAdapter = new AccountSelectorAdapter(mContext); mAccountsSelectorAdapter = new AccountSelectorAdapter(mContext);
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE // Configure action bar.
| ActionBar.DISPLAY_SHOW_HOME mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_CUSTOM);
| ActionBar.DISPLAY_SHOW_CUSTOM);
// Prepare the custom view // Prepare the custom view
final LayoutInflater inflater = LayoutInflater.from(mContext); final LayoutInflater inflater = LayoutInflater.from(mContext);
mActionBarCustomView = inflater.inflate(R.layout.action_bar_custom_view, null); mActionBarCustomView = inflater.inflate(R.layout.action_bar_custom_view, null);
final ActionBar.LayoutParams customViewLayout = new ActionBar.LayoutParams( final ActionBar.LayoutParams customViewLayout = new ActionBar.LayoutParams(
ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT,
ActionBar.LayoutParams.MATCH_PARENT); ActionBar.LayoutParams.MATCH_PARENT);
customViewLayout.setMargins(0 , 0, 0, 0); customViewLayout.setMargins(0, 0, 0, 0);
mActionBar.setCustomView(mActionBarCustomView, customViewLayout); mActionBar.setCustomView(mActionBarCustomView, customViewLayout);
// Mailbox name / unread count // Account spinner
mMailboxNameContainer = UiUtilities.getView(mActionBarCustomView, mAccountSpinner = UiUtilities.getView(mActionBarCustomView, R.id.account_spinner);
R.id.current_mailbox_container);
mMailboxNameView = UiUtilities.getView(mMailboxNameContainer, R.id.mailbox_name); mAccountSpinnerLine1View = UiUtilities.getView(mActionBarCustomView, R.id.spinner_line_1);
mUnreadCountView = UiUtilities.getView(mMailboxNameContainer, R.id.unread_count); mAccountSpinnerLine2View = UiUtilities.getView(mActionBarCustomView, R.id.spinner_line_2);
mAccountSpinnerCountView = UiUtilities.getView(mActionBarCustomView, R.id.spinner_count);
// Search // Search
mSearchContainer = UiUtilities.getView(mActionBarCustomView, R.id.search_container); mSearchContainer = UiUtilities.getView(mActionBarCustomView, R.id.search_container);
mSearchView = UiUtilities.getView(mSearchContainer, R.id.search_view); mSearchView = UiUtilities.getView(mSearchContainer, R.id.search_view);
mSearchView.setSubmitButtonEnabled(true); mSearchView.setSubmitButtonEnabled(true);
mSearchView.setOnQueryTextListener(mOnQueryText); mSearchView.setOnQueryTextListener(mOnQueryText);
// Account dropdown
mAccountDropdown = new AccountDropdownPopup(mContext);
mAccountDropdown.setAdapter(mAccountsSelectorAdapter);
mAccountSpinner.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
if (mAccountsSelectorAdapter.getCount() > 0) {
mAccountDropdown.show();
}
}
});
} }
/** Must be called from {@link UIControllerBase#onActivityCreated()} */ /** Must be called from {@link UIControllerBase#onActivityCreated()} */
public void onActivityCreated() { public void onActivityCreated() {
loadAccounts();
refresh(); refresh();
} }
/** Must be called from {@link UIControllerBase#onActivityDestroy()} */ /** Must be called from {@link UIControllerBase#onActivityDestroy()} */
public void onActivityDestroy() { public void onActivityDestroy() {
if (mAccountDropdown.isShowing()) {
mAccountDropdown.dismiss();
}
} }
/** Must be called from {@link UIControllerBase#onSaveInstanceState} */ /** Must be called from {@link UIControllerBase#onSaveInstanceState} */
@ -234,11 +274,6 @@ public class ActionBarController {
} }
mSearchMode = MODE_SEARCH; mSearchMode = MODE_SEARCH;
// Need to force it to mode "standard" to hide it.
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
mActionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
mSearchContainer.setVisibility(View.VISIBLE);
// Focus on the search input box and throw up the IME if specified. // Focus on the search input box and throw up the IME if specified.
// TODO: HACK. this is a workaround IME not popping up. // TODO: HACK. this is a workaround IME not popping up.
mSearchView.setIconified(false); mSearchView.setIconified(false);
@ -251,11 +286,6 @@ public class ActionBarController {
return; return;
} }
mSearchMode = MODE_NORMAL; mSearchMode = MODE_NORMAL;
mSearchContainer.setVisibility(View.GONE);
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
// Force update of account list when we exit search.
updateAccountList();
refresh(); refresh();
mCallback.onSearchExit(); mCallback.onSearchExit();
@ -277,187 +307,141 @@ public class ActionBarController {
/** Refreshes the action bar display. */ /** Refreshes the action bar display. */
public void refresh() { public void refresh() {
// The actual work is in refreshInernal(), but we don't call it directly here, because:
// 1. refresh() is called very often.
// 2. to avoid nested fragment transaction.
// refresh is often called during a fragment transaction, but updateTitle() may call
// a callback which would initiate another fragment transaction.
final Handler h = Utility.getMainThreadHandler();
h.removeCallbacks(mRefreshRunnable);
h.post(mRefreshRunnable);
}
private final Runnable mRefreshRunnable = new Runnable() {
@Override public void run() {
refreshInernal();
}
};
private void refreshInernal() {
final boolean showUp = isInSearchMode() || mCallback.shouldShowUp(); final boolean showUp = isInSearchMode() || mCallback.shouldShowUp();
mActionBar.setDisplayOptions(showUp mActionBar.setDisplayOptions(showUp
? ActionBar.DISPLAY_HOME_AS_UP : 0, ActionBar.DISPLAY_HOME_AS_UP); ? ActionBar.DISPLAY_HOME_AS_UP : 0, ActionBar.DISPLAY_HOME_AS_UP);
if (isInSearchMode()) { final long accountId = mCallback.getUIAccountId();
mMailboxNameContainer.setVisibility(View.GONE);
} else {
mMailboxNameContainer.setVisibility(mCallback.shouldShowMailboxName()
? View.VISIBLE : View.GONE);
}
// Update the account list only when the account has changed.
if (mLastAccountIdForDirtyCheck != mCallback.getUIAccountId()) {
mLastAccountIdForDirtyCheck = mCallback.getUIAccountId();
// If the selected account changes, reload the cursor to update the recent mailboxes
if (mLastAccountIdForDirtyCheck != Account.NO_ACCOUNT) {
mLoaderManager.destroyLoader(LOADER_ID_ACCOUNT_LIST);
loadAccounts();
} else {
updateAccountList();
}
}
// Update current mailbox info
final long mailboxId = mCallback.getMailboxId(); final long mailboxId = mCallback.getMailboxId();
if (mailboxId == Mailbox.NO_MAILBOX) { if ((mLastAccountIdForDirtyCheck != accountId)
clearMailboxInfo(); || (mLastMailboxIdForDirtyCheck != mailboxId)) {
} else { mLastAccountIdForDirtyCheck = accountId;
if (mLastMailboxIdForDirtyCheck != mailboxId) { mLastMailboxIdForDirtyCheck = mailboxId;
mLastMailboxIdForDirtyCheck = mailboxId;
loadMailboxInfo(mailboxId); if (accountId != Account.NO_ACCOUNT) {
loadAccountMailboxInfo(accountId, mailboxId);
} }
} }
updateTitle();
} }
/** /**
* Load account cursor, and update the action bar. * Load account/mailbox info, and account/recent mailbox list.
*/ */
private void loadAccounts() { private void loadAccountMailboxInfo(final long accountId, final long mailboxId) {
mLoaderManager.initLoader(LOADER_ID_ACCOUNT_LIST, null, mLoaderManager.restartLoader(LOADER_ID_ACCOUNT_LIST, null,
new LoaderCallbacks<Cursor>() { new LoaderCallbacks<Cursor>() {
@Override @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return AccountSelectorAdapter.createLoader(mContext, mCallback.getUIAccountId()); return AccountSelectorAdapter.createLoader(mContext, accountId, mailboxId);
} }
@Override @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAccountCursor = (AccountSelectorAdapter.CursorWithExtras) data; mCursor = (AccountSelectorAdapter.CursorWithExtras) data;
updateAccountList(); updateTitle();
} }
@Override @Override
public void onLoaderReset(Loader<Cursor> loader) { public void onLoaderReset(Loader<Cursor> loader) {
mAccountCursor = null; mCursor = null;
updateAccountList(); updateTitle();
} }
}); });
} }
/** /**
* Called when the LOADER_ID_ACCOUNT_LIST loader loads the data. Update the account spinner * Update the "title" part.
* on the action bar.
*/ */
private void updateAccountList() { private void updateTitle() {
mAccountsSelectorAdapter.swapCursor(mAccountCursor); mAccountsSelectorAdapter.swapCursor(mCursor);
if (mSearchMode == MODE_SEARCH) { if (mCursor == null) {
// In search mode, so we don't care about the account list - it'll get updated when // Initial load not finished.
// it goes visible again. mActionBarCustomView.setVisibility(View.GONE);
return; return;
} }
mActionBarCustomView.setVisibility(View.VISIBLE);
final ActionBar ab = mActionBar; if (mCursor.getAccountCount() == 0) {
if (mAccountCursor == null) {
// Cursor not ready or closed.
ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
return;
}
final int count = mAccountCursor.getAccountCount() + mAccountCursor.getRecentMailboxCount();
if (count == 0) {
mCallback.onNoAccountsFound(); mCallback.onNoAccountsFound();
return; return;
} }
// If only one account, don't show the drop down. if ((mCursor.getAccountId() != Account.NO_ACCOUNT) && !mCursor.accountExists()) {
int selectedPosition = mAccountCursor.getPosition(mCallback.getUIAccountId()); // Accoutn specified, but not exists. Switch to the default account.
if (count == 1) { mCallback.onAccountSelected(Account.getDefaultAccountId(mContext));
// Show the account name as the title.
ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); // STOPSHIP If in search mode, we should close the activity. Probably
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); // we should jsut call onSearchExit() instead?
if (selectedPosition >= 0) {
mAccountCursor.moveToPosition(selectedPosition);
ab.setTitle(AccountSelectorAdapter.getDisplayName(mAccountCursor));
}
return; return;
} }
// Update the drop down list. if (mSearchMode == MODE_SEARCH) {
if (ab.getNavigationMode() != ActionBar.NAVIGATION_MODE_LIST) { // In search mode, so we don't care about the account list - it'll get updated when
ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); // it goes visible again.
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); mAccountSpinner.setVisibility(View.GONE);
ab.setListNavigationCallbacks(mAccountsSelectorAdapter, mActionBarNavigationCallback); mSearchContainer.setVisibility(View.VISIBLE);
return;
} }
// Find the currently selected account, and select it.
if (selectedPosition >= 0) { final int mTitleMode = mCallback.getTitleMode();
ab.setSelectedNavigationItem(selectedPosition);
// TODO Handle TITLE_MODE_MESSAGE_SUBJECT
// Account spinner visible.
mAccountSpinner.setVisibility(View.VISIBLE);
mSearchContainer.setVisibility(View.GONE);
// Get mailbox name
final String mailboxName;
if (mTitleMode == Callback.TITLE_MODE_ACCOUNT_WITH_ALL_FOLDERS_LABEL) {
mailboxName = mAllFoldersLabel;
} else if (mTitleMode == Callback.TITLE_MODE_ACCOUNT_WITH_MAILBOX) {
mailboxName = mCursor.getMailboxDisplayName();
} else {
mailboxName = null;
} }
}
private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener { if (TextUtils.isEmpty(mailboxName)) {
@Override mAccountSpinnerLine1View.setText(mCursor.getAccountDisplayName());
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
if (mAccountsSelectorAdapter.isAccountItem(itemPosition) // Only here we change the visibility of line 2, so line 1 will be vertically-centered.
&& itemId != mCallback.getUIAccountId()) { mAccountSpinnerLine2View.setVisibility(View.GONE);
mCallback.onAccountSelected(itemId); } else {
} else if (mAccountsSelectorAdapter.isMailboxItem(itemPosition)) { mAccountSpinnerLine1View.setText(mailboxName);
mCallback.onMailboxSelected(itemId); mAccountSpinnerLine2View.setVisibility(View.VISIBLE); // Make sure it's visible again.
// We need to update the selection, otherwise the user is unable to select the mAccountSpinnerLine2View.setText(mCursor.getAccountDisplayName());
// recent folder a second time w/o first selecting another item in the spinner
int selectedPosition = mAccountsSelectorAdapter.getAccountPosition(itemPosition);
if (selectedPosition != AccountSelectorAdapter.UNKNOWN_POSITION) {
mActionBar.setSelectedNavigationItem(selectedPosition);
}
} else {
Log.i(Logging.LOG_TAG,
"Invalid type selected in ActionBarController at index " + itemPosition);
}
return true;
} }
} mAccountSpinnerCountView.setText(UiUtilities.getMessageCountForUi(
mContext, mCursor.getMailboxMessageCount(), true));
private static final String[] MAILBOX_NAME_COUNT_PROJECTION = new String[] { boolean spinnerEnabled = (mCursor.getAccountCount() + mCursor.getRecentMailboxCount()) > 1;
MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE,
MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT
};
private void loadMailboxInfo(final long mailboxId) { if (spinnerEnabled) {
clearMailboxInfo(); mAccountSpinner.setClickable(true);
if (mailboxId < 0) { } else {
// TODO FIXME mAccountSpinner.setClickable(false);
return; // Can't get combined mailbox name with this // TODO There's nothing to select -- we should remove the spinner triangle.
// (The small triangle shown at the right bottom corner)
} }
mLoaderManager.restartLoader(LOADER_ID_MAILBOX, null,
new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new ThrottlingCursorLoader(mContext,
ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId),
MAILBOX_NAME_COUNT_PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (!cursor.moveToFirst()) {
clearMailboxInfo();
return;
}
// Update action bar
FolderProperties fp = FolderProperties.getInstance(mContext);
updateMailboxInfo(
fp.getDisplayName(cursor),
fp.getMessageCount(cursor)
);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
});
}
private void clearMailboxInfo() {
updateMailboxInfo("", 0);
}
private void updateMailboxInfo(String mailboxName, int count) {
mMailboxNameView.setText(mailboxName);
mUnreadCountView.setText(UiUtilities.getMessageCountForUi(mContext, count, true));
} }
private final SearchView.OnQueryTextListener mOnQueryText private final SearchView.OnQueryTextListener mOnQueryText
@ -475,4 +459,44 @@ public class ActionBarController {
} }
}; };
private void onAccountSpinnerItemClicked(int position) {
if (mAccountsSelectorAdapter == null) { // just in case...
return;
}
final long accountId = mAccountsSelectorAdapter.getAccountId(position);
if (mAccountsSelectorAdapter.isAccountItem(position)) {
mCallback.onAccountSelected(accountId);
} else if (mAccountsSelectorAdapter.isMailboxItem(position)) {
mCallback.onMailboxSelected(accountId,
mAccountsSelectorAdapter.getId(position));
}
}
// Based on Spinner.DropdownPopup
private class AccountDropdownPopup extends ListPopupWindow {
public AccountDropdownPopup(Context context) {
super(context);
setAnchorView(mAccountSpinner);
setModal(true);
setPromptPosition(POSITION_PROMPT_ABOVE);
setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
onAccountSpinnerItemClicked(position);
dismiss();
}
});
}
@Override
public void show() {
setWidth(mContext.getResources().getDimensionPixelSize(
R.dimen.account_spinner_dropdown_width));
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
// List view is instantiated in super.show(), so we need to do this after...
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
}
} }

View File

@ -190,6 +190,11 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
private long mParentMailboxId; private long mParentMailboxId;
private long mHighlightedMailboxId; private long mHighlightedMailboxId;
/**
* Becomes {@code true} once we determine which mailbox to use as the parent.
*/
private boolean mParentDetermined;
/** /**
* ID of the mailbox that should be highlighted when the next cursor is loaded. * ID of the mailbox that should be highlighted when the next cursor is loaded.
*/ */
@ -551,7 +556,7 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
/** /**
* @return {@code true} if top-level mailboxes are shown. {@code false} otherwise. * @return {@code true} if top-level mailboxes are shown. {@code false} otherwise.
*/ */
public boolean isRoot() { private boolean isRoot() {
return mParentMailboxId == Mailbox.NO_MAILBOX; return mParentMailboxId == Mailbox.NO_MAILBOX;
} }
@ -580,6 +585,19 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
return true; return true;
} }
/**
* @return {@code true} if the fragment is showing nested mailboxes and we can go one level up.
* {@code false} otherwise, meaning we're showing the top level mailboxes *OR*
* we're still loading initial data and we can't determine if we're going to show
* top-level or not.
*/
public boolean canNavigateUp() {
if (!mParentDetermined) {
return false; // We can't determine yet...
}
return !isRoot();
}
/** /**
* A task to determine what parent mailbox ID/highlighted mailbox ID to use for the "UP" * A task to determine what parent mailbox ID/highlighted mailbox ID to use for the "UP"
* navigation, given the current parent mailbox ID, the highlighted mailbox ID, and {@link * navigation, given the current parent mailbox ID, the highlighted mailbox ID, and {@link
@ -781,6 +799,8 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
mListAdapter.swapCursor(null); mListAdapter.swapCursor(null);
setListShown(false); setListShown(false);
} else { } else {
mParentDetermined = true; // Okay now we're sure which mailbox is the parent.
mListAdapter.swapCursor(cursor); mListAdapter.swapCursor(cursor);
setListShown(true); setListShown(true);

View File

@ -431,12 +431,12 @@ abstract class UIControllerBase implements MailboxListFragment.Callback,
/** /**
* Show the default view for the given account. * Show the default view for the given account.
* *
* No-op if the given account is already selected.
*
* @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}. * @param accountId ID of the account to load. Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
* Must never be {@link Account#NO_ACCOUNT}. * Must never be {@link Account#NO_ACCOUNT}.
* @param forceShowInbox If {@code false} and the given account is already selected, do nothing.
* If {@code false}, we always change the view even if the account is selected.
*/ */
public final void switchAccount(long accountId) { public final void switchAccount(long accountId, boolean forceShowInbox) {
if (Account.isSecurityHold(mActivity, accountId)) { if (Account.isSecurityHold(mActivity, accountId)) {
ActivityHelper.showSecurityHoldDialog(mActivity, accountId); ActivityHelper.showSecurityHoldDialog(mActivity, accountId);
@ -444,7 +444,7 @@ abstract class UIControllerBase implements MailboxListFragment.Callback,
return; return;
} }
if (accountId == getUIAccountId()) { if (accountId == getUIAccountId() && !forceShowInbox) {
// Do nothing if the account is already selected. Not even going back to the inbox. // Do nothing if the account is already selected. Not even going back to the inbox.
return; return;
} }

View File

@ -55,7 +55,9 @@ class UIControllerOnePane extends UIControllerBase {
// MailboxListFragment.Callback // MailboxListFragment.Callback
@Override @Override
public void onAccountSelected(long accountId) { public void onAccountSelected(long accountId) {
switchAccount(accountId); // It's from combined view, so "forceShowInbox" doesn't really matter.
// (We're always switching accounts.)
switchAccount(accountId, true);
} }
// MailboxListFragment.Callback // MailboxListFragment.Callback
@ -94,7 +96,7 @@ class UIControllerOnePane extends UIControllerBase {
// MessageListFragment.Callback // MessageListFragment.Callback
@Override @Override
public void onMailboxNotFound() { public void onMailboxNotFound() {
switchAccount(getUIAccountId()); switchAccount(getUIAccountId(), true);
} }
// MessageListFragment.Callback // MessageListFragment.Callback
@ -202,15 +204,26 @@ class UIControllerOnePane extends UIControllerBase {
// This is all temporary as we'll have a different action bar controller for 1-pane. // This is all temporary as we'll have a different action bar controller for 1-pane.
private class ActionBarControllerCallback implements ActionBarController.Callback { private class ActionBarControllerCallback implements ActionBarController.Callback {
@Override @Override
public boolean shouldShowMailboxName() { public int getTitleMode() {
return false; // no mailbox name/unread count. if (isMailboxListInstalled()) {
return TITLE_MODE_ACCOUNT_WITH_ALL_FOLDERS_LABEL;
}
// TODO Return TITLE_MODE_MESSAGE_SUBJECT if isMessageViewInstalled()
return TITLE_MODE_ACCOUNT_WITH_MAILBOX;
} }
public String getMessageSubject() {
if (isMessageViewInstalled()) {
return "TODO: Return current message subject here";
} else {
return null;
}
}
@Override @Override
public boolean shouldShowUp() { public boolean shouldShowUp() {
return isMessageViewInstalled() return isMessageViewInstalled()
|| (isMailboxListInstalled() && !getMailboxListFragment().isRoot()); || (isMailboxListInstalled() && getMailboxListFragment().canNavigateUp());
} }
@Override @Override
@ -224,11 +237,11 @@ class UIControllerOnePane extends UIControllerBase {
} }
@Override @Override
public void onMailboxSelected(long mailboxId) { public void onMailboxSelected(long accountId, long mailboxId) {
if (mailboxId == Mailbox.NO_MAILBOX) { if (mailboxId == Mailbox.NO_MAILBOX) {
showAllMailboxes(); showAllMailboxes();
} else { } else {
openMailbox(getUIAccountId(), mailboxId); openMailbox(accountId, mailboxId);
} }
} }
@ -239,7 +252,7 @@ class UIControllerOnePane extends UIControllerBase {
@Override @Override
public void onAccountSelected(long accountId) { public void onAccountSelected(long accountId) {
switchAccount(accountId); switchAccount(accountId, true); // Always go to inbox
} }
@Override @Override
@ -434,7 +447,8 @@ class UIControllerOnePane extends UIControllerBase {
*/ */
private void commitFragmentTransaction(FragmentTransaction ft) { private void commitFragmentTransaction(FragmentTransaction ft) {
if (!ft.isEmpty()) { if (!ft.isEmpty()) {
ft.commit(); // STOPSHIP Don't use AllowingStateLoss. See b/4519430
ft.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions(); mFragmentManager.executePendingTransactions();
} }
} }

View File

@ -44,10 +44,7 @@ import java.util.Set;
* so that we can easily switch between synchronous and asynchronous transactions. * so that we can easily switch between synchronous and asynchronous transactions.
*/ */
class UIControllerTwoPane extends UIControllerBase implements class UIControllerTwoPane extends UIControllerBase implements
ThreePaneLayout.Callback, ThreePaneLayout.Callback {
MailboxListFragment.Callback,
MessageListFragment.Callback,
MessageViewFragment.Callback {
@VisibleForTesting @VisibleForTesting
static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds
@ -75,8 +72,6 @@ class UIControllerTwoPane extends UIControllerBase implements
// ThreePaneLayoutCallback // ThreePaneLayoutCallback
@Override @Override
public void onVisiblePanesChanged(int previousVisiblePanes) { public void onVisiblePanesChanged(int previousVisiblePanes) {
refreshActionBar();
// If the right pane is gone, remove the message view. // If the right pane is gone, remove the message view.
final int visiblePanes = mThreePane.getVisiblePanes(); final int visiblePanes = mThreePane.getVisiblePanes();
@ -89,6 +84,7 @@ class UIControllerTwoPane extends UIControllerBase implements
if (isMessageListInstalled()) { if (isMessageListInstalled()) {
getMessageListFragment().onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0); getMessageListFragment().onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0);
} }
refreshActionBar();
} }
// MailboxListFragment$Callback // MailboxListFragment$Callback
@ -103,7 +99,9 @@ class UIControllerTwoPane extends UIControllerBase implements
// MailboxListFragment$Callback // MailboxListFragment$Callback
@Override @Override
public void onAccountSelected(long accountId) { public void onAccountSelected(long accountId) {
switchAccount(accountId); // It's from combined view, so "forceShowInbox" doesn't really matter.
// (We're always switching accounts.)
switchAccount(accountId, true);
} }
// MailboxListFragment$Callback // MailboxListFragment$Callback
@ -427,7 +425,8 @@ class UIControllerTwoPane extends UIControllerBase implements
Log.d(Logging.LOG_TAG, this + " commitFragmentTransaction: " + ft); Log.d(Logging.LOG_TAG, this + " commitFragmentTransaction: " + ft);
} }
if (!ft.isEmpty()) { if (!ft.isEmpty()) {
ft.commit(); // STOPSHIP Don't use AllowingStateLoss. See b/4519430
ft.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions(); mFragmentManager.executePendingTransactions();
} }
} }
@ -818,12 +817,12 @@ class UIControllerTwoPane extends UIControllerBase implements
@Override @Override
public void onAccountSelected(long accountId) { public void onAccountSelected(long accountId) {
switchAccount(accountId); switchAccount(accountId, false);
} }
@Override @Override
public void onMailboxSelected(long mailboxId) { public void onMailboxSelected(long accountId, long mailboxId) {
openMailbox(getUIAccountId(), mailboxId); openMailbox(accountId, mailboxId);
} }
@Override @Override
@ -833,9 +832,32 @@ class UIControllerTwoPane extends UIControllerBase implements
} }
@Override @Override
public boolean shouldShowMailboxName() { public int getTitleMode() {
// Show when the left pane is hidden. final int visiblePanes = mThreePane.getVisiblePanes();
return (mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) == 0; if ((mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) != 0) {
// Mailbox list visible
return TITLE_MODE_ACCOUNT_NAME_ONLY;
}
if ((mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_MIDDLE) != 0) {
// Message list + message view
return TITLE_MODE_ACCOUNT_WITH_MAILBOX;
}
if ((mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_RIGHT) != 0) {
// Message view only (message list collapsed)
// TODO return TITLE_MODE_MESSAGE_SUBJECT
return TITLE_MODE_ACCOUNT_WITH_MAILBOX;
}
// Shouldn't happen, but just in case
return TITLE_MODE_ACCOUNT_NAME_ONLY;
}
public String getMessageSubject() {
if (isMessageViewInstalled()) {
return "TODO: Return current message subject here";
} else {
return null;
}
} }
@Override @Override
@ -843,7 +865,7 @@ class UIControllerTwoPane extends UIControllerBase implements
final int visiblePanes = mThreePane.getVisiblePanes(); final int visiblePanes = mThreePane.getVisiblePanes();
final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0); final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
return leftPaneHidden return leftPaneHidden
|| (isMailboxListInstalled() && !getMailboxListFragment().isRoot()); || (isMailboxListInstalled() && getMailboxListFragment().canNavigateUp());
} }
@Override @Override

View File

@ -52,7 +52,7 @@ public class AccountSelectorAdapterAccountsLoaderTest extends LoaderTestCase {
{ {
// Only 1 account -- no combined view row. // Only 1 account -- no combined view row.
Loader<Cursor> l = new AccountSelectorAdapter.AccountsLoader(mProviderContext, 0L, Loader<Cursor> l = new AccountSelectorAdapter.AccountsLoader(mProviderContext, 0L,
true); 0L, true);
AccountSelectorAdapter.CursorWithExtras result = AccountSelectorAdapter.CursorWithExtras result =
(AccountSelectorAdapter.CursorWithExtras) getLoaderResultSynchronously(l); (AccountSelectorAdapter.CursorWithExtras) getLoaderResultSynchronously(l);
assertEquals(1, result.getAccountCount()); assertEquals(1, result.getAccountCount());
@ -64,7 +64,7 @@ public class AccountSelectorAdapterAccountsLoaderTest extends LoaderTestCase {
{ {
// 2 accounts -- with combined view row, so returns 3 account rows. // 2 accounts -- with combined view row, so returns 3 account rows.
Loader<Cursor> l = new AccountSelectorAdapter.AccountsLoader(mProviderContext, 0L, Loader<Cursor> l = new AccountSelectorAdapter.AccountsLoader(mProviderContext, 0L,
true); 0L, true);
AccountSelectorAdapter.CursorWithExtras result = AccountSelectorAdapter.CursorWithExtras result =
(AccountSelectorAdapter.CursorWithExtras) getLoaderResultSynchronously(l); (AccountSelectorAdapter.CursorWithExtras) getLoaderResultSynchronously(l);
assertEquals(3, result.getAccountCount()); assertEquals(3, result.getAccountCount());