Update error banner
1. Error banner now pushes down the entire screen, rather than covers it. 2. Switch to the new ObjectAnimator for the animation to achieve #1. (Traditional Animation doesn't do this) 3. Dismiss the banner when getting any callback with MessagingException == null and progress > 0, only when the account is the one that caused the last error. 4. MessageListXL now registers its own ControllerResult to detect connection errors, and more importantly, when they're cleared. Bug 3240874 Bug 3240406 Change-Id: I07f8e2f589bb1d312859824f9ec398879003ba16
This commit is contained in:
parent
4d02297f4a
commit
45e04b009d
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<translate
|
||||
android:fromYDelta="-100%"
|
||||
android:toYDelta="0"
|
||||
android:duration="300" />
|
||||
</set>
|
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<translate
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="-100%"
|
||||
android:duration="100" />
|
||||
</set>
|
@ -14,19 +14,13 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<include
|
||||
android:id="@+id/three_pane"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
layout="@layout/three_pane"
|
||||
/>
|
||||
|
||||
<!-- Error message goes over the normal view -->
|
||||
<!-- STOPSHIP not pixel perfect -->
|
||||
<TextView
|
||||
@ -41,6 +35,12 @@
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:background="#ffff66"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/three_pane"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
layout="@layout/three_pane"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,12 @@ public class ControllerResultUiThreadWrapper<T extends Result> 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();
|
||||
|
106
src/com/android/email/activity/BannerController.java
Normal file
106
src/com/android/email/activity/BannerController.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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<ControllerResult>(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,35 +551,29 @@ 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
|
||||
/**
|
||||
* Call this when getting a connection error.
|
||||
*/
|
||||
|
||||
if (mErrorMessageView.getVisibility() == View.VISIBLE) {
|
||||
// If an error is already shown, do nothing, not even changing the text, to avoid
|
||||
// flicker.
|
||||
return;
|
||||
private void showErrorMessage(String message, long accountId) {
|
||||
if (mBannerController.show(message)) {
|
||||
mLastErrorAccountId = accountId;
|
||||
}
|
||||
mErrorMessageView.setText(message);
|
||||
mErrorMessageView.setVisibility(View.VISIBLE);
|
||||
mErrorMessageView.startAnimation(mErrorMessageShowAnimation);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (mErrorMessageFullyShown) {
|
||||
mErrorMessageView.startAnimation(mErrorMessageHideAnimation);
|
||||
}
|
||||
mBannerController.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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<Void, Void, Long> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user