Merge "make scroll position compensation more reliable" into jb-ub-mail-ur8
diff --git a/res/layout/actionbar_folder.xml b/res/layout/actionbar_folder.xml
deleted file mode 100644
index 2b38d91..0000000
--- a/res/layout/actionbar_folder.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="2.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2011 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/folder_layout"
- android:orientation="vertical"
- android:gravity="center_vertical"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:layout_marginLeft="4dip"
- android:visibility="gone" >
- <TextView
- android:id="@+id/folders"
- style="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"
- android:singleLine="true"
- android:ellipsize="end"
- android:includeFontPadding="false"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/folders" />
- <TextView
- android:id="@+id/account"
- style="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle"
- android:singleLine="true"
- android:ellipsize="end"
- android:includeFontPadding="false"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-</LinearLayout>
diff --git a/res/layout/actionbar_subject.xml b/res/layout/actionbar_subject.xml
index 1aa830c..e4b0fc5 100644
--- a/res/layout/actionbar_subject.xml
+++ b/res/layout/actionbar_subject.xml
@@ -16,14 +16,16 @@
limitations under the License.
-->
+<!-- WARNING: this view's styling must match styling in framework's ActionBarView and -->
+<!-- action_bar_title_item.xml -->
<com.android.mail.browse.SnippetTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/conversation_subject"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingRight="@dimen/actionbar_subject_padding_right"
- android:includeFontPadding="false"
- android:textSize="16dp"
+ android:visibility="gone"
+ android:paddingRight="8dp"
android:gravity="center_vertical"
android:maxLines="2"
- android:ellipsize="end" />
+ android:ellipsize="end"
+ style="@android:style/TextAppearance.Holo.Widget.ActionBar.Title" />
diff --git a/res/layout/actionbar_view.xml b/res/layout/actionbar_view.xml
index dc2c6d8..bcad65b 100644
--- a/res/layout/actionbar_view.xml
+++ b/res/layout/actionbar_view.xml
@@ -33,8 +33,6 @@
android:focusable="false"
android:clickable="true"/>
- <include layout="@layout/actionbar_folder" />
-
<include layout="@layout/actionbar_subject" />
</com.android.mail.ui.MailActionBarView>
diff --git a/res/layout/conversation_message_attachment_tile.xml b/res/layout/conversation_message_attachment_tile.xml
index 0feca5f..d659e4b 100644
--- a/res/layout/conversation_message_attachment_tile.xml
+++ b/res/layout/conversation_message_attachment_tile.xml
@@ -20,6 +20,7 @@
android:id="@+id/attachment_tile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:focusable="true"
android:background="@drawable/attachment_bg_holo">
<include layout="@layout/attachment_preview"/>
@@ -76,7 +77,7 @@
android:id="@+id/attachment_tile_push_state"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:focusable="true"
+ android:duplicateParentState="true"
android:background="?android:attr/selectableItemBackground" />
</com.android.mail.browse.MessageAttachmentTile>
diff --git a/res/layout/search_actionbar_view.xml b/res/layout/search_actionbar_view.xml
index 19825a5..93d8de8 100644
--- a/res/layout/search_actionbar_view.xml
+++ b/res/layout/search_actionbar_view.xml
@@ -35,8 +35,6 @@
android:focusable="false"
android:clickable="true"/>
- <include layout="@layout/actionbar_folder" />
-
<include layout="@layout/actionbar_subject" />
</com.android.mail.ui.SearchMailActionBarView>
\ No newline at end of file
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9c99fbe..d9fc3f8 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -159,7 +159,7 @@
</plurals>
<string name="sending" msgid="8214361929125649771">"Senden..."</string>
<string name="send_failed" msgid="750908595144529579">"Nicht gesendet"</string>
- <string name="me" msgid="6480762904022198669">"Ich"</string>
+ <string name="me" msgid="6480762904022198669">"Mich"</string>
<string name="show_all_folders" msgid="3281420732307737553">"Alle Ordner anzeigen"</string>
<plurals name="confirm_delete_conversation">
<item quantity="one" msgid="3731948757247905508">"Diese Konversation löschen?"</item>
diff --git a/res/values-land/dimen.xml b/res/values-land/dimen.xml
index b618322..0dc5d8b 100644
--- a/res/values-land/dimen.xml
+++ b/res/values-land/dimen.xml
@@ -16,7 +16,6 @@
limitations under the License.
-->
<resources>
- <dimen name="actionbar_subject_padding_right">0dp</dimen>
<dimen name="compose_scrollview_width">800dp</dimen>
<dimen name="subject_width">604dip</dimen>
</resources>
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 7bbf99f..70cd453 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -30,7 +30,6 @@
<dimen name="folders_min_height">18dp</dimen>
<dimen name="date_font_size">12sp</dimen>
<dimen name="account_dropdown_dropdownwidth">274dip</dimen>
- <dimen name="actionbar_subject_padding_right">8dp</dimen>
<dimen name="compose_scrollview_width">700dp</dimen>
<dimen name="total_conversationbar_width">150dip</dimen>
<dimen name="max_total_folder_width">64dip</dimen>
diff --git a/src/com/android/mail/browse/AttachmentActionHandler.java b/src/com/android/mail/browse/AttachmentActionHandler.java
index 7c24dda..9a359f5 100644
--- a/src/com/android/mail/browse/AttachmentActionHandler.java
+++ b/src/com/android/mail/browse/AttachmentActionHandler.java
@@ -41,7 +41,6 @@
import com.android.mail.utils.Utils;
import java.util.ArrayList;
-import java.util.List;
public class AttachmentActionHandler {
private static final String PROGRESS_FRAGMENT_TAG = "attachment-progress";
@@ -72,6 +71,10 @@
}
public void showAttachment(int destination) {
+ if (mView == null) {
+ return;
+ }
+
// If the caller requested that this attachments be saved to the external storage, we should
// verify that the it was saved there.
if (mAttachment.isPresentLocally() &&
@@ -84,6 +87,10 @@
}
}
+ /**
+ * Start downloading the full size attachment set with
+ * {@link #setAttachment(Attachment)} immediately.
+ */
public void startDownloadingAttachment(int destination) {
startDownloadingAttachment(destination, UIProvider.AttachmentRendition.BEST, 0, false);
}
@@ -146,6 +153,10 @@
* previously brought up (by tapping 'View') and the download has now finished.
*/
public void updateStatus(boolean loaderResult) {
+ if (mView == null) {
+ return;
+ }
+
final boolean showProgress = mAttachment.shouldShowProgress();
final AttachmentProgressDialogFragment dialog = (AttachmentProgressDialogFragment)
diff --git a/src/com/android/mail/browse/AttachmentViewInterface.java b/src/com/android/mail/browse/AttachmentViewInterface.java
index d56bdf4..6a8b217 100644
--- a/src/com/android/mail/browse/AttachmentViewInterface.java
+++ b/src/com/android/mail/browse/AttachmentViewInterface.java
@@ -17,10 +17,6 @@
package com.android.mail.browse;
-import com.android.mail.providers.Attachment;
-
-import java.util.List;
-
public interface AttachmentViewInterface {
/**
@@ -32,7 +28,7 @@
/**
* Allows the view to know when it should update its progress.
- * @param showProgress true if the the view should show a determinate
+ * @param showDeterminateProgress true if the the view should show a determinate
* progress value
*/
public void updateProgress(boolean showDeterminateProgress);
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index 28df1fc..6d0daec 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -276,7 +276,7 @@
}
public Set<Long> conversationIds() {
- return mConversationIdPositionMap.keySet() ;
+ return mConversationIdPositionMap.keySet();
}
public int getPosition(long conversationId) {
@@ -378,11 +378,11 @@
private void resetCursor(UnderlyingCursorWrapper newCursorWrapper) {
synchronized (mCacheMapLock) {
// Walk through the cache
- final Iterator<HashMap.Entry<String, ContentValues>> iter =
+ final Iterator<Map.Entry<String, ContentValues>> iter =
mCacheMap.entrySet().iterator();
final long now = System.currentTimeMillis();
while (iter.hasNext()) {
- HashMap.Entry<String, ContentValues> entry = iter.next();
+ Map.Entry<String, ContentValues> entry = iter.next();
final ContentValues values = entry.getValue();
final String key = entry.getKey();
boolean withinTimeWindow = false;
@@ -441,10 +441,10 @@
synchronized (mCacheMapLock) {
// Walk through the cache and return the list of uris that have been deleted
final Set<String> deletedItems = Sets.newHashSet();
- final Iterator<HashMap.Entry<String, ContentValues>> iter =
+ final Iterator<Map.Entry<String, ContentValues>> iter =
mCacheMap.entrySet().iterator();
while (iter.hasNext()) {
- final HashMap.Entry<String, ContentValues> entry = iter.next();
+ final Map.Entry<String, ContentValues> entry = iter.next();
final ContentValues values = entry.getValue();
if (values.containsKey(DELETED_COLUMN)) {
// Since clients of the conversation cursor see conversation ConversationCursor
@@ -472,10 +472,10 @@
// position, decrement the position
synchronized (mCacheMapLock) {
int updatedPosition = underlyingPosition;
- final Iterator<HashMap.Entry<String, ContentValues>> iter =
+ final Iterator<Map.Entry<String, ContentValues>> iter =
mCacheMap.entrySet().iterator();
while (iter.hasNext()) {
- final HashMap.Entry<String, ContentValues> entry = iter.next();
+ final Map.Entry<String, ContentValues> entry = iter.next();
final ContentValues values = entry.getValue();
if (values.containsKey(DELETED_COLUMN)) {
// Since clients of the conversation cursor see conversation ConversationCursor
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 07392f9..6db0d1a 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -63,6 +63,7 @@
import com.android.mail.perf.Timer;
import com.android.mail.photomanager.ContactPhotoManager;
import com.android.mail.photomanager.LetterTileProvider;
+import com.android.mail.preferences.MailPrefs;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
@@ -176,6 +177,7 @@
private TextView mSendersTextView;
private TextView mDateTextView;
private DividedImageCanvas mContactImagesHolder;
+ private boolean mConvListPhotosEnabled;
private static int sFoldersLeftPadding;
private static TextAppearanceSpan sDateTextAppearance;
private static TextAppearanceSpan sSubjectTextUnreadSpan;
@@ -342,9 +344,9 @@
setClickable(true);
setLongClickable(true);
mContext = context.getApplicationContext();
- mTabletDevice = Utils.useTabletUI(mContext);
+ final Resources res = mContext.getResources();
+ mTabletDevice = Utils.useTabletUI(res);
mAccount = account;
- Resources res = mContext.getResources();
if (CHECKMARK_OFF == null) {
// Initialize static bitmaps.
@@ -384,8 +386,10 @@
R.style.SubjectAppearanceUnreadStyle);
sSubjectTextReadSpan = new TextAppearanceSpan(mContext,
R.style.SubjectAppearanceReadStyle);
- sSnippetTextUnreadSpan = new ForegroundColorSpan(R.color.snippet_text_color_unread);
- sSnippetTextReadSpan = new ForegroundColorSpan(R.color.snippet_text_color_read);
+ sSnippetTextUnreadSpan =
+ new ForegroundColorSpan(res.getColor(R.color.snippet_text_color_unread));
+ sSnippetTextReadSpan =
+ new ForegroundColorSpan(res.getColor(R.color.snippet_text_color_read));
sTouchSlop = res.getDimensionPixelSize(R.dimen.touch_slop);
sStandardScaledDimen = res.getDimensionPixelSize(R.dimen.standard_scaled_dimen);
sShrinkAnimationDuration = res.getInteger(R.integer.shrink_animation_duration);
@@ -455,6 +459,8 @@
mSelectedConversationSet = set;
mDisplayedFolder = folder;
mCheckboxesEnabled = !checkboxesDisabled;
+ mConvListPhotosEnabled = MailPrefs.get(activity.getActivityContext())
+ .areConvListPhotosEnabled();
mStarEnabled = folder != null && !folder.isTrash();
mSwipeEnabled = swipeEnabled;
mPriorityMarkersEnabled = priorityArrowEnabled;
@@ -515,7 +521,7 @@
ConversationItemViewCoordinates.refreshConversationHeights(mContext);
}
mCoordinates = ConversationItemViewCoordinates.forWidth(mContext, mViewWidth, mMode,
- mHeader.standardScaledDimen, mCheckboxesEnabled);
+ mHeader.standardScaledDimen, mConvListPhotosEnabled);
calculateTextsAndBitmaps();
calculateCoordinates();
@@ -569,7 +575,7 @@
if (mSelectedConversationSet != null) {
mChecked = mSelectedConversationSet.contains(mHeader.conversation);
}
- mHeader.checkboxVisible = mCheckboxesEnabled;
+ mHeader.checkboxVisible = mCheckboxesEnabled && !mConvListPhotosEnabled;
final boolean isUnread = mHeader.unread;
updateBackground(isUnread);
@@ -647,7 +653,7 @@
}
private void loadSenderImages() {
- if (!mCheckboxesEnabled && mHeader.displayableSenderEmails != null
+ if (mConvListPhotosEnabled && mHeader.displayableSenderEmails != null
&& mHeader.displayableSenderEmails.size() > 0) {
mContactImagesHolder.setDimensions(mCoordinates.contactImagesWidth,
mCoordinates.contactImagesHeight);
@@ -687,51 +693,51 @@
SpannableStringBuilder subjectText = new SpannableStringBuilder(
Conversation.getSubjectAndSnippetForDisplay(mContext, subject, snippet));
- int subjectTextLength = Math.min(subjectText.length(), subject.length());
- if (!TextUtils.isEmpty(subject)) {
- subjectText.setSpan(TextAppearanceSpan.wrap(isUnread ?
- sSubjectTextUnreadSpan : sSubjectTextReadSpan), 0, subjectTextLength,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- if (!TextUtils.isEmpty(snippet)) {
- final int startOffset = subjectTextLength;
- // Start after the end of the subject text; since the subject may be
- // "" or null, this could start at the 0th character in the
- // subectText string
- if (startOffset < subjectText.length()) {
- subjectText.setSpan(ForegroundColorSpan.wrap(isUnread ?
- sSnippetTextUnreadSpan : sSnippetTextReadSpan), startOffset,
- subjectText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- layoutSubject(subjectText);
- }
-
- private void layoutSubject(SpannableStringBuilder subjectText) {
int secondLineMaxWidth = EllipsizedMultilineTextView.ALL_AVAILABLE;
if (!ConversationItemViewCoordinates.isWideMode(mMode) && mCoordinates.showFolders
- && mHeader.folderDisplayer != null
- && mHeader.folderDisplayer.hasVisibleFolders()) {
- secondLineMaxWidth = mCoordinates.subjectWidth
- - Math.min(ConversationItemViewCoordinates.getFoldersWidth(mContext, mMode),
- mHeader.folderDisplayer.measureFolders(mMode))
- - sFoldersLeftPadding;
+ && mHeader.folderDisplayer != null && mHeader.folderDisplayer.hasVisibleFolders()) {
+ secondLineMaxWidth = mCoordinates.subjectWidth - Math.min(
+ ConversationItemViewCoordinates.getFoldersWidth(mContext, mMode),
+ mHeader.folderDisplayer.measureFolders(mMode)) - sFoldersLeftPadding;
}
EllipsizedMultilineTextView subjectLayout = mSubjectTextView;
int subjectWidth = mCoordinates.subjectWidth;
- int subjectHeight = (int) (subjectLayout.getLineHeight() * 2 + subjectLayout.getPaint()
- .descent());
+ int subjectHeight =
+ (int) (subjectLayout.getLineHeight() * 2 + subjectLayout.getPaint().descent());
+ subjectLayout.measure(
+ MeasureSpec.makeMeasureSpec(subjectWidth, MeasureSpec.EXACTLY), subjectHeight);
+ subjectLayout.layout(0, 0, subjectWidth, subjectHeight);
+
+ final CharSequence displayedText = subjectLayout.setText(subjectText, secondLineMaxWidth);
+
+ final SpannableStringBuilder displayedStringBuilder =
+ new SpannableStringBuilder(displayedText);
+ int subjectTextLength =
+ Math.min(displayedText.length(), Math.min(subjectText.length(), subject.length()));
+ if (!TextUtils.isEmpty(subject)) {
+ displayedStringBuilder.setSpan(TextAppearanceSpan.wrap(
+ isUnread ? sSubjectTextUnreadSpan : sSubjectTextReadSpan), 0, subjectTextLength,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ if (!TextUtils.isEmpty(snippet)) {
+ final int startOffset = subjectTextLength;
+ // Start after the end of the subject text; since the subject may be
+ // "" or null, this could start at the 0th character in the subectText string
+ if (startOffset < displayedText.length()) {
+ displayedStringBuilder.setSpan(ForegroundColorSpan.wrap(
+ isUnread ? sSnippetTextUnreadSpan : sSnippetTextReadSpan), startOffset,
+ displayedText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
if (isActivated() && showActivatedText()) {
- subjectText.setSpan(sActivatedTextSpan, 0, subjectText.length(),
+ displayedStringBuilder.setSpan(sActivatedTextSpan, 0, displayedText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
- subjectText.removeSpan(sActivatedTextSpan);
+ displayedStringBuilder.removeSpan(sActivatedTextSpan);
}
- subjectLayout.measure(MeasureSpec.makeMeasureSpec(subjectWidth, MeasureSpec.EXACTLY),
- subjectHeight);
- subjectLayout.layout(0, 0, subjectWidth, subjectHeight);
- subjectLayout.setText(subjectText, secondLineMaxWidth);
+
+ subjectLayout.setText(displayedStringBuilder);
}
/**
@@ -1023,13 +1029,13 @@
@Override
protected void onDraw(Canvas canvas) {
// Check mark.
- if (mHeader.checkboxVisible) {
- Bitmap checkmark = mChecked ? CHECKMARK_ON : CHECKMARK_OFF;
- canvas.drawBitmap(checkmark, mCoordinates.checkmarkX, mCoordinates.checkmarkY, sPaint);
- } else {
+ if (mConvListPhotosEnabled) {
canvas.save();
drawContactImages(canvas);
canvas.restore();
+ } else if (mHeader.checkboxVisible) {
+ Bitmap checkmark = mChecked ? CHECKMARK_ON : CHECKMARK_OFF;
+ canvas.drawBitmap(checkmark, mCoordinates.checkmarkX, mCoordinates.checkmarkY, sPaint);
}
// Personal Level.
@@ -1144,7 +1150,7 @@
private void drawSenders(Canvas canvas) {
int left;
- if (!mCheckboxesEnabled && mCoordinates.inlinePersonalLevel) {
+ if (mConvListPhotosEnabled && mCoordinates.inlinePersonalLevel) {
if (mCoordinates.showPersonalLevel && mHeader.personalLevelBitmap != null) {
left = mCoordinates.sendersX;
} else {
@@ -1161,6 +1167,14 @@
return mHeader.conversation.starred ? STAR_ON : STAR_OFF;
}
+ /**
+ * Set the background for this item based on:
+ * 1. Read / Unread (unread messages have a lighter background)
+ * 2. Tablet / Phone
+ * 3. Checkbox checked / Unchecked (controls CAB color for item)
+ * 4. Activated / Not activated (controls the blue highlight on tablet)
+ * @param isUnread
+ */
private void updateBackground(boolean isUnread) {
if (mBackgroundOverride != -1) {
// If the item is animating, we use a color to avoid shrinking a 9-patch
@@ -1169,42 +1183,44 @@
return;
}
final boolean isListOnTablet = mTabletDevice && mActivity.getViewMode().isListMode();
+ final int background;
if (isUnread) {
if (isListOnTablet) {
if (mChecked) {
- setBackgroundResource(R.drawable.list_conversation_wide_unread_selected_holo);
+ background = R.drawable.list_conversation_wide_unread_selected_holo;
} else {
- setBackgroundResource(R.drawable.conversation_wide_unread_selector);
+ background = R.drawable.conversation_wide_unread_selector;
}
} else {
if (mChecked) {
- setCheckedActivatedBackground();
+ background = getCheckedActivatedBackground();
} else {
- setBackgroundResource(R.drawable.conversation_unread_selector);
+ background = R.drawable.conversation_unread_selector;
}
}
} else {
if (isListOnTablet) {
if (mChecked) {
- setBackgroundResource(R.drawable.list_conversation_wide_read_selected_holo);
+ background = R.drawable.list_conversation_wide_read_selected_holo;
} else {
- setBackgroundResource(R.drawable.conversation_wide_read_selector);
+ background = R.drawable.conversation_wide_read_selector;
}
} else {
if (mChecked) {
- setCheckedActivatedBackground();
+ background = getCheckedActivatedBackground();
} else {
- setBackgroundResource(R.drawable.conversation_read_selector);
+ background = R.drawable.conversation_read_selector;
}
}
}
+ setBackgroundResource(background);
}
- private void setCheckedActivatedBackground() {
+ private final int getCheckedActivatedBackground() {
if (isActivated() && mTabletDevice) {
- setBackgroundResource(R.drawable.list_arrow_selected_holo);
+ return R.drawable.list_arrow_selected_holo;
} else {
- setBackgroundResource(R.drawable.list_selected_holo);
+ return R.drawable.list_selected_holo;
}
}
diff --git a/src/com/android/mail/browse/ConversationItemViewCoordinates.java b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
index aca8f43..dfce5d8 100644
--- a/src/com/android/mail/browse/ConversationItemViewCoordinates.java
+++ b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
@@ -168,8 +168,8 @@
/**
* Returns the layout id to be inflated in this mode.
*/
- private static int getLayoutId(int mode, boolean checkboxesEnabled) {
- if (checkboxesEnabled) {
+ private static int getLayoutId(int mode, boolean convListPhotosEnabled) {
+ if (!convListPhotosEnabled) {
switch (mode) {
case WIDE_MODE:
return R.layout.conversation_item_view_wide;
@@ -339,17 +339,17 @@
* the view width.
*/
public static ConversationItemViewCoordinates forWidth(Context context, int width, int mode,
- int standardScaledDimen, boolean checkboxesEnabled) {
+ int standardScaledDimen, boolean convListPhotosEnabled) {
ConversationItemViewCoordinates coordinates = sCache.get(Objects.hashCode(width, mode,
- checkboxesEnabled));
+ convListPhotosEnabled));
if (coordinates == null) {
coordinates = new ConversationItemViewCoordinates();
- sCache.put(Objects.hashCode(width, mode, checkboxesEnabled), coordinates);
+ sCache.put(Objects.hashCode(width, mode, convListPhotosEnabled), coordinates);
// Layout the appropriate view.
int height = getHeight(context, mode);
- View view = LayoutInflater.from(context).inflate(getLayoutId(mode, checkboxesEnabled),
- null);
+ View view = LayoutInflater.from(context).inflate(
+ getLayoutId(mode, convListPhotosEnabled), null);
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
Resources res = context.getResources();
@@ -442,7 +442,7 @@
coordinates.paperclipY = getY(paperclip);
// Contact images view
- if (!checkboxesEnabled) {
+ if (convListPhotosEnabled) {
View contactImagesView = view.findViewById(R.id.contact_image);
if (contactImagesView != null) {
coordinates.contactImagesWidth = contactImagesView.getWidth();
diff --git a/src/com/android/mail/browse/ConversationListFooterView.java b/src/com/android/mail/browse/ConversationListFooterView.java
index 0fb1c8c..0d72a90 100644
--- a/src/com/android/mail/browse/ConversationListFooterView.java
+++ b/src/com/android/mail/browse/ConversationListFooterView.java
@@ -57,7 +57,7 @@
public ConversationListFooterView(Context context, AttributeSet attrs) {
super(context, attrs);
- mTabletDevice = Utils.useTabletUI(context);
+ mTabletDevice = Utils.useTabletUI(context.getResources());
}
@Override
diff --git a/src/com/android/mail/browse/ConversationPagerAdapter.java b/src/com/android/mail/browse/ConversationPagerAdapter.java
index 855aa13..a32b0a3 100644
--- a/src/com/android/mail/browse/ConversationPagerAdapter.java
+++ b/src/com/android/mail/browse/ConversationPagerAdapter.java
@@ -39,7 +39,6 @@
import com.android.mail.utils.FragmentStatePagerAdapter2;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
public class ConversationPagerAdapter extends FragmentStatePagerAdapter2
implements ViewPager.OnPageChangeListener {
@@ -129,6 +128,11 @@
}
private ConversationCursor getCursor() {
+ if (mDetachedMode) {
+ // In detached mode, the pager is decoupled from the cursor. Nothing should rely on the
+ // cursor at this point.
+ return null;
+ }
if (mController == null) {
// Happens when someone calls setActivityController(null) on us. This is done in
// ConversationPagerController.stopListening() to indicate that the Conversation View
@@ -278,6 +282,9 @@
return;
}
+ // If we are in detached mode, changes to the cursor are of no interest to us, but they may
+ // be to parent classes.
+
// when the currently visible item disappears from the dataset:
// if the new version of the currently visible item has zero messages:
// notify the list controller so it can handle this 'current conversation gone' case
@@ -285,13 +292,14 @@
// else
// 'detach' the conversation view from the cursor, keeping the current item as-is but
// disabling swipe (effectively the same as singleton mode)
- if (mController != null) {
+ if (mController != null && !mDetachedMode) {
final Conversation currConversation = mController.getCurrentConversation();
final int pos = getConversationPosition(currConversation);
if (pos == POSITION_NONE && getCursor() != null && currConversation != null) {
// enable detached mode and do no more here. the fragment itself will figure out
// if the conversation is empty (using message list cursor) and back out if needed.
mDetachedMode = true;
+ mController.setDetachedMode();
LogUtils.i(LOG_TAG, "CPA: current conv is gone, reverting to detached mode. c=%s",
currConversation.uri);
} else {
@@ -332,10 +340,6 @@
public int getConversationPosition(Conversation conv) {
if (isPagingDisabled()) {
- if (getCursor() == null) {
- return POSITION_NONE;
- }
-
if (conv != getDefaultConversation()) {
LogUtils.d(LOG_TAG, "unable to find conversation in singleton mode. c=%s", conv);
return POSITION_NONE;
diff --git a/src/com/android/mail/photo/MailPhotoViewActivity.java b/src/com/android/mail/photo/MailPhotoViewActivity.java
index 7d597e1..6a17f37 100644
--- a/src/com/android/mail/photo/MailPhotoViewActivity.java
+++ b/src/com/android/mail/photo/MailPhotoViewActivity.java
@@ -36,6 +36,7 @@
import com.android.mail.browse.AttachmentActionHandler;
import com.android.mail.providers.Attachment;
import com.android.mail.providers.UIProvider.AttachmentDestination;
+import com.android.mail.providers.UIProvider.AttachmentState;
import com.android.mail.utils.AttachmentUtils;
import com.android.mail.utils.Utils;
import com.google.common.collect.Lists;
@@ -61,6 +62,7 @@
super.onCreate(savedInstanceState);
mActionHandler = new AttachmentActionHandler(this, null);
+ mActionHandler.initialize(getFragmentManager());
}
@Override
@@ -193,6 +195,12 @@
@Override
public void onFragmentVisible(PhotoViewFragment fragment) {
super.onFragmentVisible(fragment);
+ Attachment attachment = getCurrentAttachment();
+ if (attachment.state == AttachmentState.PAUSED) {
+ mActionHandler.setAttachment(attachment);
+ mActionHandler.startDownloadingAttachment(attachment.destination);
+ }
+ return;
}
@Override
@@ -322,9 +330,8 @@
}
@Override
- public void onNewPhotoLoaded() {
- // Don't call the super class, as the default implementation calls setViewActivated()
- // and we don't want to do that
+ public void onNewPhotoLoaded(int position) {
+ // do nothing
}
/**
diff --git a/src/com/android/mail/photomanager/LetterTileProvider.java b/src/com/android/mail/photomanager/LetterTileProvider.java
index ab40234..415ae9b 100644
--- a/src/com/android/mail/photomanager/LetterTileProvider.java
+++ b/src/com/android/mail/photomanager/LetterTileProvider.java
@@ -57,14 +57,14 @@
private static int sTileFontColor;
private static TextPaint sPaint = new TextPaint();
private static int DEFAULT_AVATAR_DRAWABLE = R.drawable.ic_contact_picture;
- private static final Pattern ALPHABET = Pattern.compile("^[a-zA-Z]+$");
+ private static final Pattern ALPHABET = Pattern.compile("^[a-zA-Z0-9]+$");
public LetterTileProvider() {
super();
final float cacheSizeAdjustment =
(MemoryUtils.getTotalMemorySize() >= MemoryUtils.LARGE_RAM_THRESHOLD) ?
1.0f : 0.5f;
- final int bitmapCacheSize = (int) (cacheSizeAdjustment * 26);
+ final int bitmapCacheSize = (int) (cacheSizeAdjustment * 36);
mTileBitmapCache = new LruCache<Integer, Bitmap>(bitmapCacheSize);
}
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index 2f60c4e..1aa8ac0 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -45,6 +45,7 @@
// Hidden preference to indicate what version a "What's New" dialog was last shown for.
private static final String WHATS_NEW_LAST_SHOWN_VERSION = "whats-new-last-shown-version";
+ public static final String ENABLE_CONVLIST_PHOTOS = "enable-convlist-photos";
public static MailPrefs get(Context c) {
if (sInstance == null) {
@@ -61,6 +62,13 @@
return PREFS_NAME;
}
+ /**
+ * Set the value of a shared preference of type boolean.
+ */
+ public void setSharedBooleanPreference(String pref, boolean value) {
+ mPrefs.edit().putBoolean(pref, value).apply();
+ }
+
public boolean isWidgetConfigured(int appWidgetId) {
return mPrefs.contains(WIDGET_ACCOUNT_PREFIX + appWidgetId);
}
@@ -76,6 +84,14 @@
return mPrefs.getString(WIDGET_ACCOUNT_PREFIX + appWidgetId, null);
}
+ /**
+ * Get whether to show the experimental inline contact photos in the
+ * conversation list.
+ */
+ public boolean areConvListPhotosEnabled() {
+ return mPrefs.getBoolean(ENABLE_CONVLIST_PHOTOS, false);
+ }
+
private static String createWidgetPreferenceValue(Account account, Folder folder) {
return account.uri.toString() +
ACCOUNT_FOLDER_PREFERENCE_SEPARATOR + folder.uri.toString();
diff --git a/src/com/android/mail/providers/Attachment.java b/src/com/android/mail/providers/Attachment.java
index 707619a..51f2607 100644
--- a/src/com/android/mail/providers/Attachment.java
+++ b/src/com/android/mail/providers/Attachment.java
@@ -256,7 +256,7 @@
}
public boolean isDownloading() {
- return state == AttachmentState.DOWNLOADING;
+ return state == AttachmentState.DOWNLOADING || state == AttachmentState.PAUSED;
}
public boolean isSavedToExternal() {
@@ -268,7 +268,7 @@
}
public boolean shouldShowProgress() {
- return state == AttachmentState.DOWNLOADING
+ return (state == AttachmentState.DOWNLOADING || state == AttachmentState.PAUSED)
&& size > 0 && downloadedSize > 0 && downloadedSize <= size;
}
diff --git a/src/com/android/mail/providers/MailAppProvider.java b/src/com/android/mail/providers/MailAppProvider.java
index 798c446..587170d 100644
--- a/src/com/android/mail/providers/MailAppProvider.java
+++ b/src/com/android/mail/providers/MailAppProvider.java
@@ -295,8 +295,8 @@
case UIProvider.ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN:
builder.add(Integer.valueOf(account.settings.replyBehavior));
break;
- case UIProvider.ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN:
- builder.add(Integer.valueOf(account.settings.showCheckboxes ? 1 : 0));
+ case UIProvider.ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN:
+ builder.add(Integer.valueOf(account.settings.hideCheckboxes ? 1 : 0));
break;
case UIProvider.ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN:
builder.add(Integer.valueOf(account.settings.confirmDelete ? 1 : 0));
diff --git a/src/com/android/mail/providers/Settings.java b/src/com/android/mail/providers/Settings.java
index d37bab1..b77a19d 100644
--- a/src/com/android/mail/providers/Settings.java
+++ b/src/com/android/mail/providers/Settings.java
@@ -66,7 +66,7 @@
public final int messageTextSize;
public final int snapHeaders;
public final int replyBehavior;
- public final boolean showCheckboxes;
+ public final boolean hideCheckboxes;
public final boolean confirmDelete;
public final boolean confirmArchive;
public final boolean confirmSend;
@@ -101,7 +101,7 @@
messageTextSize = MessageTextSize.NORMAL;
snapHeaders = SnapHeaderValue.ALWAYS;
replyBehavior = DefaultReplyBehavior.REPLY;
- showCheckboxes = false;
+ hideCheckboxes = false;
confirmDelete = false;
confirmArchive = false;
confirmSend = false;
@@ -122,7 +122,7 @@
messageTextSize = inParcel.readInt();
snapHeaders = inParcel.readInt();
replyBehavior = inParcel.readInt();
- showCheckboxes = inParcel.readInt() != 0;
+ hideCheckboxes = inParcel.readInt() != 0;
confirmDelete = inParcel.readInt() != 0;
confirmArchive = inParcel.readInt() != 0;
confirmSend = inParcel.readInt() != 0;
@@ -143,7 +143,7 @@
messageTextSize = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN);
snapHeaders = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN);
replyBehavior = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN);
- showCheckboxes = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN) != 0;
+ hideCheckboxes = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN) != 0;
confirmDelete = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN) != 0;
confirmArchive = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN) != 0;
confirmSend = cursor.getInt(UIProvider.ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN) != 0;
@@ -173,8 +173,8 @@
sDefault.snapHeaders);
replyBehavior = json.optInt(AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
sDefault.replyBehavior);
- showCheckboxes = json.optBoolean(AccountColumns.SettingsColumns.SHOW_CHECKBOXES,
- sDefault.showCheckboxes);
+ hideCheckboxes = json.optBoolean(AccountColumns.SettingsColumns.HIDE_CHECKBOXES,
+ sDefault.hideCheckboxes);
confirmDelete = json.optBoolean(AccountColumns.SettingsColumns.CONFIRM_DELETE,
sDefault.confirmDelete);
confirmArchive = json.optBoolean(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE,
@@ -228,7 +228,7 @@
json.put(AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, messageTextSize);
json.put(AccountColumns.SettingsColumns.SNAP_HEADERS, snapHeaders);
json.put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR, replyBehavior);
- json.put(AccountColumns.SettingsColumns.SHOW_CHECKBOXES, showCheckboxes);
+ json.put(AccountColumns.SettingsColumns.HIDE_CHECKBOXES, hideCheckboxes);
json.put(AccountColumns.SettingsColumns.CONFIRM_DELETE, confirmDelete);
json.put(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, confirmArchive);
json.put(AccountColumns.SettingsColumns.CONFIRM_SEND, confirmSend);
@@ -298,7 +298,7 @@
dest.writeInt(messageTextSize);
dest.writeInt(snapHeaders);
dest.writeInt(replyBehavior);
- dest.writeInt(showCheckboxes ? 1 : 0);
+ dest.writeInt(hideCheckboxes ? 1 : 0);
dest.writeInt(confirmDelete ? 1 : 0);
dest.writeInt(confirmArchive? 1 : 0);
dest.writeInt(confirmSend? 1 : 0);
@@ -391,7 +391,7 @@
&& messageTextSize == that.messageTextSize
&& snapHeaders == that.snapHeaders
&& replyBehavior == that.replyBehavior
- && showCheckboxes == that.showCheckboxes
+ && hideCheckboxes == that.hideCheckboxes
&& confirmDelete == that.confirmDelete
&& confirmArchive == that.confirmArchive
&& confirmSend == that.confirmSend
@@ -420,7 +420,7 @@
private final int calculateHashCode() {
return super.hashCode()
^ Objects.hashCode(signature, mAutoAdvance, mTransientAutoAdvance, messageTextSize,
- snapHeaders, replyBehavior, showCheckboxes, confirmDelete, confirmArchive,
+ snapHeaders, replyBehavior, hideCheckboxes, confirmDelete, confirmArchive,
confirmSend, defaultInbox, forceReplyFromDefault, maxAttachmentSize, swipe,
priorityArrowsEnabled, setupIntentUri, conversationViewMode,
veiledAddressPattern);
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 69b56aa..adee501 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -80,14 +80,8 @@
public static boolean isSyncInProgress(int syncStatus) {
return 0 != (syncStatus & (BACKGROUND_SYNC |
USER_REFRESH |
- LIVE_QUERY |
- USER_MORE_RESULTS));
+ LIVE_QUERY));
}
- /**
- * Unused currently, is not used by any provider.
- * TODO(viki): Remove.
- */
- public static final int USER_MORE_RESULTS = 1<<5;
}
/**
@@ -157,7 +151,7 @@
.put(AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE, Integer.class)
.put(AccountColumns.SettingsColumns.SNAP_HEADERS, Integer.class)
.put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR, Integer.class)
- .put(AccountColumns.SettingsColumns.SHOW_CHECKBOXES, Integer.class)
+ .put(AccountColumns.SettingsColumns.HIDE_CHECKBOXES, Integer.class)
.put(AccountColumns.SettingsColumns.CONFIRM_DELETE, Integer.class)
.put(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, Integer.class)
.put(AccountColumns.SettingsColumns.CONFIRM_SEND, Integer.class)
@@ -209,7 +203,7 @@
public static final int ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN = 28;
public static final int ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN = 29;
public static final int ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN = 30;
- public static final int ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN = 31;
+ public static final int ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN = 31;
public static final int ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN = 32;
public static final int ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN = 33;
public static final int ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN = 34;
@@ -534,9 +528,9 @@
/**
* Integer column containing the user's specified checkbox preference. A
- * non zero value means to show checkboxes.
+ * non zero value means to hide checkboxes.
*/
- public static final String SHOW_CHECKBOXES = "show_checkboxes";
+ public static final String HIDE_CHECKBOXES = "hide_checkboxes";
/**
* Integer column containing the user's specified confirm delete preference value.
@@ -644,8 +638,8 @@
.put(AccountColumns.SettingsColumns.SNAP_HEADERS,ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN)
.put(AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN)
- .put(AccountColumns.SettingsColumns.SHOW_CHECKBOXES,
- ACCOUNT_SETTINGS_SHOW_CHECKBOXES_COLUMN)
+ .put(AccountColumns.SettingsColumns.HIDE_CHECKBOXES,
+ ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN)
.put(AccountColumns.SettingsColumns.CONFIRM_DELETE,
ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN)
.put(AccountColumns.SettingsColumns.CONFIRM_ARCHIVE,
@@ -1808,11 +1802,17 @@
* setting this state will tell the provider to initiate a download to
* the accompanying destination in {@link AttachmentColumns#DESTINATION}
* .
- * <p>
- * Valid next states: {@link #NOT_SAVED}, {@link #FAILED},
- * {@link #SAVED}
*/
public static final int REDOWNLOADING = 4;
+ /**
+ * The attachment is either pending or paused in the download manager.
+ * {@link AttachmentColumns#DOWNLOADED_SIZE} should reflect the current
+ * download progress while in this state. This state may not be used as
+ * a command on its own.
+ * <p>
+ * Valid next states: {@link #DOWNLOADING}, {@link #FAILED}
+ */
+ public static final int PAUSED = 5;
private AttachmentState() {}
}
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index e736e28..1e3886b 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -36,6 +36,7 @@
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.Loader;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
@@ -59,10 +60,9 @@
import com.android.mail.R;
import com.android.mail.browse.ConfirmDialogFragment;
import com.android.mail.browse.ConversationCursor;
-import com.android.mail.browse.ConversationItemView;
+import com.android.mail.browse.ConversationCursor.ConversationOperation;
import com.android.mail.browse.ConversationItemViewModel;
import com.android.mail.browse.ConversationPagerController;
-import com.android.mail.browse.ConversationCursor.ConversationOperation;
import com.android.mail.browse.MessageCursor.ConversationMessage;
import com.android.mail.browse.SelectedConversationsActionMenu;
import com.android.mail.browse.SyncErrorDialogFragment;
@@ -140,6 +140,8 @@
private static final String SAVED_ACTION = "saved-action";
/** Tag for {@link #mDialogFromSelectedSet} */
private static final String SAVED_ACTION_FROM_SELECTED = "saved-action-from-selected";
+ /** Tag for {@link #mDetachedConvUri} */
+ private static final String SAVED_DETACHED_CONV_URI = "saved-detached-conv-uri";
/** Tag used when loading a wait fragment */
protected static final String TAG_WAIT = "wait-fragment";
@@ -159,6 +161,10 @@
protected final RecentFolderList mRecentFolderList;
protected ConversationListContext mConvListContext;
protected Conversation mCurrentConversation;
+ /**
+ * The hash of {@link #mCurrentConversation} in detached mode. 0 if we are not in detached mode.
+ */
+ private Uri mDetachedConvUri;
/** A {@link android.content.BroadcastReceiver} that suppresses new e-mail notifications. */
private SuppressNotificationReceiver mNewEmailReceiver = null;
@@ -177,6 +183,9 @@
private boolean mDestroyed;
+ /** True if running on tablet */
+ private final boolean mIsTablet;
+
/**
* Are we in a point in the Activity/Fragment lifecycle where it's safe to execute fragment
* transactions? (including back stack manipulation)
@@ -206,6 +215,19 @@
}
};
+ /**
+ * Interface for actions that are deferred until after a load completes. This is for handling
+ * user actions which affect cursors (e.g. marking messages read or unread) that happen before
+ * that cursor is loaded.
+ */
+ private interface LoadFinishedCallback {
+ void onLoadFinished();
+ }
+
+ /** The deferred actions to execute when mConversationListCursor load completes. */
+ private final ArrayList<LoadFinishedCallback> mConversationListLoadFinishedCallbacks =
+ new ArrayList<LoadFinishedCallback>();
+
private RefreshTimerTask mConversationListRefreshTask;
/** Listeners that are interested in changes to the current account. */
@@ -339,11 +361,11 @@
// aware of the selected set.
mSelectedSet.addObserver(this);
- mFolderItemUpdateDelayMs =
- mContext.getResources().getInteger(R.integer.folder_item_refresh_delay_ms);
- mShowUndoBarDelay =
- mContext.getResources().getInteger(R.integer.show_undo_bar_delay_ms);
+ final Resources r = mContext.getResources();
+ mFolderItemUpdateDelayMs = r.getInteger(R.integer.folder_item_refresh_delay_ms);
+ mShowUndoBarDelay = r.getInteger(R.integer.show_undo_bar_delay_ms);
mVeiledMatcher = VeiledAddressMatcher.newInstance(activity.getResources());
+ mIsTablet = Utils.useTabletUI(r);
}
@Override
@@ -826,9 +848,7 @@
if (savedState.containsKey(SAVED_ACTION)) {
mDialogAction = savedState.getInt(SAVED_ACTION);
}
- if (savedState.containsKey(SAVED_ACTION_FROM_SELECTED)) {
- mDialogFromSelectedSet = savedState.getBoolean(SAVED_ACTION_FROM_SELECTED);
- }
+ mDialogFromSelectedSet = savedState.getBoolean(SAVED_ACTION_FROM_SELECTED, false);
mViewMode.handleRestore(savedState);
} else if (intent != null) {
handleIntent(intent);
@@ -874,7 +894,6 @@
@Override
public final boolean onKeyDown(int keyCode, KeyEvent event) {
- // TODO(viki): Auto-generated method stub
return false;
}
@@ -1012,8 +1031,8 @@
}
@Override
- public void markConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
- byte[] originalConversationInfo) {
+ public void markConversationMessagesUnread(final Conversation conv,
+ final Set<Uri> unreadMessageUris, final byte[] originalConversationInfo) {
// The only caller of this method is the conversation view, from where marking unread should
// *always* take you back to list mode.
showConversation(null);
@@ -1021,12 +1040,24 @@
// locally mark conversation unread (the provider is supposed to propagate message unread
// to conversation unread)
conv.read = false;
-
if (mConversationListCursor == null) {
- LogUtils.e(LOG_TAG, "null ConversationCursor in markConversationMessagesUnread");
- return;
- }
+ LogUtils.d(LOG_TAG, "deferring markConversationMessagesUnread for id=%d", conv.id);
+ mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
+ @Override
+ public void onLoadFinished() {
+ doMarkConversationMessagesUnread(conv, unreadMessageUris,
+ originalConversationInfo);
+ }
+ });
+ } else {
+ doMarkConversationMessagesUnread(conv, unreadMessageUris, originalConversationInfo);
+ }
+ }
+
+ private void doMarkConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
+ byte[] originalConversationInfo) {
+ LogUtils.d(LOG_TAG, "performing markConversationMessagesUnread for id=%d", conv.id);
// only do a granular 'mark unread' if a subset of messages are unread
final int unreadCount = (unreadMessageUris == null) ? 0 : unreadMessageUris.size();
final int numMessages = conv.getNumMessages();
@@ -1068,14 +1099,25 @@
}
@Override
- public void markConversationsRead(Collection<Conversation> targets, boolean read,
- boolean viewed) {
- // We want to show the next conversation if we are marking unread.
- markConversationsRead(targets, read, viewed, true);
+ public void markConversationsRead(final Collection<Conversation> targets, final boolean read,
+ final boolean viewed) {
+ if (mConversationListCursor == null) {
+ LogUtils.d(LOG_TAG, "deferring markConversationsRead");
+ mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
+ @Override
+ public void onLoadFinished() {
+ markConversationsRead(targets, read, viewed, true);
+ }
+ });
+ } else {
+ // We want to show the next conversation if we are marking unread.
+ markConversationsRead(targets, read, viewed, true);
+ }
}
private void markConversationsRead(final Collection<Conversation> targets, final boolean read,
final boolean markViewed, final boolean showNext) {
+ LogUtils.d(LOG_TAG, "performing markConversationsRead");
// Auto-advance if requested and the current conversation is being marked unread
if (showNext && !read) {
final Runnable operation = new Runnable() {
@@ -1141,6 +1183,8 @@
* <p>Does nothing if outside of conversation mode.</p>
*
* @param target the set of conversations being deleted/marked unread
+ * @param operation if auto-advance setting is unset, this operation is run after the user
+ * is prompted to select a setting.
* @return <code>false</code> if we aborted because the user has not yet specified a default
* action, <code>true</code> otherwise
*/
@@ -1154,7 +1198,7 @@
if (currentConversationInView) {
final int autoAdvanceSetting = mAccount.settings.getAutoAdvanceSetting();
- if (autoAdvanceSetting == AutoAdvance.UNSET && Utils.useTabletUI(mContext)) {
+ if (autoAdvanceSetting == AutoAdvance.UNSET && mIsTablet) {
displayAutoAdvanceDialogAndPerformAction(operation);
return false;
} else {
@@ -1389,6 +1433,9 @@
outState.putInt(SAVED_ACTION, mDialogAction);
outState.putBoolean(SAVED_ACTION_FROM_SELECTED, mDialogFromSelectedSet);
}
+ if (mDetachedConvUri != null) {
+ outState.putParcelable(SAVED_DETACHED_CONV_URI, mDetachedConvUri);
+ }
mSafeToModifyFragments = false;
outState.putString(SAVED_HIERARCHICAL_FOLDER,
(mFolderListFolder != null) ? Folder.toString(mFolderListFolder) : null);
@@ -1523,7 +1570,7 @@
*/
@Override
public void onRestoreInstanceState(Bundle savedState) {
- LogUtils.d(LOG_TAG, "IN AAC.onRestoreInstanceState");
+ mDetachedConvUri = savedState.getParcelable(SAVED_DETACHED_CONV_URI);
if (savedState.containsKey(SAVED_CONVERSATION)) {
// Open the conversation.
final Conversation conversation = savedState.getParcelable(SAVED_CONVERSATION);
@@ -1765,7 +1812,7 @@
public final void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
// Only animate destructive actions if we are going to be showing the
// conversation list when we show the next conversation.
- commitDestructiveActions(Utils.useTabletUI(mContext));
+ commitDestructiveActions(mIsTablet);
showConversation(conversation, inLoaderCallbacks);
}
@@ -1782,7 +1829,14 @@
*/
@Override
public void setCurrentConversation(Conversation conversation) {
- // Must be the first call because this sets conversation.position if a cursor is available.
+ // The controller should come out of detached mode if a new conversation is viewed, or if
+ if (conversation == null || (mDetachedConvUri != null
+ && !mDetachedConvUri.equals(conversation.uri))) {
+ clearDetachedMode();
+ }
+
+ // Must happen *before* setting mCurrentConversation because this sets
+ // conversation.position if a cursor is available.
mTracker.initialize(conversation);
mCurrentConversation = conversation;
@@ -2105,7 +2159,7 @@
// default recents. The default recents will not stomp on the existing value: it
// will be shown in addition to the default folders: the max number of recent
// folders is more than 1+num(defaultRecents).
- if (data != null && data.getCount() <= 1 && !Utils.useTabletUI(mContext)) {
+ if (data != null && data.getCount() <= 1 && !mIsTablet) {
final class PopulateDefault extends AsyncTask<Uri, Void, Void> {
@Override
protected Void doInBackground(Uri... uri) {
@@ -2502,7 +2556,7 @@
@Override
public void onSetPopulated(ConversationSelectionSet set) {
mCabActionMenu = new SelectedConversationsActionMenu(mActivity, set, mFolder);
- if (mViewMode.isListMode()) {
+ if (mViewMode.isListMode() || (mIsTablet && mViewMode.isConversationMode())) {
enableCabMode();
}
}
@@ -2765,6 +2819,11 @@
mConversationListCursor.addListener(AbstractActivityController.this);
mTracker.onCursorUpdated();
mConversationListObservable.notifyChanged();
+ // Handle actions that were deferred until after the conversation list was loaded.
+ for (LoadFinishedCallback callback : mConversationListLoadFinishedCallbacks) {
+ callback.onLoadFinished();
+ }
+ mConversationListLoadFinishedCallbacks.clear();
final ConversationListFragment convList = getConversationListFragment();
if (isFragmentVisible(convList)) {
@@ -3173,7 +3232,7 @@
// Clear the cache of objects.
ConversationItemViewModel.onAccessibilityUpdated();
// Re-render the list if it exists.
- ConversationListFragment frag = getConversationListFragment();
+ final ConversationListFragment frag = getConversationListFragment();
if (frag != null) {
AnimatedAdapter adapter = frag.getAnimatedAdapter();
if (adapter != null) {
@@ -3224,5 +3283,31 @@
@Override
public VeiledAddressMatcher getVeiledAddressMatcher() {
return mVeiledMatcher;
- };
+ }
+
+ @Override
+ public void setDetachedMode() {
+ // Tell the conversation list not to select anything.
+ final ConversationListFragment frag = getConversationListFragment();
+ if (frag != null) {
+ frag.setChoiceNone();
+ } else {
+ // How did we ever land here? Detached mode, and no CLF???
+ LogUtils.e(LOG_TAG, "AAC.setDetachedMode(): CLF = null!");
+ }
+ mDetachedConvUri = mCurrentConversation.uri;
+ }
+
+ private void clearDetachedMode() {
+ // Tell the conversation list to go back to its usual selection behavior.
+ final ConversationListFragment frag = getConversationListFragment();
+ if (frag != null) {
+ frag.revertChoiceMode();
+ } else {
+ // How did we ever land here? Detached mode, and no CLF???
+ LogUtils.e(LOG_TAG, "AAC.clearDetachedMode(): CLF = null!");
+ }
+ mDetachedConvUri = null;
+ }
+
}
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index 0c2e355..74a3d9a 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -63,6 +63,7 @@
import com.android.mail.providers.Folder;
import com.android.mail.providers.ListParams;
import com.android.mail.providers.UIProvider;
+import com.android.mail.providers.UIProvider.CursorStatus;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
@@ -114,13 +115,6 @@
*/
protected ConversationViewState mViewState;
- /**
- * Handles a deferred 'mark read' operation, necessary when the conversation view has finished
- * loading before the conversation cursor. Normally null unless this situation occurs.
- * When finally able to 'mark read', this observer will also be unregistered and cleaned up.
- */
- private MarkReadObserver mMarkReadObserver;
-
private long mLoadingShownTime = -1;
private final Runnable mDelayedShow = new FragmentRunnable("mDelayedShow") {
@@ -424,11 +418,6 @@
public void onDestroyView() {
super.onDestroyView();
mAccountObserver.unregisterAndDestroy();
- if (mMarkReadObserver != null) {
- mActivity.getConversationUpdater().unregisterConversationListObserver(
- mMarkReadObserver);
- mMarkReadObserver = null;
- }
}
/**
@@ -476,9 +465,9 @@
LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
}
- // When the last cursor had message(s), and the new version has
- // no messages, we need to exit conversation view.
- if (messageCursor.getCount() == 0 && mCursor != null) {
+ // We have no messages: exit conversation view.
+ if (messageCursor.getCount() == 0
+ && !CursorStatus.isWaitingForResults(messageCursor.getStatus())) {
if (mUserVisible) {
onError();
} else {
@@ -558,16 +547,14 @@
// but we do want future re-renders to mark read (e.g. "New message from X" case)
MessageCursor cursor = getMessageCursor();
if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
- final ConversationUpdater listController = activity.getConversationUpdater();
- // The conversation cursor may not have finished loading by now (when launched via
- // notification), so watch for when it finishes and mark it read then.
- if (listController.getConversationListCursor() == null) {
- LogUtils.i(LOG_TAG, "deferring conv mark read on open for id=%d",
- mConversation.id);
- mMarkReadObserver = new MarkReadObserver(listController);
- listController.registerConversationListObserver(mMarkReadObserver);
- } else {
- markReadOnSeen(listController);
+ // Mark the conversation viewed and read.
+ activity.getConversationUpdater().markConversationsRead(Arrays.asList(mConversation),
+ true /* read */, true /* viewed */);
+
+ // and update the Message objects in the cursor so the next time a cursor update happens
+ // with these messages marked read, we know to ignore it
+ if (cursor != null) {
+ cursor.markMessagesRead();
}
}
@@ -576,19 +563,6 @@
showAutoFitPrompt();
}
- protected void markReadOnSeen(ConversationUpdater listController) {
- // Mark the conversation viewed and read.
- listController.markConversationsRead(Arrays.asList(mConversation), true /* read */,
- true /* viewed */);
-
- // and update the Message objects in the cursor so the next time a cursor update happens
- // with these messages marked read, we know to ignore it
- MessageCursor cursor = getMessageCursor();
- if (cursor != null) {
- cursor.markMessagesRead();
- }
- }
-
protected ConversationViewState getNewViewState() {
return new ConversationViewState();
}
@@ -751,27 +725,6 @@
}
}
- private class MarkReadObserver extends DataSetObserver {
- private final ConversationUpdater mListController;
-
- private MarkReadObserver(ConversationUpdater listController) {
- mListController = listController;
- }
-
- @Override
- public void onChanged() {
- if (mListController.getConversationListCursor() == null) {
- // nothing yet, keep watching
- return;
- }
- // done loading, safe to mark read now
- mListController.unregisterConversationListObserver(this);
- mMarkReadObserver = null;
- LogUtils.i(LOG_TAG, "running deferred conv mark read on open, id=%d", mConversation.id);
- markReadOnSeen(mListController);
- }
- }
-
public abstract void onConversationUpdated(Conversation conversation);
/**
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index e320ac3..abb1dd6 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -127,10 +127,10 @@
private static final String LOG_TAG = LogTag.getLogTag();
private static final int INCREASE_WAIT_COUNT = 2;
- public AnimatedAdapter(Context context, int textViewResourceId, ConversationCursor cursor,
- ConversationSelectionSet batch,
- ControllableActivity activity, SwipeableListView listView) {
- super(context, textViewResourceId, cursor, UIProvider.CONVERSATION_PROJECTION, null, 0);
+ public AnimatedAdapter(Context context, ConversationCursor cursor,
+ ConversationSelectionSet batch, ControllableActivity activity,
+ SwipeableListView listView) {
+ super(context, -1, cursor, UIProvider.CONVERSATION_PROJECTION, null, 0);
mContext = context;
mBatchConversations = batch;
setAccount(mAccountListener.initialize(activity.getAccountController()));
@@ -211,7 +211,7 @@
view = new SwipeableConversationItemView(context, mAccount.name);
}
view.bind(conv, mActivity, mBatchConversations, mFolder,
- mAccount != null ? !mAccount.settings.showCheckboxes : false, mSwipeEnabled,
+ mAccount != null ? mAccount.settings.hideCheckboxes : false, mSwipeEnabled,
mPriorityMarkersEnabled, this);
return view;
}
@@ -541,7 +541,7 @@
return;
}
((SwipeableConversationItemView) view).bind(cursor, mActivity, mBatchConversations, mFolder,
- mAccount != null ? !mAccount.settings.showCheckboxes : false,
+ mAccount != null ? mAccount.settings.hideCheckboxes : false,
mSwipeEnabled, mPriorityMarkersEnabled, this);
}
@@ -551,7 +551,7 @@
position, null, parent);
view.reset();
view.bind(conversation, mActivity, mBatchConversations, mFolder,
- mAccount != null ? !mAccount.settings.showCheckboxes : false, mSwipeEnabled,
+ mAccount != null ? mAccount.settings.hideCheckboxes : false, mSwipeEnabled,
mPriorityMarkersEnabled, this);
mAnimatingViews.put(conversation.id, view);
return view;
@@ -797,4 +797,4 @@
item.cancelFadeOutText();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/mail/ui/ConversationListCallbacks.java b/src/com/android/mail/ui/ConversationListCallbacks.java
index 974de55..40e75cb 100644
--- a/src/com/android/mail/ui/ConversationListCallbacks.java
+++ b/src/com/android/mail/ui/ConversationListCallbacks.java
@@ -73,4 +73,9 @@
* Detect if there are any animations occuring in the conversation list.
*/
boolean isAnimating();
+
+ /**
+ * Tell the controller that the conversation view has entered detached mode.
+ */
+ void setDetachedMode();
}
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 12f31fa..a54aab5 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -69,6 +69,8 @@
private static final String LIST_STATE_KEY = "list-state";
private static final String LOG_TAG = LogTag.getLogTag();
+ /** Key used to save the ListView choice mode, since ListView doesn't save it automatically! */
+ private static final String CHOICE_MODE_KEY = "choice-mode-key";
// True if we are on a tablet device
private static boolean mTabletDevice;
@@ -116,7 +118,7 @@
private View mEmptyView;
private ErrorListener mErrorListener;
private DataSetObserver mFolderObserver;
- private DataSetObserver mConversationListStatusObserver;
+ private DataSetObserver mConversationCursorObserver;
private ConversationSelectionSet mSelectedSet;
private final AccountObserver mAccountObserver = new AccountObserver() {
@@ -153,7 +155,7 @@
}
}
- private class ConversationListStatusObserver extends DataSetObserver {
+ private class ConversationCursorObserver extends DataSetObserver {
@Override
public void onChanged() {
onConversationListStatusUpdated();
@@ -233,8 +235,8 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onActivityCreated(Bundle savedState) {
+ super.onActivityCreated(savedState);
// Strictly speaking, we get back an android.app.Activity from
// getActivity. However, the
// only activity creating a ConversationListContext is a MailActivity
@@ -262,7 +264,7 @@
null);
mFooterView.setClickListener(mActivity);
final ConversationCursor conversationCursor = getConversationListCursor();
- mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(), -1,
+ mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(),
conversationCursor, mActivity.getSelectedSet(), mActivity, mListView);
mListAdapter.addFooter(mFooterView);
mListView.setAdapter(mListAdapter);
@@ -271,10 +273,10 @@
mListAdapter.hideFooter();
mFolderObserver = new FolderObserver();
mActivity.getFolderController().registerFolderObserver(mFolderObserver);
- mConversationListStatusObserver = new ConversationListStatusObserver();
+ mConversationCursorObserver = new ConversationCursorObserver();
mUpdater = mActivity.getConversationUpdater();
- mUpdater.registerConversationListObserver(mConversationListStatusObserver);
- mTabletDevice = Utils.useTabletUI(mActivity.getApplicationContext());
+ mUpdater.registerConversationListObserver(mConversationCursorObserver);
+ mTabletDevice = Utils.useTabletUI(mActivity.getApplicationContext().getResources());
initializeUiForFirstDisplay();
configureSearchResultHeader();
// The onViewModeChanged callback doesn't get called when the mode
@@ -294,6 +296,21 @@
conversationCursor.sync();
}
+ // On a phone we never highlight a conversation, so the default is to select none.
+ // On a tablet, we highlight a SINGLE conversation in landscape conversation view.
+ int choice = getDefaultChoiceMode(mTabletDevice);
+ if (savedState != null) {
+ // Restore the choice mode if it was set earlier, or NONE if creating a fresh view.
+ // Choice mode here represents the current conversation only. CAB mode does not rely on
+ // the platform: checked state is a local variable {@link ConversationItemView#mChecked}
+ choice = savedState.getInt(CHOICE_MODE_KEY, choice);
+ if (savedState.containsKey(LIST_STATE_KEY)) {
+ // TODO: find a better way to unset the selected item when restoring
+ mListView.clearChoices();
+ }
+ }
+ setChoiceMode(choice);
+
// Show list and start loading list.
showList();
ToastBarOperation pendingOp = mActivity.getPendingToastOperation();
@@ -304,6 +321,16 @@
}
}
+ /**
+ * Returns the default choice mode for the list based on whether the list is displayed on tablet
+ * or not.
+ * @param isTablet
+ * @return
+ */
+ private final static int getDefaultChoiceMode(boolean isTablet) {
+ return isTablet ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE;
+ }
+
public AnimatedAdapter getAnimatedAdapter() {
return mListAdapter;
}
@@ -337,22 +364,57 @@
mEmptyView = rootView.findViewById(R.id.empty_view);
mListView = (SwipeableListView) rootView.findViewById(android.R.id.list);
mListView.setHeaderDividersEnabled(false);
- // Choice mode here represents the current conversation only. CAB mode does not rely on the
- // platform: it is a local variable within conversation items.
- mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mListView.setOnItemLongClickListener(this);
mListView.enableSwipe(mAccount.supportsCapability(AccountCapabilities.UNDO));
mListView.setSwipedListener(this);
- // Restore the list state
if (savedState != null && savedState.containsKey(LIST_STATE_KEY)) {
mListView.onRestoreInstanceState(savedState.getParcelable(LIST_STATE_KEY));
- // TODO: find a better way to unset the selected item when restoring
- mListView.clearChoices();
}
return rootView;
}
+ /**
+ * Sets the choice mode of the list view
+ * @param choiceMode ListView#
+ */
+ private final void setChoiceMode(int choiceMode) {
+ mListView.setChoiceMode(choiceMode);
+ }
+
+ /**
+ * Tell the list to select nothing.
+ */
+ public final void setChoiceNone() {
+ // On a phone, the default choice mode is already none, so nothing to do.
+ if (!mTabletDevice) {
+ return;
+ }
+ final int currentSelected = mListView.getCheckedItemPosition();
+ mListView.clearChoices();
+ // We use the activated state to show the blue highlight on tablet. Clearing the choices
+ // removes the checked state, but doesn't do anything to the activated state. We must
+ // manually clear that.
+ if (currentSelected != ListView.INVALID_POSITION) {
+ final View v = mListView.getChildAt(currentSelected);
+ if (v != null) {
+ v.setActivated(false);
+ }
+ }
+ setChoiceMode(ListView.CHOICE_MODE_NONE);
+ }
+
+ /**
+ * Tell the list to get out of selecting none.
+ */
+ public final void revertChoiceMode() {
+ // On a phone, the default choice mode is always none, so nothing to do.
+ if (!mTabletDevice) {
+ return;
+ }
+ setChoiceMode(getDefaultChoiceMode(mTabletDevice));
+ }
+
@Override
public void onDestroy() {
super.onDestroy();
@@ -370,9 +432,9 @@
mActivity.getFolderController().unregisterFolderObserver(mFolderObserver);
mFolderObserver = null;
}
- if (mConversationListStatusObserver != null) {
- mUpdater.unregisterConversationListObserver(mConversationListStatusObserver);
- mConversationListStatusObserver = null;
+ if (mConversationCursorObserver != null) {
+ mUpdater.unregisterConversationListObserver(mConversationCursorObserver);
+ mConversationCursorObserver = null;
}
mAccountObserver.unregisterAndDestroy();
super.onDestroyView();
@@ -435,7 +497,7 @@
if (!(view instanceof ToggleableItem)) {
return;
}
- if (!mAccount.settings.showCheckboxes && !mSelectedSet.isEmpty()) {
+ if (mAccount.settings.hideCheckboxes && !mSelectedSet.isEmpty()) {
ToggleableItem v = (ToggleableItem) view;
v.toggleCheckMarkOrBeginDrag();
} else {
@@ -444,7 +506,7 @@
// When a new list item is clicked, commit any existing leave behind
// items. Wait until we have opened the desired conversation to cause
// any position changes.
- commitDestructiveActions(Utils.useTabletUI(mActivity.getActivityContext()));
+ commitDestructiveActions(Utils.useTabletUI(mActivity.getActivityContext().getResources()));
}
@Override
@@ -457,6 +519,7 @@
super.onSaveInstanceState(outState);
if (mListView != null) {
outState.putParcelable(LIST_STATE_KEY, mListView.onSaveInstanceState());
+ outState.putInt(CHOICE_MODE_KEY, mListView.getChoiceMode());
}
}
@@ -476,17 +539,13 @@
public void onViewModeChanged(int newMode) {
// Change the divider based on view mode.
if (mTabletDevice) {
- if (newMode == ViewMode.CONVERSATION) {
+ if (ViewMode.isConversationMode(newMode)) {
mListView.setBackgroundResource(R.drawable.panel_conversation_leftstroke);
- } else if (newMode == ViewMode.CONVERSATION_LIST
- || newMode == ViewMode.SEARCH_RESULTS_LIST) {
- // There are no selected conversations when in conversation
- // list mode.
- mListView.clearChoices();
+ } else if (ViewMode.isListMode(newMode)) {
+ // There are no selected conversations when in conversation list mode.
mListView.setBackgroundDrawable(null);
+ mListView.clearChoices();
}
- } else {
- mListView.setBackgroundDrawable(null);
}
if (mFooterView != null) {
mFooterView.onViewModeChanged(newMode);
@@ -510,7 +569,7 @@
*/
protected void viewConversation(int position) {
LogUtils.d(LOG_TAG, "ConversationListFragment.viewConversation(%d)", position);
- setSelected(position);
+ setSelected(position, true);
final ConversationCursor cursor = getConversationListCursor();
if (cursor != null && cursor.moveToPosition(position)) {
final Conversation conv = new Conversation(cursor);
@@ -519,8 +578,17 @@
}
}
-
+ /**
+ * Sets the selected conversation to the position given here.
+ * @param position
+ * @param different if the currently selected conversation is different from the one provided
+ * here. This is a difference in conversations, not a difference in positions. For example, a
+ * conversation at position 2 can move to position 4 as a result of new mail.
+ */
public void setSelected(int position, boolean different) {
+ if (mListView.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
+ return;
+ }
if (different) {
mListView.smoothScrollToPosition(position);
}
@@ -528,14 +596,9 @@
}
/**
- * Sets the selected position (the highlighted conversation) to the position
- * provided here.
- * @param position
+ * Returns the cursor associated with the conversation list.
+ * @return
*/
- protected final void setSelected(int position) {
- setSelected(position, true);
- }
-
private ConversationCursor getConversationListCursor() {
return mCallbacks != null ? mCallbacks.getConversationListCursor() : null;
}
@@ -693,8 +756,9 @@
if (conv == null) {
return;
}
- if (mListView.getCheckedItemPosition() == -1) {
- setSelected(conv.position);
+ if (mListView.getChoiceMode() != ListView.CHOICE_MODE_NONE
+ && mListView.getCheckedItemPosition() == -1) {
+ setSelected(conv.position, true);
}
}
diff --git a/src/com/android/mail/ui/ConversationPositionTracker.java b/src/com/android/mail/ui/ConversationPositionTracker.java
index 3d4f86f..46ca0a0 100644
--- a/src/com/android/mail/ui/ConversationPositionTracker.java
+++ b/src/com/android/mail/ui/ConversationPositionTracker.java
@@ -17,16 +17,12 @@
package com.android.mail.ui;
-import android.database.Cursor;
-
import com.android.mail.browse.ConversationCursor;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Settings;
import com.android.mail.providers.UIProvider.AutoAdvance;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
-
import java.util.Collection;
/**
@@ -132,7 +128,7 @@
public void initialize(Conversation conversation) {
mConversation = conversation;
mCursorDirty = true;
- calculatePosition();
+ calculatePosition(); // Return value discarded. Running for side effects.
}
/** @return whether or not we have a valid cursor to check the position of. */
@@ -179,7 +175,7 @@
return invalidPosition;
}
mCursorDirty = false;
- final int listSize = (cursor == null) ? 0 : cursor.getCount();
+ final int listSize = cursor.getCount();
if (!isDataLoaded(cursor) || listSize == 0) {
return invalidPosition;
}
@@ -198,21 +194,24 @@
// If the conversation is no longer found in the list, try to save the same position if
// it is still a valid position. Otherwise, go back to a valid position until we can
// find a valid one.
- int newPosition = foundPosition;
- if (mConversation.position >= listSize || foundPosition >= listSize) {
+ final int newPosition;
+ if (foundPosition >= listSize) {
// Go to the last position since our expected position is past this somewhere.
- newPosition = cursor.getCount() - 1;
+ newPosition = listSize - 1;
+ } else {
+ newPosition = foundPosition;
}
- // Did not keep the same conversation, but could still be a valid conversation.
- if (isDataLoaded(cursor)){
+ // Did not keep the current conversation, so let's try to load the conversation from the
+ // new position.
+ if (isDataLoaded(cursor) && newPosition >= 0){
LogUtils.d(LOG_TAG, "ConversationPositionTracker: Could not find conversation %s" +
" in the cursor. Moving to position %d ", mConversation.toString(),
newPosition);
cursor.moveToPosition(newPosition);
mConversation = new Conversation(cursor);
+ mConversation.position = newPosition;
}
-
return newPosition;
}
@@ -221,10 +220,10 @@
* conversations available in the folder. If no next conversation can be found, this method
* returns null.
* @param autoAdvance the auto advance preference for the user as an
- * {@link Settings#autoAdvance} value.
+ * {@link Settings#getAutoAdvanceSetting()} value.
* @param mTarget conversations to overlook while finding the next conversation. (These are
* usually the conversations to be deleted.)
- * @return
+ * @return the next conversation to be shown, or null if no next conversation exists.
*/
public Conversation getNextConversation(int autoAdvance, Collection<Conversation> mTarget) {
final boolean getNewer = autoAdvance == AutoAdvance.NEWER;
diff --git a/src/com/android/mail/ui/EllipsizedMultilineTextView.java b/src/com/android/mail/ui/EllipsizedMultilineTextView.java
index 0f83f27..fca39a9 100644
--- a/src/com/android/mail/ui/EllipsizedMultilineTextView.java
+++ b/src/com/android/mail/ui/EllipsizedMultilineTextView.java
@@ -64,17 +64,18 @@
* @param avail available width in pixels for the last line
* @param paint Paint that has the proper properties set to measure the text
* for this view
+ * @return the {@link CharSequence} that was set on the {@link TextView}
*/
- public void setText(final CharSequence text, int avail) {
+ public CharSequence setText(final CharSequence text, int avail) {
if (text == null || text.length() == 0) {
- return;
+ return text;
}
setEllipsize(null);
setText(text, TextView.BufferType.SPANNABLE);
if (avail == ALL_AVAILABLE) {
- return;
+ return text;
}
Layout layout = getLayout();
@@ -85,7 +86,7 @@
if (layout == null) {
// Bail
- return;
+ return text;
}
CharSequence remainder;
@@ -104,5 +105,7 @@
builder.append(remainder);
}
setText(builder, TextView.BufferType.SPANNABLE);
+
+ return builder;
}
}
\ No newline at end of file
diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java
index ea52172..ac83a2f 100644
--- a/src/com/android/mail/ui/MailActionBarView.java
+++ b/src/com/android/mail/ui/MailActionBarView.java
@@ -22,6 +22,7 @@
import android.app.SearchableInfo;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
@@ -33,7 +34,6 @@
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SearchView;
-import android.widget.TextView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.SearchView.OnSuggestionListener;
@@ -61,10 +61,12 @@
public class MailActionBarView extends LinearLayout implements ViewMode.ModeChangeListener,
OnQueryTextListener, OnSuggestionListener, MenuItem.OnActionExpandListener,
SubjectDisplayChanger {
+
+ private static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;
+
protected ActionBar mActionBar;
protected ControllableActivity mActivity;
protected ActivityController mController;
- private View mFolderView;
/**
* The current mode of the ActionBar. This references constants in {@link ViewMode}
*/
@@ -106,19 +108,16 @@
}
};
private final boolean mShowConversationSubject;
- private TextView mFolderAccountName;
private DataSetObserver mFolderObserver;
private final AccountObserver mAccountObserver = new AccountObserver() {
@Override
public void onChanged(Account newAccount) {
updateAccount(newAccount);
- if (mFolderAccountName != null) {
- mFolderAccountName.setText(mAccount.name);
- }
mSpinner.setAccount(mAccount);
}
};
+
/** True if the application has more than one account. */
private boolean mHasManyAccounts;
@@ -132,8 +131,9 @@
public MailActionBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mShowConversationSubject = getResources().getBoolean(R.bool.show_conversation_subject);
- mIsOnTablet = Utils.useTabletUI(context);
+ final Resources r = getResources();
+ mShowConversationSubject = r.getBoolean(R.bool.show_conversation_subject);
+ mIsOnTablet = Utils.useTabletUI(r);
}
// update the pager title strip as the Folder's conversation count changes
@@ -149,8 +149,6 @@
super.onFinishInflate();
mSubjectView = (SnippetTextView) findViewById(R.id.conversation_subject);
- mFolderView = findViewById(R.id.folder_layout);
- mFolderAccountName = (TextView) mFolderView.findViewById(R.id.account);
}
/**
@@ -238,8 +236,9 @@
mFolderObserver = new FolderObserver();
mController.registerFolderObserver(mFolderObserver);
// We don't want to include the "Show all folders" menu item on tablet devices
- final boolean showAllFolders = !Utils.useTabletUI(getContext());
- mSpinnerAdapter = new AccountSpinnerAdapter(activity, getContext(), showAllFolders);
+ final Context context = getContext();
+ final boolean showAllFolders = !Utils.useTabletUI(context.getResources());
+ mSpinnerAdapter = new AccountSpinnerAdapter(activity, context, showAllFolders);
mSpinner = (MailSpinner) findViewById(R.id.account_spinner);
mSpinner.setAdapter(mSpinnerAdapter);
mSpinner.setController(mController);
@@ -331,8 +330,6 @@
mSpinnerAdapter.disableRecentFolders();
}
- boolean showFolderView = false;
-
switch (mMode) {
case ViewMode.UNKNOWN:
if (mSearch != null) {
@@ -347,13 +344,12 @@
if (!mShowConversationSubject) {
showNavList();
} else {
- setStandardMode();
+ setSnippetMode();
}
break;
case ViewMode.FOLDER_LIST:
mActionBar.setDisplayHomeAsUpEnabled(true);
- setStandardMode();
- showFolderView = true;
+ setFoldersMode();
break;
case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
// We want the user to be able to switch accounts while waiting for an account
@@ -361,7 +357,6 @@
showNavList();
break;
}
- mFolderView.setVisibility(showFolderView ? VISIBLE : GONE);
}
protected int getMode() {
@@ -425,27 +420,35 @@
* Put the ActionBar in List navigation mode. This starts the spinner up if it is missing.
*/
private void showNavList() {
+ setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
mSpinner.setVisibility(View.VISIBLE);
- mFolderView.setVisibility(View.GONE);
- mFolderAccountName.setVisibility(View.GONE);
+ mSubjectView.setVisibility(View.GONE);
}
/**
- * Set the actionbar mode to standard mode: no list navigation.
+ * Set the actionbar mode to "snippet" mode: no list navigation, show what looks like 2-line
+ * "standard" snippet. Later on, {@link #getUnshownSubject(String)} will seamlessly switch
+ * back to bog-standard SHOW_TITLE mode once the text remainders can safely be determined.
*/
- private void setStandardMode() {
+ private void setSnippetMode() {
+ setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
mSpinner.setVisibility(View.GONE);
- mFolderView.setVisibility(View.VISIBLE);
- mFolderAccountName.setVisibility(View.VISIBLE);
+ mSubjectView.setVisibility(View.VISIBLE);
+ }
+
+ private void setFoldersMode() {
+ setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
+ mActionBar.setTitle(R.string.folders);
+ mActionBar.setSubtitle(mAccount.name);
}
/**
* Set the actionbar mode to empty: no title, no custom content.
*/
protected void setEmptyMode() {
+ setTitleModeFlags(ActionBar.DISPLAY_SHOW_CUSTOM);
mSpinner.setVisibility(View.GONE);
- mFolderView.setVisibility(View.GONE);
- mFolderAccountName.setVisibility(View.GONE);
+ mSubjectView.setVisibility(View.GONE);
}
public void removeBackButton() {
@@ -596,12 +599,21 @@
return true;
}
+ private void setTitleModeFlags(int enabledFlags) {
+ final int mask = ActionBar.DISPLAY_SHOW_TITLE
+ | ActionBar.DISPLAY_SHOW_CUSTOM | DISPLAY_TITLE_MULTIPLE_LINES;
+
+ mActionBar.setDisplayOptions(enabledFlags, mask);
+ }
+
@Override
public void setSubject(String subject) {
if (!mShowConversationSubject) {
return;
}
+ mActionBar.setTitle(subject);
+ mActionBar.setSubtitle(null);
mSubjectView.setText(subject);
}
@@ -611,6 +623,8 @@
return;
}
+ mActionBar.setTitle(null);
+ mActionBar.setSubtitle(null);
mSubjectView.setText(null);
}
@@ -620,7 +634,33 @@
return subject;
}
- return mSubjectView.getTextRemainder(subject);
+ final String remainder = mSubjectView.getTextRemainder(subject);
+
+ if (actionBarReportsMultipleLineTitle(mActionBar)) {
+ // Switch over to title mode when the first fragment asks for a subject remainder.
+ // We assume that layout has happened by now, so the SnippetTextView already has
+ // measurements it needs to calculate remainders, and it's safe to switch over to
+ // TITLE mode to inherit standard system behaviors.
+ setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE | DISPLAY_TITLE_MULTIPLE_LINES);
+
+ // Work around a bug where the title's container is stuck GONE when a title is set while
+ // in CUSTOM mode.
+ mActionBar.setTitle(mActionBar.getTitle());
+ }
+
+ return remainder;
+ }
+
+ private static boolean actionBarReportsMultipleLineTitle(ActionBar bar) {
+ boolean reports = false;
+ try {
+ if (bar != null) {
+ reports = (ActionBar.class.getField("DISPLAY_TITLE_MULTIPLE_LINES") != null);
+ }
+ } catch (NoSuchFieldException e) {
+ // stay false
+ }
+ return reports;
}
public void setCurrentConversation(Conversation conversation) {
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index 470fb16..dc1e452 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -144,7 +144,7 @@
super.onCreate(savedState);
mViewMode = new ViewMode(this);
- final boolean tabletUi = Utils.useTabletUI(this);
+ final boolean tabletUi = Utils.useTabletUI(this.getResources());
mController = ControllerFactory.forActivity(this, mViewMode, tabletUi);
mController.onCreate(savedState);
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index e097ad8..1a5e257 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -77,7 +77,6 @@
@Override
public void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
- // TODO(mindyp) handle saved state.
if (inState == null) {
return;
}
@@ -95,7 +94,6 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- // TODO(mindyp) handle saved state.
outState.putInt(FOLDER_LIST_TRANSACTION_KEY, mLastFolderListTransactionId);
outState.putInt(INBOX_CONVERSATION_LIST_TRANSACTION_KEY,
mLastInboxConversationListTransactionId);
@@ -170,7 +168,6 @@
// When entering conversation list mode, hide and clean up any currently visible
// conversation.
- // TODO: improve this transition
if (ViewMode.isListMode(newMode)) {
mPagerController.hide(true /* changeVisibility */);
}
@@ -185,15 +182,11 @@
public void showConversationList(ConversationListContext listContext) {
super.showConversationList(listContext);
enableCabMode();
- // TODO(viki): Check if the account has been changed since the previous
- // time.
if (ConversationListContext.isSearchResult(listContext)) {
mViewMode.enterSearchResultsListMode();
} else {
mViewMode.enterConversationListMode();
}
- // TODO(viki): This account transition looks strange in two pane mode.
- // Revisit as the app is coming together and improve the look and feel.
final int transition = mConversationListNeverShown
? FragmentTransaction.TRANSIT_FRAGMENT_FADE
: FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
@@ -248,7 +241,6 @@
// activity, as when the transaction is popped off, the FragmentManager will attempt to
// readd the same fragment twice
if (f != null && f.isAdded()) {
- // TODO: improve this transition
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.remove(f);
ft.commitAllowingStateLoss();
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 77f9f67..0aa9a43 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -37,8 +37,6 @@
* Controller for two-pane Mail activity. Two Pane is used for tablets, where screen real estate
* abounds.
*/
-
-// Called TwoPaneActivityController in Gmail.
public final class TwoPaneController extends AbstractActivityController {
private TwoPaneLayout mLayout;
private Conversation mConversationToShow;
@@ -121,9 +119,8 @@
}
fragmentTransaction.replace(R.id.content_pane, folderListFragment, TAG_FOLDER_LIST);
fragmentTransaction.commitAllowingStateLoss();
- // Since we are showing the folder list, we are at the start of the view
- // stack.
- // TODO(viki): We don't need this call. Evaluate and remove.
+ // We only set the action bar if the viewmode has been set previously. Otherwise, we leave
+ // the action bar in the state it is currently in.
if (mViewMode.getMode() != ViewMode.UNKNOWN) {
resetActionBarIcon();
}
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 3ab91ac..5b0bab1 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -514,11 +514,10 @@
/**
* Returns a boolean indicating whether the table UI should be shown.
*/
- public static boolean useTabletUI(Context context) {
- return context.getResources().getInteger(R.integer.use_tablet_ui) != 0;
+ public static boolean useTabletUI(Resources res) {
+ return res.getInteger(R.integer.use_tablet_ui) != 0;
}
-
/**
* Returns a boolean indicating whether or not we should animate in the
* folder list fragment.