diff --git a/res/anim/header_slide_in.xml b/res/anim/header_slide_in.xml
deleted file mode 100644
index 926e4dff1..000000000
--- a/res/anim/header_slide_in.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
diff --git a/res/anim/header_slide_out.xml b/res/anim/header_slide_out.xml
deleted file mode 100644
index 2bc33c831..000000000
--- a/res/anim/header_slide_out.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
diff --git a/res/layout/message_list_xl.xml b/res/layout/message_list_xl.xml
index aba76d37e..ceae29984 100644
--- a/res/layout/message_list_xl.xml
+++ b/res/layout/message_list_xl.xml
@@ -14,19 +14,13 @@
limitations under the License.
-->
-
-
-
-
+
+
+
diff --git a/src/com/android/email/Controller.java b/src/com/android/email/Controller.java
index 5bb365439..5ab0036ab 100644
--- a/src/com/android/email/Controller.java
+++ b/src/com/android/email/Controller.java
@@ -1040,7 +1040,7 @@ public class Controller {
public static abstract class Result {
private volatile boolean mRegistered;
- private void setRegistered(boolean registered) {
+ protected void setRegistered(boolean registered) {
mRegistered = registered;
}
diff --git a/src/com/android/email/ControllerResultUiThreadWrapper.java b/src/com/android/email/ControllerResultUiThreadWrapper.java
index f4f949204..c50e6572a 100644
--- a/src/com/android/email/ControllerResultUiThreadWrapper.java
+++ b/src/com/android/email/ControllerResultUiThreadWrapper.java
@@ -41,6 +41,12 @@ public class ControllerResultUiThreadWrapper extends Result {
return mWrappee;
}
+ @Override
+ protected void setRegistered(boolean registered) {
+ super.setRegistered(registered);
+ mWrappee.setRegistered(registered);
+ }
+
private void run(Runnable runnable) {
if (mHandler == null) {
runnable.run();
diff --git a/src/com/android/email/activity/BannerController.java b/src/com/android/email/activity/BannerController.java
new file mode 100644
index 000000000..0084091d6
--- /dev/null
+++ b/src/com/android/email/activity/BannerController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.email.activity;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.TextView;
+
+/**
+ * Class to hide/show a banner.
+ */
+public class BannerController {
+ private static final int ANIMATION_DURATION = 100;
+ private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator(1.5f);
+
+ private final TextView mBannerView;
+ private final int mBannerHeight;
+
+ private boolean mShown;
+
+ /** Hold last animator to cancel. */
+ private Animator mLastAnimator;
+
+ public BannerController(Context context, TextView bannerView, int bannerHeight) {
+ mBannerView = bannerView;
+ mBannerHeight = bannerHeight;
+
+ setBannerYAnim(-mBannerHeight); // hide by default.
+ }
+
+ /**
+ * @return the current y position of the banner.
+ */
+ private int getBannerY() {
+ return ((ViewGroup.MarginLayoutParams) mBannerView.getLayoutParams()).topMargin;
+ }
+
+ private static final String PROP_SET_BANNER_Y = "bannerYAnim";
+
+ /**
+ * Set the Y position of the banner. public, but should only be used by animators.
+ */
+ public void setBannerYAnim(int y) {
+ ((ViewGroup.MarginLayoutParams) mBannerView.getLayoutParams()).topMargin = y;
+ mBannerView.requestLayout();
+ }
+
+ /**
+ * Show a banner with a message.
+ *
+ * @return false if a banner is already shown, in which case the message won't be updated.
+ */
+ public boolean show(String message) {
+ if (mShown) {
+ return false; // If already shown, don't change the message, to avoid flicker.
+ }
+ mShown = true;
+ mBannerView.setText(message);
+ slideBanner(0);
+ return true;
+ }
+
+ /**
+ * Dismiss a banner.
+ */
+ public void dismiss() {
+ if (!mShown) {
+ return; // Always hidden, or hiding.
+ }
+ mShown = false;
+ slideBanner(-mBannerHeight); // Slide up to hide.
+ }
+
+ private void slideBanner(int toY) {
+ if (mLastAnimator != null) {
+ mLastAnimator.cancel();
+ }
+
+ final PropertyValuesHolder[] values = {
+ PropertyValuesHolder.ofInt(PROP_SET_BANNER_Y, getBannerY(), toY) };
+ final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
+ this, values).setDuration(ANIMATION_DURATION);
+ animator.setInterpolator(INTERPOLATOR);
+ mLastAnimator = animator;
+ animator.start();
+ }
+}
diff --git a/src/com/android/email/activity/MessageListXL.java b/src/com/android/email/activity/MessageListXL.java
index 87300dd11..0fbe89dba 100644
--- a/src/com/android/email/activity/MessageListXL.java
+++ b/src/com/android/email/activity/MessageListXL.java
@@ -17,6 +17,8 @@
package com.android.email.activity;
import com.android.email.Clock;
+import com.android.email.Controller;
+import com.android.email.ControllerResultUiThreadWrapper;
import com.android.email.Email;
import com.android.email.Preferences;
import com.android.email.R;
@@ -24,8 +26,10 @@ import com.android.email.RefreshManager;
import com.android.email.Utility;
import com.android.email.activity.setup.AccountSecurity;
import com.android.email.activity.setup.AccountSettingsXL;
+import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
+import com.android.email.provider.EmailContent.Message;
import android.app.ActionBar;
import android.app.Activity;
@@ -36,14 +40,12 @@ import android.content.Loader;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.AnimationUtils;
import android.widget.TextView;
import java.security.InvalidParameterException;
@@ -64,9 +66,11 @@ public class MessageListXL extends Activity implements
/* package */ static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds
private Context mContext;
+ private Controller mController;
private RefreshManager mRefreshManager;
private final RefreshListener mMailRefreshManagerListener
= new RefreshListener();
+ private Controller.Result mControllerResult;
private AccountSelectorAdapter mAccountsSelectorAdapter;
private final ActionBarNavigationCallback mActionBarNavigationCallback
@@ -82,12 +86,12 @@ public class MessageListXL extends Activity implements
private RefreshTask mRefreshTask;
+ private BannerController mBannerController;
private TextView mErrorMessageView;
- /** True when {@link #mErrorMessageView} is fully shown, when it should be clickable. */
- private boolean mErrorMessageFullyShown;
-
- private Animation mErrorMessageShowAnimation;
- private Animation mErrorMessageHideAnimation;
+ /**
+ * Id of the account that had a messaging exception most recently.
+ */
+ private long mLastErrorAccountId;
/**
* Launch and open account's inbox.
@@ -156,6 +160,7 @@ public class MessageListXL extends Activity implements
mFragmentManager.onActivityViewReady();
mContext = getApplicationContext();
+ mController = Controller.getInstance(this);
mRefreshManager = RefreshManager.getInstance(this);
mRefreshManager.registerListener(mMailRefreshManagerListener);
@@ -177,12 +182,16 @@ public class MessageListXL extends Activity implements
// so that it'll be easy to reuse for the phone activities.
mErrorMessageView = (TextView) findViewById(R.id.error_message);
mErrorMessageView.setOnClickListener(this);
-
- initAnimation();
+ mBannerController = new BannerController(this, mErrorMessageView,
+ getResources().getDimensionPixelSize(R.dimen.error_message_height));
// Halt the progress indicator (we'll display it later when needed)
setProgressBarIndeterminate(true);
setProgressBarIndeterminateVisibility(false);
+
+ mControllerResult = new ControllerResultUiThreadWrapper(new Handler(),
+ new ControllerResult());
+ mController.addResultCallback(mControllerResult);
}
private void initFromIntent() {
@@ -199,33 +208,6 @@ public class MessageListXL extends Activity implements
}
}
- private void initAnimation() {
- // Set up error message animations.
- mErrorMessageShowAnimation = AnimationUtils.loadAnimation(this, R.anim.header_slide_in);
- mErrorMessageShowAnimation.setAnimationListener(new AnimationListener() {
- @Override public void onAnimationRepeat(Animation animation) { }
- @Override public void onAnimationStart(Animation animation) { }
- @Override
- public void onAnimationEnd(Animation animation) {
- mErrorMessageFullyShown = true;
- }
- });
-
- mErrorMessageHideAnimation = AnimationUtils.loadAnimation(this, R.anim.header_slide_out);
- mErrorMessageHideAnimation.setAnimationListener(new AnimationListener() {
- @Override public void onAnimationRepeat(Animation animation) { }
- @Override
- public void onAnimationStart(Animation animation) {
- mErrorMessageFullyShown = false;
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mErrorMessageView.setVisibility(View.GONE);
- }
- });
- }
-
@Override
protected void onSaveInstanceState(Bundle outState) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
@@ -278,6 +260,7 @@ public class MessageListXL extends Activity implements
@Override
protected void onDestroy() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onDestroy");
+ mController.removeResultCallback(mControllerResult);
Utility.cancelTaskInterrupt(mRefreshTask);
mRefreshManager.unregisterListener(mMailRefreshManagerListener);
mFragmentManager.onDestroy();
@@ -510,7 +493,6 @@ public class MessageListXL extends Activity implements
@Override
public void onLoadMessageError(String errorMessage) {
- showErrorMessage(errorMessage);
}
@Override
@@ -569,37 +551,31 @@ public class MessageListXL extends Activity implements
updateProgressIcon();
}
- private void showErrorMessage(String message) {
- /* Note: All error messages come from the Controller.Result callback to the UI,
- * but this class doesn't use it directly. Instead it uses the following callbacks.
- *
- * RefreshManager.Listener.onMessagingError for
- * -updateMailboxListCallback
- * -updateMailboxCallback
- * -serviceCheckMailCallback
- * -sendMailCallback
- *
- * MessageViewFragmentBase.Callback.onLoadMessageError for
- * -loadMessageForViewCallback
- * -loadAttachmentCallback
- */
-
- if (mErrorMessageView.getVisibility() == View.VISIBLE) {
- // If an error is already shown, do nothing, not even changing the text, to avoid
- // flicker.
- return;
+ /**
+ * Call this when getting a connection error.
+ */
+ private void showErrorMessage(String message, long accountId) {
+ if (mBannerController.show(message)) {
+ mLastErrorAccountId = accountId;
}
- mErrorMessageView.setText(message);
- mErrorMessageView.setVisibility(View.VISIBLE);
- mErrorMessageView.startAnimation(mErrorMessageShowAnimation);
}
- private void dismissErrorMessage() {
- if (mErrorMessageFullyShown) {
- mErrorMessageView.startAnimation(mErrorMessageHideAnimation);
+ /**
+ * Call this when the connection for an account is considered working.
+ */
+ private void clearErrorMessage(long accountId) {
+ if (mLastErrorAccountId == accountId) {
+ dismissErrorMessage();
}
}
+ /**
+ * Force dismiss the error banner.
+ */
+ private void dismissErrorMessage() {
+ mBannerController.dismiss();
+ }
+
/**
* Load account list for the action bar.
*
@@ -672,7 +648,6 @@ public class MessageListXL extends Activity implements
implements RefreshManager.Listener {
@Override
public void onMessagingError(final long accountId, long mailboxId, final String message) {
- showErrorMessage(message);
updateProgressIcon();
}
@@ -842,4 +817,98 @@ public class MessageListXL extends Activity implements
return true;
}
}
+
+ /**
+ * A {@link Controller.Result} to detect connection status.
+ */
+ private class ControllerResult extends Controller.Result {
+ @Override
+ public void sendMailCallback(
+ MessagingException result, long accountId, long messageId, int progress) {
+ handleError(result, accountId, progress);
+ }
+
+ @Override
+ public void serviceCheckMailCallback(
+ MessagingException result, long accountId, long mailboxId, int progress, long tag) {
+ handleError(result, accountId, progress);
+ }
+
+ @Override
+ public void updateMailboxCallback(MessagingException result, long accountId, long mailboxId,
+ int progress, int numNewMessages) {
+ handleError(result, accountId, progress);
+ }
+
+ @Override
+ public void updateMailboxListCallback(
+ MessagingException result, long accountId, int progress) {
+ handleError(result, accountId, progress);
+ }
+
+ @Override
+ public void loadAttachmentCallback(
+ MessagingException result, long messageId, long attachmentId, int progress) {
+ new AccountFinder(result, messageId, progress).execute();
+ }
+
+ @Override
+ public void loadMessageForViewCallback(
+ MessagingException result, long messageId, int progress) {
+ new AccountFinder(result, messageId, progress).execute();
+ }
+
+ /**
+ * AsyncTask to determine the account id from a message id. Used for
+ * {@link #loadAttachmentCallback} and {@link #loadMessageForViewCallback}, which don't
+ * report the underlying account ID.
+ */
+ private class AccountFinder extends AsyncTask {
+ private final MessagingException mException;
+ private final long mMessageId;
+ private final int mProgress;
+
+ public AccountFinder(MessagingException exception, long messageId, int progress) {
+ mException = exception;
+ mMessageId = messageId;
+ mProgress = progress;
+ }
+
+ @Override
+ protected Long doInBackground(Void... params) {
+ if (mMessageId == -1) {
+ return null; // Message ID unknown
+ }
+ Message m = Message.restoreMessageWithId(MessageListXL.this, mMessageId);
+ return m != null ? m.mAccountKey : null;
+ }
+
+ @Override
+ protected void onPostExecute(Long accountId) {
+ if ((accountId == null) || isCancelled()) {
+ return;
+ }
+ handleError(mException, accountId, mProgress);
+ }
+ }
+
+ private void handleError(MessagingException result, long accountId, int progress) {
+ if (!isRegistered()) {
+ // This ControllerResult may be already unregistered, because of the asynctask.
+ return;
+ }
+ if (accountId == -1) {
+ return;
+ }
+ if (result == null) {
+ if (progress > 0) {
+ // Connection now working.
+ clearErrorMessage(accountId);
+ }
+ } else {
+ // Connection error.
+ showErrorMessage(result.getUiErrorMessage(MessageListXL.this), accountId);
+ }
+ }
+ }
}