Merge "Import translations. DO NOT MERGE" into jb-ub-mail
diff --git a/res/drawable-sw600dp/folder_item.xml b/res/drawable-sw600dp/folder_item.xml
new file mode 100644
index 0000000..7632b33
--- /dev/null
+++ b/res/drawable-sw600dp/folder_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo" />
+ <item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
+ <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/folder_item.xml b/res/drawable/folder_item.xml
index 7632b33..f047291 100644
--- a/res/drawable/folder_item.xml
+++ b/res/drawable/folder_item.xml
@@ -16,8 +16,8 @@
limitations under the License.
-->
+<!-- On phone we don't want to have an activated state. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo" />
- <item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
<item android:drawable="@android:color/transparent" />
</selector>
diff --git a/res/layout/folder_list.xml b/res/layout/folder_list.xml
index 28ea922..19b0e36 100644
--- a/res/layout/folder_list.xml
+++ b/res/layout/folder_list.xml
@@ -16,7 +16,7 @@
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.mail.ui.FolderListLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -47,4 +47,4 @@
android:textAppearance="?android:attr/textAppearanceMedium"/>
</RelativeLayout>
-</FrameLayout>
+</com.android.mail.ui.FolderListLayout>
diff --git a/res/layout/one_pane_activity.xml b/res/layout/one_pane_activity.xml
index a4b31d6..07974b7 100644
--- a/res/layout/one_pane_activity.xml
+++ b/res/layout/one_pane_activity.xml
@@ -15,7 +15,8 @@
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.mail.ui.OnePaneRoot xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/one_pane_root"
android:layout_width="match_parent"
android:layout_height="match_parent" >
@@ -30,4 +31,4 @@
android:id="@+id/toast_bar"
style="@style/ToastBarStyle" />
-</FrameLayout>
\ No newline at end of file
+</com.android.mail.ui.OnePaneRoot>
\ No newline at end of file
diff --git a/src/com/android/mail/browse/ConversationContainer.java b/src/com/android/mail/browse/ConversationContainer.java
index 5fa3b0b..bbee924 100644
--- a/src/com/android/mail/browse/ConversationContainer.java
+++ b/src/com/android/mail/browse/ConversationContainer.java
@@ -37,6 +37,7 @@
import com.android.mail.utils.DequeMap;
import com.android.mail.utils.InputSmoother;
import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
import com.google.common.collect.Lists;
import java.util.List;
@@ -565,6 +566,12 @@
}
@Override
+ public void requestLayout() {
+ Utils.checkRequestLayout(this);
+ super.requestLayout();
+ }
+
+ @Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
diff --git a/src/com/android/mail/providers/MailAppProvider.java b/src/com/android/mail/providers/MailAppProvider.java
index 158287c..fbc2917 100644
--- a/src/com/android/mail/providers/MailAppProvider.java
+++ b/src/com/android/mail/providers/MailAppProvider.java
@@ -39,9 +39,12 @@
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.MatrixCursorWithExtra;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@@ -168,11 +171,11 @@
extras.putInt(AccountCursorExtraKeys.ACCOUNTS_LOADED, mAccountsFullyLoaded ? 1 : 0);
// Make a copy of the account cache
-
- final Set<AccountCacheEntry> accountList;
+ final List<AccountCacheEntry> accountList = Lists.newArrayList();
synchronized (mAccountCache) {
- accountList = ImmutableSet.copyOf(mAccountCache.values());
+ accountList.addAll(mAccountCache.values());
}
+ Collections.sort(accountList);
final MatrixCursor cursor =
new MatrixCursorWithExtra(resultProjection, accountList.size(), extras);
@@ -366,10 +369,16 @@
}
private void addAccountImpl(Account account, Uri accountsQueryUri, boolean notify) {
+ addAccountImpl(account, accountsQueryUri, mAccountCache.size(), notify);
+ }
+
+ private void addAccountImpl(Account account, Uri accountsQueryUri, int position,
+ boolean notify) {
synchronized (mAccountCache) {
if (account != null) {
LogUtils.v(LOG_TAG, "adding account %s", account);
- mAccountCache.put(account.uri, new AccountCacheEntry(account, accountsQueryUri));
+ mAccountCache.put(account.uri,
+ new AccountCacheEntry(account, accountsQueryUri, position));
}
}
// Explicitly calling this out of the synchronized block in case any of the observers get
@@ -447,16 +456,20 @@
final Set<String> accountsStringSet = preference.getStringSet(ACCOUNT_LIST_KEY, null);
if (accountsStringSet != null) {
+ int pos = 0;
for (String serializedAccount : accountsStringSet) {
try {
+ // TODO (pwestbro): we are creating duplicate AccountCacheEntry objects.
+ // One here, and one in addAccountImpl. We should stop doing that.
final AccountCacheEntry accountEntry =
- new AccountCacheEntry(serializedAccount);
+ new AccountCacheEntry(serializedAccount, pos);
if (accountEntry.mAccount.settings != null) {
- addAccountImpl(accountEntry.mAccount, accountEntry.mAccountsQueryUri,
+ addAccountImpl(accountEntry.mAccount, accountEntry.mAccountsQueryUri, pos,
false /* don't notify */);
} else {
LogUtils.e(LOG_TAG, "Dropping account that doesn't specify settings");
}
+ pos++;
} catch (Exception e) {
// Unable to create account object, skip to next
LogUtils.e(LOG_TAG, e,
@@ -469,10 +482,11 @@
}
private void cacheAccountList() {
- final Set<AccountCacheEntry> accountList;
+ final List<AccountCacheEntry> accountList = Lists.newArrayList();
synchronized (mAccountCache) {
- accountList = ImmutableSet.copyOf(mAccountCache.values());
+ accountList.addAll(mAccountCache.values());
}
+ Collections.sort(accountList);
final Set<String> serializedAccounts = Sets.newHashSet();
for (AccountCacheEntry accountEntry : accountList) {
@@ -521,11 +535,15 @@
accountList = ImmutableSet.copyOf(mAccountCache.values());
}
+ int lastPosition = 0;
// Build a set of the account uris that had been associated with that query
- final Set<Uri> previousQueryUriMap = Sets.newHashSet();
+ final Set<Uri> previousQueryUriSet = Sets.newHashSet();
for (AccountCacheEntry entry : accountList) {
if (accountsQueryUri.equals(entry.mAccountsQueryUri)) {
- previousQueryUriMap.add(entry.mAccount.uri);
+ previousQueryUriSet.add(entry.mAccount.uri);
+ }
+ if (entry.mPosition > lastPosition) {
+ lastPosition = entry.mPosition;
}
}
@@ -536,19 +554,23 @@
mAccountsFullyLoaded = extra.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
final Set<Uri> newQueryUriMap = Sets.newHashSet();
+
+ // We are relying on the fact that all accounts are added in the order specified in the
+ // cursor. Initially assume that we insert these items to at the end of the list
+ int pos = lastPosition;
while (data.moveToNext()) {
final Account account = new Account(data);
final Uri accountUri = account.uri;
newQueryUriMap.add(accountUri);
- addAccountImpl(account, accountsQueryUri, false /* don't notify */);
+ addAccountImpl(account, accountsQueryUri, pos++, false /* don't notify */);
}
// Remove all of the accounts that are in the new result set
- previousQueryUriMap.removeAll(newQueryUriMap);
+ previousQueryUriSet.removeAll(newQueryUriMap);
// For all of the entries that had been in the previous result set, and are not
// in the new result set, remove them from the cache
- if (previousQueryUriMap.size() > 0 && mAccountsFullyLoaded) {
- removeAccounts(previousQueryUriMap, false /* don't notify */);
+ if (previousQueryUriSet.size() > 0 && mAccountsFullyLoaded) {
+ removeAccounts(previousQueryUriSet, false /* don't notify */);
}
broadcastAccountChange();
}
@@ -557,9 +579,10 @@
* Object that allows the Account Cache provider to associate the account with the content
* provider uri that originated that account.
*/
- private static class AccountCacheEntry {
+ private static class AccountCacheEntry implements Comparable<AccountCacheEntry> {
final Account mAccount;
final Uri mAccountsQueryUri;
+ final int mPosition;
private static final String ACCOUNT_ENTRY_COMPONENT_SEPARATOR = "^**^";
private static final Pattern ACCOUNT_ENTRY_COMPONENT_SEPARATOR_PATTERN =
@@ -567,9 +590,10 @@
private static final int NUMBER_MEMBERS = 2;
- public AccountCacheEntry(Account account, Uri accountQueryUri) {
+ public AccountCacheEntry(Account account, Uri accountQueryUri, int position) {
mAccount = account;
mAccountsQueryUri = accountQueryUri;
+ mPosition = position;
}
/**
@@ -591,7 +615,8 @@
* ignoring the newly created object if the exception is thrown.
* @param serializedString
*/
- public AccountCacheEntry(String serializedString) throws IllegalArgumentException {
+ public AccountCacheEntry(String serializedString, int position)
+ throws IllegalArgumentException {
String[] cacheEntryMembers = TextUtils.split(serializedString,
ACCOUNT_ENTRY_COMPONENT_SEPARATOR_PATTERN);
if (cacheEntryMembers.length != NUMBER_MEMBERS) {
@@ -611,6 +636,12 @@
}
mAccountsQueryUri = !TextUtils.isEmpty(cacheEntryMembers[1]) ?
Uri.parse(cacheEntryMembers[1]) : null;
+ mPosition = position;
+ }
+
+ @Override
+ public int compareTo(AccountCacheEntry o) {
+ return o.mPosition - mPosition;
}
}
}
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 9211f80..daf701a 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -479,37 +479,56 @@
@Override
public void onFolderChanged(Folder folder) {
- if (!Objects.equal(mFolder, folder)) {
- commitDestructiveActions(false);
- }
changeFolder(folder, null);
}
/**
+ * Sets the folder state without changing view mode and without creating a list fragment, if
+ * possible.
+ * @param folder
+ */
+ private void setListContext(Folder folder, String query) {
+ updateFolder(folder);
+ if (query != null) {
+ mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, query);
+ } else {
+ mConvListContext = ConversationListContext.forFolder(mAccount, mFolder);
+ }
+ // Add the folder that we were viewing to the recent folders list.
+ // TODO: this may need to be fine tuned. If this is the signal that is indicating that
+ // the list is shown to the user, this could fire in one pane if the user goes directly
+ // to a conversation
+ updateRecentFolderList();
+ cancelRefreshTask();
+ }
+
+ /**
* Changes the folder to the value provided here. This causes the view mode to change.
* @param folder the folder to change to
* @param query if non-null, this represents the search string that the folder represents.
*/
private void changeFolder(Folder folder, String query) {
+ if (!Objects.equal(mFolder, folder)) {
+ commitDestructiveActions(false);
+ }
if (folder != null && !folder.equals(mFolder)
|| (mViewMode.getMode() != ViewMode.CONVERSATION_LIST)) {
- updateFolder(folder);
- if (query != null) {
- mConvListContext = ConversationListContext.forSearchQuery(mAccount, mFolder, query);
- } else {
- mConvListContext = ConversationListContext.forFolder(mAccount, mFolder);
- }
+ setListContext(folder, query);
showConversationList(mConvListContext);
-
- // Add the folder that we were viewing to the recent folders list.
- // TODO: this may need to be fine tuned. If this is the signal that is indicating that
- // the list is shown to the user, this could fire in one pane if the user goes directly
- // to a conversation
- updateRecentFolderList();
- cancelRefreshTask();
}
}
+ /**
+ * Update the conversation list to {@link #mConvListContext} without creating a new frament if
+ * possible.
+ */
+ protected abstract void updateConversationList();
+
+ private void setFirstFolder(Folder folder, String query) {
+ setListContext(folder, query);
+ updateConversationList();
+ }
+
@Override
public void onFolderSelected(Folder folder) {
onFolderChanged(folder);
@@ -660,11 +679,8 @@
}
if (savedState.containsKey(SAVED_FOLDER)) {
final Folder folder = (Folder) savedState.getParcelable(SAVED_FOLDER);
- if (savedState.containsKey(SAVED_QUERY)) {
- changeFolder(folder, savedState.getString(SAVED_QUERY));
- } else {
- onFolderChanged(folder);
- }
+ final String query = savedState.getString(SAVED_QUERY, null);
+ setFirstFolder(folder, query);
}
} else if (intent != null) {
handleIntent(intent);
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 746ce02..530eec7 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -153,6 +153,8 @@
@Override
public void onResume() {
+ Utils.dumpLayoutRequests("CLF.onResume()", getView());
+
super.onResume();
// Hacky workaround for http://b/6946182
Utils.fixSubTreeLayoutIfOrphaned(getView(), "ConversationListFragment");
@@ -345,11 +347,20 @@
if (conversationListCursor != null && conversationListCursor.isRefreshReady()) {
conversationListCursor.sync();
}
+ Utils.dumpLayoutRequests("CLF.onCreateView()", container);
return rootView;
}
@Override
+ public void onDestroy() {
+ Utils.dumpLayoutRequests("CLF.onDestroy()", getView());
+ super.onDestroy();
+ }
+
+ @Override
public void onDestroyView() {
+ Utils.dumpLayoutRequests("CLF.onDestroyView()", getView());
+
// If this fragment is being retained, onSaveInstance will not be called, so we need to
// manage saving the state ourselves. Unfortunately we don't have a signal indicates that
// this fragment instance will be reused, so we have to save the state in all cases.
@@ -437,6 +448,7 @@
@Override
public void onPause() {
+ Utils.dumpLayoutRequests("CLF.onPause()", getView());
super.onPause();
}
@@ -450,12 +462,14 @@
@Override
public void onStart() {
+ Utils.dumpLayoutRequests("CLF.onStart()", getView());
super.onStart();
mHandler.postDelayed(mUpdateTimestampsRunnable, TIMESTAMP_UPDATE_INTERVAL);
}
@Override
public void onStop() {
+ Utils.dumpLayoutRequests("CLF.onStop()", getView());
super.onStop();
mHandler.removeCallbacks(mUpdateTimestampsRunnable);
}
diff --git a/src/com/android/mail/ui/FolderListFragment.java b/src/com/android/mail/ui/FolderListFragment.java
index 71bcb98..4cec4f4 100644
--- a/src/com/android/mail/ui/FolderListFragment.java
+++ b/src/com/android/mail/ui/FolderListFragment.java
@@ -92,6 +92,8 @@
@Override
public void onResume() {
+ Utils.dumpLayoutRequests("FLF(" + this + ").onResume()", getView());
+
super.onResume();
// Hacky workaround for http://b/6946182
Utils.fixSubTreeLayoutIfOrphaned(getView(), "FolderListFragment");
@@ -165,12 +167,32 @@
if (mParentFolder != null) {
mSelectedFolder = mParentFolder;
}
+ Utils.dumpLayoutRequests("FLF(" + this + ").onCreateView()", rootView);
return rootView;
}
@Override
+ public void onStart() {
+ Utils.dumpLayoutRequests("FLF(" + this + ").onStart()", getView());
+ super.onStart();
+ }
+
+ @Override
+ public void onStop() {
+ Utils.dumpLayoutRequests("FLF(" + this + ").onStop()", getView());
+ super.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ Utils.dumpLayoutRequests("FLF(" + this + ").onPause()", getView());
+ super.onPause();
+ }
+
+ @Override
public void onDestroyView() {
+ Utils.dumpLayoutRequests("FLF(" + this + ").onDestoryView()", getView());
// Clear the adapter.
setListAdapter(null);
if (mFolderObserver != null) {
diff --git a/src/com/android/mail/ui/FolderListLayout.java b/src/com/android/mail/ui/FolderListLayout.java
new file mode 100644
index 0000000..0095df9
--- /dev/null
+++ b/src/com/android/mail/ui/FolderListLayout.java
@@ -0,0 +1,41 @@
+package com.android.mail.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
+
+/**
+ * temporary annonated FrameLayout to help find cases of b/6946182
+ */
+public class FolderListLayout extends FrameLayout {
+
+ public FolderListLayout(Context c) {
+ this(c, null);
+ }
+
+ public FolderListLayout(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "FolderListLayout(%s).onMeasure() called", this);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "FolderListLayout(%s).onLayout() called", this);
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ @Override
+ public void requestLayout() {
+ Utils.checkRequestLayout(this);
+ super.requestLayout();
+ }
+
+}
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index 75b3575..663aac5 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -406,6 +406,50 @@
}
}
+ /**
+ * Update the conversation list without creating another fragment, if possible
+ */
+ @Override
+ protected void updateConversationList(){
+ enableCabMode();
+ // TODO(viki): Check if the account has been changed since the previous
+ // time.
+ if (ConversationListContext.isSearchResult(mConvListContext)) {
+ 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;
+ Fragment listFragment = getConversationListFragment();
+ if (listFragment == null) {
+ listFragment = ConversationListFragment.newInstance(mConvListContext);
+ if (!inInbox(mAccount, mConvListContext)) {
+ // Maintain fragment transaction history so we can get back to the
+ // fragment used to launch this list.
+ mLastConversationListTransactionId = replaceFragment(listFragment,
+ transition, TAG_CONVERSATION_LIST);
+ } else {
+ // If going to the inbox, clear the folder list transaction history.
+ mInbox = mConvListContext.folder;
+ mLastInboxConversationListTransactionId = replaceFragment(listFragment,
+ transition, TAG_CONVERSATION_LIST);
+ mLastFolderListTransactionId = INVALID_ID;
+
+ // If we ever to to the inbox, we want to unset the transation id for any other
+ // non-inbox folder.
+ mLastConversationListTransactionId = INVALID_ID;
+ }
+ }
+ mConversationListVisible = true;
+ onConversationVisibilityChanged(false);
+ onConversationListVisibilityChanged(true);
+ mConversationListNeverShown = false;
+ }
+
private boolean isTransactionIdValid(int id) {
return id >= 0;
}
diff --git a/src/com/android/mail/ui/OnePaneRoot.java b/src/com/android/mail/ui/OnePaneRoot.java
new file mode 100644
index 0000000..8ffaede
--- /dev/null
+++ b/src/com/android/mail/ui/OnePaneRoot.java
@@ -0,0 +1,45 @@
+package com.android.mail.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
+
+/**
+ * TODO: Insert description here. (generated by ath)
+ */
+public class OnePaneRoot extends FrameLayout {
+
+ private static final String LOG_TAG = LogTag.getLogTag();
+
+ public OnePaneRoot(Context c) {
+ this(c, null);
+ }
+
+ public OnePaneRoot(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "OnePaneLayout(%s).onMeasure() called", this);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "OnePaneLayout(%s).onLayout() START", this);
+ super.onLayout(changed, left, top, right, bottom);
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "OnePaneLayout(%s).onLayout() FINISH", this);
+ }
+
+ @Override
+ public void requestLayout() {
+ Utils.checkRequestLayout(this);
+ super.requestLayout();
+ }
+
+}
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 745cd37..b9921e1 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -37,6 +38,7 @@
import com.android.mail.ui.SwipeHelper.Callback;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
import java.util.ArrayList;
import java.util.Collection;
@@ -78,6 +80,23 @@
mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
}
+ @Override
+ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG,
+ "START CLF-ListView.onFocusChanged layoutRequested=%s root.layoutRequested=%s",
+ isLayoutRequested(), getRootView().isLayoutRequested());
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, new Error(),
+ "FINISH CLF-ListView.onFocusChanged layoutRequested=%s root.layoutRequested=%s",
+ isLayoutRequested(), getRootView().isLayoutRequested());
+ }
+
+ @Override
+ public void requestLayout() {
+ Utils.checkRequestLayout(this);
+ super.requestLayout();
+ }
+
/**
* Enable swipe gestures.
*/
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 953c2a5..b12bad6 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -121,6 +121,21 @@
initializeConversationListFragment(true);
}
+ /**
+ * Update the conversation list without creating another fragment, if possible
+ */
+ @Override
+ protected void updateConversationList(){
+ exitCabMode();
+ FolderListFragment folderList = getFolderListFragment();
+ if (folderList == null && mViewMode.getMode() == ViewMode.CONVERSATION_LIST) {
+ // Create a folder list fragment if none exists.
+ renderFolderList();
+ folderList = getFolderListFragment();
+ }
+ initializeConversationListFragment(true);
+ }
+
@Override
public void showFolderList() {
// On two-pane layouts, showing the folder list takes you to the top level of the
diff --git a/src/com/android/mail/ui/TwoPaneLayout.java b/src/com/android/mail/ui/TwoPaneLayout.java
index bee4bb9..b703ef1 100644
--- a/src/com/android/mail/ui/TwoPaneLayout.java
+++ b/src/com/android/mail/ui/TwoPaneLayout.java
@@ -748,4 +748,22 @@
mOutstandingAnimator = animator;
animator.start();
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onMeasure()", this);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ LogUtils.w(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onLayout()", this);
+ super.onLayout(changed, l, t, r, b);
+ }
+
+ @Override
+ public void requestLayout() {
+ Utils.checkRequestLayout(this);
+ super.requestLayout();
+ }
}
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index c262987..366432e 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -87,6 +87,10 @@
public static final String EXTRA_COMPOSE_URI = "composeUri";
public static final String EXTRA_CONVERSATION = "conversationUri";
public static final String EXTRA_FOLDER = "folder";
+
+ /** Extra tag for debugging the blank fragment problem. */
+ public static final String VIEW_DEBUGGING_TAG = "MailBlankFragment";
+
/*
* Notifies that changes happened. Certain UI components, e.g., widgets, can
* register for this {@link Intent} and update accordingly. However, this
@@ -1049,7 +1053,11 @@
* Hacky method to allow invalidating views all the way up the hierarchy.
*/
public static void markDirtyTillRoot(String message, View v) {
- LogUtils.d(LOG_TAG, "%s: markingDirtyTillRoot", message);
+ // During development, we want to log extra debugging information, and disable the
+ // hacky workaround to help diagnose the underlying problem.
+ if (LogUtils.isDebugLoggingEnabled(VIEW_DEBUGGING_TAG)) return;
+
+ LogUtils.d(VIEW_DEBUGGING_TAG, "%s: markingDirtyTillRoot", message);
v.invalidate();
ViewParent parent = v.getParent();
while (parent != null) {
@@ -1058,4 +1066,56 @@
}
}
+ public static void checkRequestLayout(View v) {
+ boolean inLayout = false;
+ final View root = v.getRootView();
+
+ if (root == null) {
+ return;
+ }
+
+ final Error e = new Error();
+ for (StackTraceElement ste : e.getStackTrace()) {
+ if ("android.view.ViewGroup".equals(ste.getClassName())
+ && "layout".equals(ste.getMethodName())) {
+ inLayout = true;
+ break;
+ }
+ }
+ if (inLayout && !v.isLayoutRequested()) {
+ LogUtils.e(VIEW_DEBUGGING_TAG,
+ e, "WARNING: in requestLayout during layout pass, view=%s", v);
+ }
+ }
+
+ /**
+ * Logs extra information about the views to help find the problem with blank fragments.
+ * To turn on this debugging, enable the "MailBlankFragment" tag with
+ * adb shell setprop log.tag.MailBlankFragment VERBOSE
+ * @param message
+ * @param v
+ */
+ public static void dumpLayoutRequests(String message, View v) {
+ LogUtils.w(VIEW_DEBUGGING_TAG, "dumpLayoutRequests: %s", message);
+
+ while (v != null) {
+ LogUtils.w(VIEW_DEBUGGING_TAG,
+ "view item: %s mw/mh=%d/%d w/h=%d/%d layoutRequested=%s vis=%s id=0x%x",
+ v, v.getMeasuredWidth(), v.getMeasuredHeight(), v.getWidth(), v.getHeight(),
+ v.isLayoutRequested(), v.getVisibility(), v.getId());
+
+ ViewParent vp = v.getParent();
+ if (vp instanceof ViewGroup) {
+ v = (ViewGroup) vp;
+ } else {
+ if (vp != null) {
+ // this is the root. can't really get access to this guy
+ LogUtils.w(VIEW_DEBUGGING_TAG,
+ "view item: (ViewRootImpl) isLayoutRequested=%s\n",
+ vp.isLayoutRequested());
+ }
+ v = null;
+ }
+ }
+ }
}