summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJin Cao <jinyan@google.com>2014-08-12 17:14:59 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-08-12 16:54:46 +0000
commit9bb2bb06ab60c0b4643b39950b7c6b975c1887e2 (patch)
treec0662160de4aee6a7a214e4d0f3b6e1eb4fc8faf /src
parent824504cefedc128f6c26aba48af56082d0cb50b4 (diff)
parent0b69338a45faa422ccba8faf64c9816c55d33e4a (diff)
downloadandroid_packages_apps_UnifiedEmail-9bb2bb06ab60c0b4643b39950b7c6b975c1887e2.tar.gz
android_packages_apps_UnifiedEmail-9bb2bb06ab60c0b4643b39950b7c6b975c1887e2.tar.bz2
android_packages_apps_UnifiedEmail-9bb2bb06ab60c0b4643b39950b7c6b975c1887e2.zip
Merge "[KBNav] two-pane landscape navigation polish" into ub-gmail-ur14-dev
Diffstat (limited to 'src')
-rw-r--r--src/com/android/mail/browse/ConversationContainer.java22
-rw-r--r--src/com/android/mail/browse/ConversationOverlayItem.java8
-rw-r--r--src/com/android/mail/browse/ConversationPagerController.java5
-rw-r--r--src/com/android/mail/browse/ConversationViewAdapter.java106
-rw-r--r--src/com/android/mail/ui/AbstractActivityController.java2
-rw-r--r--src/com/android/mail/ui/ActivityController.java2
-rw-r--r--src/com/android/mail/ui/ControllableActivity.java2
-rw-r--r--src/com/android/mail/ui/ConversationListFragment.java10
-rw-r--r--src/com/android/mail/ui/ConversationViewFragment.java93
-rw-r--r--src/com/android/mail/ui/FolderSelectionActivity.java6
-rw-r--r--src/com/android/mail/ui/KeyboardNavigationController.java34
-rw-r--r--src/com/android/mail/ui/MailActivity.java5
-rw-r--r--src/com/android/mail/ui/OnePaneController.java13
-rw-r--r--src/com/android/mail/ui/TwoPaneController.java32
-rw-r--r--src/com/android/mail/ui/TwoPaneLayout.java23
15 files changed, 327 insertions, 36 deletions
diff --git a/src/com/android/mail/browse/ConversationContainer.java b/src/com/android/mail/browse/ConversationContainer.java
index f99bd3731..05faad17b 100644
--- a/src/com/android/mail/browse/ConversationContainer.java
+++ b/src/com/android/mail/browse/ConversationContainer.java
@@ -20,6 +20,7 @@ package com.android.mail.browse;
import android.content.Context;
import android.content.res.Configuration;
import android.database.DataSetObserver;
+import android.support.annotation.IdRes;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -994,6 +995,27 @@ public class ConversationContainer extends ViewGroup implements ScrollListener {
LogUtils.d(TAG, msg, params);
}
+ public void focusFirstMessageHeader() {
+ mOverlayAdapter.focusFirstMessageHeader();
+ }
+
+ public int getViewPosition(View v) {
+ return mOverlayAdapter.getViewPosition(v);
+ }
+
+ public View getNextOverlayView(int position, boolean isDown) {
+ return mOverlayAdapter.getNextOverlayView(position, isDown);
+ }
+
+ public boolean shouldInterceptLeftRightEvents(@IdRes int id, boolean isLeft, boolean isRight,
+ boolean twoPaneLand) {
+ return mOverlayAdapter.shouldInterceptLeftRightEvents(id, isLeft, isRight, twoPaneLand);
+ }
+
+ public boolean shouldNavigateAway(@IdRes int id, boolean isLeft, boolean twoPaneLand) {
+ return mOverlayAdapter.shouldNavigateAway(id, isLeft, twoPaneLand);
+ }
+
private class AdapterObserver extends DataSetObserver {
@Override
public void onChanged() {
diff --git a/src/com/android/mail/browse/ConversationOverlayItem.java b/src/com/android/mail/browse/ConversationOverlayItem.java
index 08f2cdfba..6850dd113 100644
--- a/src/com/android/mail/browse/ConversationOverlayItem.java
+++ b/src/com/android/mail/browse/ConversationOverlayItem.java
@@ -38,6 +38,9 @@ public abstract class ConversationOverlayItem {
private int mPosition;
+ // The view to focus when this overlay item should be focused.
+ protected View mRootView;
+
/**
* @see Adapter#getItemViewType(int)
*/
@@ -188,6 +191,11 @@ public abstract class ConversationOverlayItem {
// DO NOTHING
}
+ public View getFocusableView() {
+ // Focus the root view by default
+ return mRootView;
+ }
+
public void registerOnKeyListeners(View... views) {
final View.OnKeyListener listener = getOnKeyListener();
if (listener != null) {
diff --git a/src/com/android/mail/browse/ConversationPagerController.java b/src/com/android/mail/browse/ConversationPagerController.java
index ae576bda8..347b4ae95 100644
--- a/src/com/android/mail/browse/ConversationPagerController.java
+++ b/src/com/android/mail/browse/ConversationPagerController.java
@@ -157,6 +157,11 @@ public class ConversationPagerController {
cleanup();
}
+ // Explicitly set the focus to the conversation pager, specifically the conv overlay.
+ public void focusPager() {
+ mPager.requestFocus();
+ }
+
public boolean isInitialConversationLoading() {
return mInitialConversationLoading;
}
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index bda63c136..14da20367 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -20,12 +20,14 @@ package com.android.mail.browse;
import android.app.FragmentManager;
import android.app.LoaderManager;
import android.content.Context;
+import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.support.v4.text.BidiFormatter;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.BaseAdapter;
import com.android.emailcommon.mail.Address;
@@ -67,6 +69,7 @@ import java.util.Map;
public class ConversationViewAdapter extends BaseAdapter {
private static final String LOG_TAG = LogTag.getLogTag();
+ private static final String OVERLAY_ITEM_ROOT_TAG = "overlay_item_root";
private final Context mContext;
private final FormattedDateBuilder mDateBuilder;
@@ -130,13 +133,13 @@ public class ConversationViewAdapter extends BaseAdapter {
R.layout.conversation_view_header, parent, false);
v.setCallbacks(
mConversationCallbacks, mAccountController, mConversationUpdater);
- v.bind(this);
v.setSubject(mConversation.subject);
if (mAccountController.getAccount().supportsCapability(
UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV)) {
v.setFolders(mConversation);
}
v.setStarred(mConversation.starred);
+ v.setTag(OVERLAY_ITEM_ROOT_TAG);
// Register the onkey listener for all relevant views
registerOnKeyListeners(v, v.findViewById(R.id.subject_and_folder_view));
@@ -148,6 +151,7 @@ public class ConversationViewAdapter extends BaseAdapter {
public void bindView(View v, boolean measureOnly) {
ConversationViewHeader header = (ConversationViewHeader) v;
header.bind(this);
+ mRootView = v;
}
@Override
@@ -183,6 +187,7 @@ public class ConversationViewAdapter extends BaseAdapter {
inflater.inflate(R.layout.conversation_footer, parent, false);
v.setAccountController(mAccountController);
v.setConversationFooterCallbacks(mConversationFooterCallbacks);
+ v.setTag(OVERLAY_ITEM_ROOT_TAG);
// Register the onkey listener for all relevant views
registerOnKeyListeners(v, v.findViewById(R.id.reply_button),
@@ -194,11 +199,18 @@ public class ConversationViewAdapter extends BaseAdapter {
@Override
public void bindView(View v, boolean measureOnly) {
((ConversationFooterView) v).bind(this);
+ mRootView = v;
}
@Override
public void rebindView(View view) {
((ConversationFooterView) view).rebind(this);
+ mRootView = view;
+ }
+
+ @Override
+ public View getFocusableView() {
+ return mRootView.findViewById(R.id.reply_button);
}
@Override
@@ -268,6 +280,7 @@ public class ConversationViewAdapter extends BaseAdapter {
v.setCallbacks(mAdapter.mMessageCallbacks);
v.setContactInfoSource(mAdapter.mContactInfoSource);
v.setVeiledMatcher(mAdapter.mMatcher);
+ v.setTag(OVERLAY_ITEM_ROOT_TAG);
// Register the onkey listener for all relevant views
registerOnKeyListeners(v, v.findViewById(R.id.upper_header),
@@ -281,6 +294,12 @@ public class ConversationViewAdapter extends BaseAdapter {
public void bindView(View v, boolean measureOnly) {
final MessageHeaderView header = (MessageHeaderView) v;
header.bind(this, measureOnly);
+ mRootView = v;
+ }
+
+ @Override
+ public View getFocusableView() {
+ return mRootView.findViewById(R.id.upper_header);
}
@Override
@@ -370,6 +389,7 @@ public class ConversationViewAdapter extends BaseAdapter {
public void rebindView(View view) {
final MessageHeaderView header = (MessageHeaderView) view;
header.rebind(this);
+ mRootView = view;
}
}
@@ -398,6 +418,7 @@ public class ConversationViewAdapter extends BaseAdapter {
R.layout.conversation_message_footer, parent, false);
v.initialize(mAdapter.mLoaderManager, mAdapter.mFragmentManager,
mAdapter.mAccountController, mAdapter.mFooterCallbacks);
+ v.setTag(OVERLAY_ITEM_ROOT_TAG);
// Register the onkey listener for all relevant views
registerOnKeyListeners(v, v.findViewById(R.id.view_entire_message_prompt));
@@ -408,6 +429,7 @@ public class ConversationViewAdapter extends BaseAdapter {
public void bindView(View v, boolean measureOnly) {
final MessageFooterView attachmentsView = (MessageFooterView) v;
attachmentsView.bind(mHeaderItem, measureOnly);
+ mRootView = v;
}
@Override
@@ -470,6 +492,7 @@ public class ConversationViewAdapter extends BaseAdapter {
R.layout.super_collapsed_block, parent, false);
v.initialize(mSuperCollapsedListener);
v.setOnKeyListener(mOnKeyListener);
+ v.setTag(OVERLAY_ITEM_ROOT_TAG);
// Register the onkey listener for all relevant views
registerOnKeyListeners(v);
@@ -480,6 +503,7 @@ public class ConversationViewAdapter extends BaseAdapter {
public void bindView(View v, boolean measureOnly) {
final SuperCollapsedBlock scb = (SuperCollapsedBlock) v;
scb.bind(this);
+ mRootView = v;
}
@Override
@@ -712,6 +736,86 @@ public class ConversationViewAdapter extends BaseAdapter {
&& mItems.get(position).getType() == VIEW_TYPE_SUPER_COLLAPSED_BLOCK;
}
+ // This should be a safe call since all containers should have at least a conv header and a
+ // message header.
+ // TODO: what to do when the first header is off the screen and recycled?
+ public void focusFirstMessageHeader() {
+ if (mItems.size() > 1) {
+ final View v = mItems.get(1).getFocusableView();
+ if (v != null) {
+ v.requestFocus();
+ }
+ }
+ }
+
+ /**
+ * Try to find the position of the provided view (or it's view container) in the adapter.
+ */
+ public int getViewPosition(View v) {
+ // First find the root view of the overlay item
+ while (v.getTag() != OVERLAY_ITEM_ROOT_TAG) {
+ final ViewParent parent = v.getParent();
+ if (parent != null && parent instanceof View) {
+ v = (View) parent;
+ } else {
+ return -1;
+ }
+ }
+ // Find the position of the root view
+ for (int i = 0; i < mItems.size(); i++) {
+ if (mItems.get(i).mRootView == v) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find the next view that should grab focus with respect to the current position.
+ */
+ public View getNextOverlayView(int position, boolean isDown) {
+ if (isDown && position >= 0) {
+ while (++position < mItems.size()) {
+ final View v = mItems.get(position).getFocusableView();
+ if (v != null && v.isFocusable()) {
+ return v;
+ }
+ }
+ } else {
+ while (--position >= 0) {
+ final View v = mItems.get(position).getFocusableView();
+ if (v != null && v.isFocusable()) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean shouldInterceptLeftRightEvents(@IdRes int id, boolean isLeft, boolean isRight,
+ boolean twoPaneLand) {
+ return twoPaneLand && (id == R.id.conversation_header ||
+ id == R.id.subject_and_folder_view ||
+ id == R.id.upper_header ||
+ id == R.id.super_collapsed_block ||
+ id == R.id.message_footer ||
+ (id == R.id.overflow && isRight) ||
+ (id == R.id.reply_button && isLeft) ||
+ (id == R.id.forward_button && isRight));
+ }
+
+ // Indicates if the direction with the provided id should navigate away from the conversation
+ // view. Note that this is only applicable in two-pane landscape mode.
+ public boolean shouldNavigateAway(@IdRes int id, boolean isLeft, boolean twoPaneLand) {
+ return twoPaneLand && isLeft &&
+ (id == R.id.conversation_header ||
+ id == R.id.subject_and_folder_view ||
+ id == R.id.upper_header ||
+ id == R.id.super_collapsed_block ||
+ id == R.id.message_footer ||
+ id == R.id.reply_button);
+ }
+
public BidiFormatter getBidiFormatter() {
return mBidiFormatter;
}
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index f6727d38a..6d75402cd 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -2540,7 +2540,7 @@ public abstract class AbstractActivityController implements ActivityController,
}
@Override
- public final void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
+ public void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
final ConversationListFragment convListFragment = getConversationListFragment();
if (convListFragment != null && convListFragment.getAnimatedAdapter() != null) {
convListFragment.getAnimatedAdapter().onConversationSelected();
diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java
index 9af3e3987..fec109b4b 100644
--- a/src/com/android/mail/ui/ActivityController.java
+++ b/src/com/android/mail/ui/ActivityController.java
@@ -63,7 +63,7 @@ public interface ActivityController extends LayoutListener,
FolderChangeListener, ConversationSetObserver, ConversationListener, FolderSelector,
UndoListener, ConversationUpdater, ErrorListener, FolderController, AccountController,
ConversationPositionTracker.Callbacks, ConversationListFooterView.FooterViewClickListener,
- RecentFolderController, FragmentLauncher {
+ RecentFolderController, FragmentLauncher, KeyboardNavigationController {
// As far as possible, the methods here that correspond to Activity lifecycle have the same name
// as their counterpart in the Activity lifecycle.
diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java
index 0600be697..b9995b947 100644
--- a/src/com/android/mail/ui/ControllableActivity.java
+++ b/src/com/android/mail/ui/ControllableActivity.java
@@ -101,6 +101,8 @@ public interface ControllableActivity extends RestrictedActivity,
DrawerController getDrawerController();
+ KeyboardNavigationController getKeyboardNavigationController();
+
void startDragMode();
void stopDragMode();
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 9192de24d..177d38eb0 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -156,6 +156,9 @@ public final class ConversationListFragment extends Fragment implements
private boolean mInitialCursorLoading;
private @IdRes int mNextFocusLeftId;
+ // Tracks if a onKey event was initiated from the listview (received ACTION_DOWN before
+ // ACTION_UP). If not, the listview only receives ACTION_UP.
+ private boolean mKeyInitiatedFromList;
/** Duration, in milliseconds, of the CAB mode (peek icon) animation. */
private static long sSelectionModeAnimationDuration = -1;
@@ -627,7 +630,12 @@ public final class ConversationListFragment extends Fragment implements
// Don't need to handle ENTER because it's auto-handled as a "click".
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
- onListItemSelected(list.getSelectedView(), list.getSelectedItemPosition());
+ if (mKeyInitiatedFromList) {
+ onListItemSelected(list.getSelectedView(), list.getSelectedItemPosition());
+ }
+ mKeyInitiatedFromList = false;
+ } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ mKeyInitiatedFromList = true;
}
return true;
} else if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 803574ffc..a24b4d404 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -23,6 +23,7 @@ import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DataSetObserver;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -127,6 +128,18 @@ public class ConversationViewFragment extends AbstractConversationViewFragment i
*/
private final int LOAD_WAIT_UNTIL_VISIBLE = 2;
+ // Keyboard navigation
+ private KeyboardNavigationController mNavigationController;
+ // Since we manually control navigation for most of the conversation view due to problems
+ // with two-pane layout but still rely on the system for SOME navigation, we need to keep track
+ // of the view that had focus when KeyEvent.ACTION_DOWN was fired. This is because we only
+ // manually change focus on KeyEvent.ACTION_UP (to prevent holding down the DOWN button and
+ // lagging the app), however, the view in focus might have changed between ACTION_UP and
+ // ACTION_DOWN since the system might have handled the ACTION_DOWN and moved focus.
+ private View mOriginalKeyedView;
+ private int mMaxScreenHeight;
+ private int mTopOfVisibleScreen;
+
protected ConversationContainer mConversationContainer;
protected ConversationWebView mWebView;
@@ -277,6 +290,8 @@ public class ConversationViewFragment extends AbstractConversationViewFragment i
final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(context);
+ mNavigationController = mActivity.getKeyboardNavigationController();
+
mAdapter = new ConversationViewAdapter(mActivity, this,
getLoaderManager(), this, this, getContactInfoSource(), this, this,
getListController(), this, mAddressCache, dateBuilder, mBidiFormatter, this);
@@ -329,6 +344,12 @@ public class ConversationViewFragment extends AbstractConversationViewFragment i
new SetCookieTask(getContext(), mConversation.conversationBaseUri.toString(),
mAccount.accountCookieQueryUri).execute();
}
+
+ // Find the height of the screen for manually scrolling the webview via keyboard.
+ final Rect screen = new Rect();
+ mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(screen);
+ mMaxScreenHeight = screen.bottom;
+ mTopOfVisibleScreen = screen.top + mActivity.getSupportActionBar().getHeight();
}
@Override
@@ -1121,20 +1142,68 @@ public class ConversationViewFragment extends AbstractConversationViewFragment i
@Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
- // Only care about enter and esc
- View currFocus = mActivity.getWindow().getCurrentFocus();
- if (keyCode == KeyEvent.KEYCODE_BACK && currFocus != null &&
- currFocus.getId() != R.id.conversation_topmost_overlay) {
- if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
- mTopmostOverlay.requestFocus();
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ mOriginalKeyedView = view;
+ }
+
+ if (mOriginalKeyedView != null) {
+ final int id = mOriginalKeyedView.getId();
+ final boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
+ final boolean isLeft = keyCode == KeyEvent.KEYCODE_DPAD_LEFT;
+ final boolean isRight = keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
+ final boolean isUp = keyCode == KeyEvent.KEYCODE_DPAD_UP;
+ final boolean isDown = keyCode == KeyEvent.KEYCODE_DPAD_DOWN;
+
+ // First we run the event by the controller
+ // We manually check if the view+direction combination should shift focus away from the
+ // conversation view to the thread list in two-pane landscape mode.
+ final boolean isTwoPaneLand = mNavigationController.isTwoPaneLandscape();
+ final boolean navigateAway = mConversationContainer.shouldNavigateAway(id, isLeft,
+ isTwoPaneLand);
+ if (mNavigationController.onInterceptKeyFromCV(keyCode, keyEvent, navigateAway)) {
+ return true;
}
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_ENTER && (currFocus == null ||
- currFocus.getId() == R.id.conversation_topmost_overlay)) {
- if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
- mConversationContainer.findViewById(R.id.upper_header).requestFocus();
+
+ // If controller didn't handle the event, check directional interception.
+ if ((isLeft || isRight) && mConversationContainer.shouldInterceptLeftRightEvents(
+ id, isLeft, isRight, isTwoPaneLand)) {
+ return true;
+ } else if (isUp || isDown) {
+ // We manually handle up/down navigation through the overlay items because the
+ // system's default isn't optimal for two-pane landscape since it's not a real list.
+ final int position = mConversationContainer.getViewPosition(mOriginalKeyedView);
+ final View next = mConversationContainer.getNextOverlayView(position, isDown);
+ if (next != null) {
+ if (isActionUp) {
+ next.requestFocus();
+
+ // Make sure that v is in view
+ final int[] coords = new int[2];
+ next.getLocationOnScreen(coords);
+ final int bottom = coords[1] + next.getHeight();
+ if (bottom > mMaxScreenHeight) {
+ mWebView.scrollBy(0, bottom - mMaxScreenHeight);
+ } else if (coords[1] < mTopOfVisibleScreen) {
+ mWebView.scrollBy(0, coords[1] - mTopOfVisibleScreen);
+ }
+ }
+ return true;
+ }
+ }
+
+ // Finally we handle the special keys
+ if (keyCode == KeyEvent.KEYCODE_BACK && id != R.id.conversation_topmost_overlay) {
+ if (isActionUp) {
+ mTopmostOverlay.requestFocus();
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_ENTER &&
+ id == R.id.conversation_topmost_overlay) {
+ if (isActionUp) {
+ mConversationContainer.focusFirstMessageHeader();
+ }
+ return true;
}
- return true;
}
return false;
}
diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java
index 93594b5ed..96b06496f 100644
--- a/src/com/android/mail/ui/FolderSelectionActivity.java
+++ b/src/com/android/mail/ui/FolderSelectionActivity.java
@@ -437,6 +437,12 @@ public class FolderSelectionActivity extends ActionBarActivity implements OnClic
}
@Override
+ public KeyboardNavigationController getKeyboardNavigationController() {
+ // Unsupported
+ return null;
+ }
+
+ @Override
public boolean isAccessibilityEnabled() {
// Unsupported
return true;
diff --git a/src/com/android/mail/ui/KeyboardNavigationController.java b/src/com/android/mail/ui/KeyboardNavigationController.java
new file mode 100644
index 000000000..768133a47
--- /dev/null
+++ b/src/com/android/mail/ui/KeyboardNavigationController.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.ui;
+
+import android.view.KeyEvent;
+
+/**
+ * Callbacks for cross-fragment keyboard navigation functionality.
+ */
+public interface KeyboardNavigationController {
+ /**
+ * Intercept a key press from ConversationViewFragment.
+ * @param navigateAway custom param indicating if the controller should navigate away from
+ * the conversation view.
+ * @return true if the event was handled by the controller, false otherwise.
+ */
+ public boolean onInterceptKeyFromCV(int keyCode, KeyEvent keyEvent, boolean navigateAway);
+ public boolean isTwoPaneLandscape();
+}
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index 0c102241b..78507dad1 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -418,6 +418,11 @@ public class MailActivity extends AbstractMailActivity implements ControllableAc
}
@Override
+ public KeyboardNavigationController getKeyboardNavigationController() {
+ return mController;
+ }
+
+ @Override
public void onFooterViewErrorActionClick(Folder folder, int errorStatus) {
mController.onFooterViewErrorActionClick(folder, errorStatus);
}
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index 0d83572c6..0d0e58dec 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -23,9 +23,11 @@ import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity;
+import android.view.KeyEvent;
import android.view.View;
import android.widget.ListView;
@@ -500,4 +502,15 @@ public final class OnePaneController extends AbstractActivityController {
replaceFragment(fragment, FragmentTransaction.TRANSIT_FRAGMENT_OPEN,
TAG_CUSTOM_FRAGMENT, R.id.content_pane);
}
+
+ @Override
+ public boolean onInterceptKeyFromCV(int keyCode, KeyEvent keyEvent, boolean navigateAway) {
+ // Not applicable
+ return false;
+ }
+
+ @Override
+ public boolean isTwoPaneLandscape() {
+ return false;
+ }
}
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 7e03f1030..3be51ff22 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -21,11 +21,13 @@ import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.app.ListFragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.v7.app.ActionBar;
+import android.view.KeyEvent;
import android.view.View;
import android.widget.ListView;
@@ -43,7 +45,7 @@ import com.android.mail.utils.Utils;
* abounds.
*/
public final class TwoPaneController extends AbstractActivityController implements
- ConversationViewFrame.DownEventListener, TwoPaneLayout.TwoPaneLayoutListener {
+ ConversationViewFrame.DownEventListener {
private static final String SAVED_MISCELLANEOUS_VIEW = "saved-miscellaneous-view";
private static final String SAVED_MISCELLANEOUS_VIEW_TRANSACTION_ID =
@@ -157,7 +159,6 @@ public final class TwoPaneController extends AbstractActivityController implemen
return false;
}
mLayout.setController(this, Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction()));
- mLayout.setLayoutListener(this);
mActivity.getWindow().setBackgroundDrawable(null);
mIsTabletLandscape = !mActivity.getResources().getBoolean(R.bool.list_collapsible);
@@ -398,6 +399,13 @@ public final class TwoPaneController extends AbstractActivityController implemen
}
@Override
+ public final void onConversationSelected(Conversation conversation, boolean inLoaderCallbacks) {
+ super.onConversationSelected(conversation, inLoaderCallbacks);
+ // Shift the focus to the conversation in landscape mode
+ mPagerController.focusPager();
+ }
+
+ @Override
public void onConversationFocused(Conversation conversation) {
if (mIsTabletLandscape) {
showConversation(conversation, false /* markAsRead */);
@@ -606,10 +614,22 @@ public final class TwoPaneController extends AbstractActivityController implemen
}
@Override
- public void onListViewLayout(boolean isOnScreen) {
- ConversationListFragment clf = getConversationListFragment();
- if (clf != null && clf.getListView() != null) {
- clf.getListView().setFocusable(isOnScreen);
+ public boolean onInterceptKeyFromCV(int keyCode, KeyEvent keyEvent, boolean navigateAway) {
+ // Override left/right key presses in landscape mode.
+ if (navigateAway) {
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ ConversationListFragment clf = getConversationListFragment();
+ if (clf != null) {
+ clf.getListView().requestFocus();
+ }
+ }
+ return true;
}
+ return false;
+ }
+
+ @Override
+ public boolean isTwoPaneLandscape() {
+ return mIsTabletLandscape;
}
}
diff --git a/src/com/android/mail/ui/TwoPaneLayout.java b/src/com/android/mail/ui/TwoPaneLayout.java
index 5f29664a7..6dfa711ef 100644
--- a/src/com/android/mail/ui/TwoPaneLayout.java
+++ b/src/com/android/mail/ui/TwoPaneLayout.java
@@ -96,8 +96,6 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
private View mFoldersView;
private View mListView;
- private TwoPaneLayoutListener mLayoutListener;
-
public static final int MISCELLANEOUS_VIEW_ID = R.id.miscellaneous_pane;
private final Runnable mTransitionCompleteRunnable = new Runnable() {
@@ -173,10 +171,6 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
super.onLayout(changed, l, t, r, b);
}
- public void setLayoutListener(TwoPaneLayoutListener listener) {
- mLayoutListener = listener;
- }
-
/**
* Sizes up the three sliding panes. This method will ensure that the LayoutParams of the panes
* have the correct widths set for the current overall size and view mode.
@@ -206,6 +200,7 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
final int foldersW = isDrawerOpen() ? mDrawerWidthOpen : mDrawerWidthMini;
final int listW = getPaneWidth(mListView);
+ boolean cvOnScreen = true;
if (!mListCollapsible) {
if (isRtl) {
foldersX = width - mDrawerWidthOpen;
@@ -231,6 +226,7 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
}
} else {
// TL mode
+ cvOnScreen = false;
if (isRtl) {
foldersX = width - mDrawerWidthOpen;
listX = width - foldersW - listW;
@@ -244,10 +240,13 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
}
animatePanes(foldersX, listX, convX);
- // For keyboard navigation, let's disable focus on the list if it's not visible.
- if (mLayoutListener != null) {
- mLayoutListener.onListViewLayout(listX >= 0);
- }
+
+ // For views that are not on the screen, let's set their visibility for accessibility.
+ mFoldersView.setVisibility(foldersX >= 0 ? VISIBLE : INVISIBLE);
+ mListView.setVisibility(listX >= 0 ? VISIBLE : INVISIBLE);
+ mConversationView.setVisibility(cvOnScreen ? VISIBLE : INVISIBLE);
+ mMiscellaneousView.setVisibility(cvOnScreen ? VISIBLE : INVISIBLE);
+
mPositionedMode = mCurrentMode;
}
@@ -465,8 +464,4 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
public boolean shouldShowPreviewPanel() {
return !mListCollapsible;
}
-
- public interface TwoPaneLayoutListener {
- public void onListViewLayout(boolean isOnScreen);
- }
}