diff options
Diffstat (limited to 'libraries/Android-AppMsg/library/src')
| -rw-r--r-- | libraries/Android-AppMsg/library/src/com/devspark/appmsg/AppMsg.java | 587 | ||||
| -rw-r--r-- | libraries/Android-AppMsg/library/src/com/devspark/appmsg/MsgManager.java | 340 | 
2 files changed, 927 insertions, 0 deletions
| diff --git a/libraries/Android-AppMsg/library/src/com/devspark/appmsg/AppMsg.java b/libraries/Android-AppMsg/library/src/com/devspark/appmsg/AppMsg.java new file mode 100644 index 000000000..e86273762 --- /dev/null +++ b/libraries/Android-AppMsg/library/src/com/devspark/appmsg/AppMsg.java @@ -0,0 +1,587 @@ +/* + * Copyright 2012 Evgeny Shishkin + * + * 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.devspark.appmsg; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.TextView; + +/** + * In-layout notifications. Based on {@link android.widget.Toast} notifications + * and article by Cyril Mottier (http://android.cyrilmottier.com/?p=773). + * + * @author e.shishkin + */ +public class AppMsg { + +    /** +     * Show the view or text notification for a short period of time. This time +     * could be user-definable. This is the default. +     * +     * @see #setDuration +     */ +    public static final int LENGTH_SHORT = 3000; + +    /** +     * Show the view or text notification for a long period of time. This time +     * could be user-definable. +     * +     * @see #setDuration +     */ +    public static final int LENGTH_LONG = 5000; + +    /** +     * <p>Show the view or text notification for an undefined amount of time +     * -Usually until an invocation of {@link #cancel()}, {@link #cancelAll(android.app.Activity)}, +     * {@link #cancelAll()} or {@link android.app.Activity#onDestroy()}-, +     * stacking on top of any other {@link com.devspark.appmsg.AppMsg} with this duration.</p> +     * +     * <p><b>Note</b>: You are responsible +     * for calling {@link #cancel()} on such {@link com.devspark.appmsg.AppMsg}.</p> +     * +     * @see #setDuration +     */ +    public static final int LENGTH_STICKY = -1; + +    /** +     * Lowest priority, messages with this priority will be showed after all messages with priority +     * {@link #PRIORITY_HIGH} and {@link #PRIORITY_NORMAL} have been shown. +     * +     * @see #setPriority(int) +     */ +    public static final int PRIORITY_LOW = Integer.MIN_VALUE; +    /** +     * Normal priority, messages with this priority will be showed after all messages with priority +     * {@link #PRIORITY_HIGH} but before {@link #PRIORITY_LOW} have been shown. +     * +     * @see #setPriority(int) +     */ +    public static final int PRIORITY_NORMAL = 0; +    /** +     * Highest priority, messages with this priority will be showed before any other message. +     * +     * @see #setPriority(int) +     */ +    public static final int PRIORITY_HIGH = Integer.MAX_VALUE; + +    /** +     * Show the text notification for a long period of time with a negative style. +     */ +    public static final Style STYLE_ALERT = new Style(LENGTH_LONG, R.color.alert); + +    /** +     * Show the text notification for a short period of time with a positive style. +     */ +    public static final Style STYLE_CONFIRM = new Style(LENGTH_SHORT, R.color.confirm); + +    /** +     * Show the text notification for a short period of time with a neutral style. +     */ +    public static final Style STYLE_INFO = new Style(LENGTH_SHORT, R.color.info); + +    private final Activity mActivity; +    private int mDuration = LENGTH_SHORT; +    private View mView; +    private ViewGroup mParent; +    private LayoutParams mLayoutParams; +    private boolean mFloating; +    Animation mInAnimation, mOutAnimation; +    int mPriority = PRIORITY_NORMAL; + +    /** +     * Construct an empty AppMsg object. You must call {@link #setView} before +     * you can call {@link #show}. +     * +     * @param activity {@link android.app.Activity} to use. +     */ +    public AppMsg(Activity activity) { +        mActivity =  activity; +    } + +    /** +     * Make a {@link AppMsg} that just contains a text view. +     * +     * @param context The context to use. Usually your +     *                {@link android.app.Activity} object. +     * @param text    The text to show. Can be formatted text. +     * @param style   The style with a background and a duration. +     */ +    public static AppMsg makeText(Activity context, CharSequence text, Style style) { +        return makeText(context, text, style, R.layout.app_msg); +    } +     +    /** +     * @author mengguoqiang 扩展支持设置字体大小 +     * Make a {@link AppMsg} that just contains a text view. +     * +     * @param context The context to use. Usually your +     *                {@link android.app.Activity} object. +     * @param text    The text to show. Can be formatted text. +     * @param style   The style with a background and a duration. +     */ +    public static AppMsg makeText(Activity context, CharSequence text, Style style, float textSize) { +        return makeText(context, text, style, R.layout.app_msg, textSize); +    } + +    /** +     * Make a {@link AppMsg} with a custom layout. The layout must have a {@link TextView} com id {@link android.R.id.message} +     * +     * @param context The context to use. Usually your +     *                {@link android.app.Activity} object. +     * @param text    The text to show. Can be formatted text. +     * @param style   The style with a background and a duration. +     */ +    public static AppMsg makeText(Activity context, CharSequence text, Style style, int layoutId) { +        LayoutInflater inflate = (LayoutInflater) +                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); +        View v = inflate.inflate(layoutId, null); + +        return makeText(context, text, style, v, true); +    } +     +    /** +     * @author mengguoqiang 扩展支持字体大小 +     * Make a {@link AppMsg} with a custom layout. The layout must have a {@link TextView} com id {@link android.R.id.message} +     * +     * @param context The context to use. Usually your +     *                {@link android.app.Activity} object. +     * @param text    The text to show. Can be formatted text. +     * @param style   The style with a background and a duration. +     */ +    public static AppMsg makeText(Activity context, CharSequence text, Style style, int layoutId, float textSize) { +        LayoutInflater inflate = (LayoutInflater) +                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); +        View v = inflate.inflate(layoutId, null); + +        return makeText(context, text, style, v, true, textSize); +    } + +    /** +     * Make a non-floating {@link AppMsg} with a custom view presented inside the layout. +     * It can be used to create non-floating notifications if floating is false. +     * +     * @param context  The context to use. Usually your +     *                 {@link android.app.Activity} object. +     * @param customView +     *                 View to be used. +     * @param text     The text to show. Can be formatted text. +     * @param style    The style with a background and a duration. +     */ +    public static AppMsg makeText(Activity context, CharSequence text, Style style, View customView) { +       return makeText(context, text, style, customView, false); +    } + +    /** +     * Make a {@link AppMsg} with a custom view. It can be used to create non-floating notifications if floating is false. +     * +     * @param context  The context to use. Usually your +     *                 {@link android.app.Activity} object. +     * @param view +     *                 View to be used. +     * @param text     The text to show. Can be formatted text. +     * @param style    The style with a background and a duration. +     * @param floating true if it'll float. +     */ +    private static AppMsg makeText(Activity context, CharSequence text, Style style, View view, boolean floating) { +        return makeText(context, text, style, view, floating, 0); +    } +     +    /** +     *  +     * @author mengguoqiang 扩展支持设置字体大小 +     * Make a {@link AppMsg} with a custom view. It can be used to create non-floating notifications if floating is false. +     * +     * @param context  The context to use. Usually your +     *                 {@link android.app.Activity} object. +     * @param view +     *                 View to be used. +     * @param text     The text to show. Can be formatted text. +     * @param style    The style with a background and a duration. +     * @param floating true if it'll float. +     */ +    private static AppMsg makeText(Activity context, CharSequence text, Style style, View view, boolean floating, float textSize) { +        AppMsg result = new AppMsg(context); + +        view.setBackgroundResource(style.background); + +        TextView tv = (TextView) view.findViewById(android.R.id.message); +        if(textSize > 0) tv.setTextSize(textSize); +        tv.setText(text); + +        result.mView = view; +        result.mDuration = style.duration; +        result.mFloating = floating; + +        return result; +    } + +    /** +     * Make a {@link AppMsg} with a custom view. It can be used to create non-floating notifications if floating is false. +     * +     * @param context  The context to use. Usually your +     *                 {@link android.app.Activity} object. +     * @param resId    The resource id of the string resource to use. Can be +     *                 formatted text. +     * @param style    The style with a background and a duration. +     * @param floating true if it'll float. +     */ +    public static AppMsg makeText(Activity context, int resId, Style style, View customView, boolean floating) { +        return makeText(context, context.getResources().getText(resId), style, customView, floating); +    } + +    /** +     * Make a {@link AppMsg} that just contains a text view with the text from a +     * resource. +     * +     * @param context The context to use. Usually your +     *                {@link android.app.Activity} object. +     * @param resId   The resource id of the string resource to use. Can be +     *                formatted text. +     * @param style   The style with a background and a duration. +     * @throws Resources.NotFoundException if the resource can't be found. +     */ +    public static AppMsg makeText(Activity context, int resId, Style style) +            throws Resources.NotFoundException { +        return makeText(context, context.getResources().getText(resId), style); +    } + +    /** +     * Make a {@link AppMsg} with a custom layout using the text from a +     * resource. The layout must have a {@link TextView} com id {@link android.R.id.message} +     * +     * @param context The context to use. Usually your +     *                {@link android.app.Activity} object. +     * @param resId   The resource id of the string resource to use. Can be +     *                formatted text. +     * @param style   The style with a background and a duration. +     * @throws Resources.NotFoundException if the resource can't be found. +     */ +    public static AppMsg makeText(Activity context, int resId, Style style, int layoutId) +            throws Resources.NotFoundException { +        return makeText(context, context.getResources().getText(resId), style, layoutId); +    } + +    /** +     * Show the view for the specified duration. +     */ +    public void show() { +        MsgManager manager = MsgManager.obtain(mActivity); +        manager.add(this); +    } + +    /** +     * @return <code>true</code> if the {@link AppMsg} is being displayed, else <code>false</code>. +     */ +    public boolean isShowing() { +        if (mFloating) { +            return mView != null && mView.getParent() != null; +        } else { +            return mView.getVisibility() == View.VISIBLE; +        } +    } + +    /** +     * Close the view if it's showing, or don't show it if it isn't showing yet. +     * You do not normally have to call this.  Normally view will disappear on its own +     * after the appropriate duration. +     */ +    public void cancel() { +        MsgManager.obtain(mActivity).clearMsg(this); + +    } + +    /** +     * Cancels all queued {@link AppMsg}s, in all Activities. If there is a {@link AppMsg} +     * displayed currently, it will be the last one displayed. +     */ +    public static void cancelAll() { +        MsgManager.clearAll(); +    } + +    /** +     * Cancels all queued {@link AppMsg}s, in given {@link android.app.Activity}. +     * If there is a {@link AppMsg} displayed currently, it will be the last one displayed. +     * @param activity +     */ +    public static void cancelAll(Activity activity) { +        MsgManager.release(activity); +    } + +    /** +     * Return the activity. +     */ +    public Activity getActivity() { +        return mActivity; +    } + +    /** +     * Set the view to show. +     * +     * @see #getView +     */ +    public void setView(View view) { +        mView = view; +    } + +    /** +     * Return the view. +     * +     * @see #setView +     */ +    public View getView() { +        return mView; +    } + +    /** +     * Set how long to show the view for. +     * +     * @see #LENGTH_SHORT +     * @see #LENGTH_LONG +     */ +    public void setDuration(int duration) { +        mDuration = duration; +    } + +    /** +     * Return the duration. +     * +     * @see #setDuration +     */ +    public int getDuration() { +        return mDuration; +    } + +    /** +     * Update the text in a AppMsg that was previously created using one of the makeText() methods. +     * +     * @param resId The new text for the AppMsg. +     */ +    public void setText(int resId) { +        setText(mActivity.getText(resId)); +    } + +    /** +     * Update the text in a AppMsg that was previously created using one of the makeText() methods. +     * +     * @param s The new text for the AppMsg. +     */ +    public void setText(CharSequence s) { +        if (mView == null) { +            throw new RuntimeException("This AppMsg was not created with AppMsg.makeText()"); +        } +        TextView tv = (TextView) mView.findViewById(android.R.id.message); +        if (tv == null) { +            throw new RuntimeException("This AppMsg was not created with AppMsg.makeText()"); +        } +        tv.setText(s); +    } + +    /** +     * Gets the crouton's layout parameters, constructing a default if necessary. +     * +     * @return the layout parameters +     */ +    public LayoutParams getLayoutParams() { +        if (mLayoutParams == null) { +            mLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); +        } +        return mLayoutParams; +    } + +    /** +     * Sets the layout parameters which will be used to display the crouton. +     * +     * @param layoutParams The layout parameters to use. +     * @return <code>this</code>, for chaining. +     */ +    public AppMsg setLayoutParams(LayoutParams layoutParams) { +        mLayoutParams = layoutParams; +        return this; +    } + +    /** +     * Constructs and sets the layout parameters to have some gravity. +     * +     * @param gravity the gravity of the Crouton +     * @return <code>this</code>, for chaining. +     * @see android.view.Gravity +     */ +    public AppMsg setLayoutGravity(int gravity) { +        mLayoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, gravity); +        return this; +    } + +    /** +     * Return the value of floating. +     * +     * @see #setFloating(boolean) +     */ +    public boolean isFloating() { +        return mFloating; +    } + +    /** +     * Sets the value of floating. +     * +     * @param mFloating +     */ +    public void setFloating(boolean mFloating) { +        this.mFloating = mFloating; +    } + +    /** +     * Sets the Animations to be used when displaying/removing the Crouton. +     * @param inAnimation the Animation resource ID to be used when displaying. +     * @param outAnimation the Animation resource ID to be used when removing. +     */ +    public AppMsg setAnimation(int inAnimation, int outAnimation) { +        return setAnimation(AnimationUtils.loadAnimation(mActivity, inAnimation), +                AnimationUtils.loadAnimation(mActivity, outAnimation)); +    } + +    /** +     * Sets the Animations to be used when displaying/removing the Crouton. +     * @param inAnimation the Animation to be used when displaying. +     * @param outAnimation the Animation to be used when removing. +     */ +    public AppMsg setAnimation(Animation inAnimation, Animation outAnimation) { +        mInAnimation = inAnimation; +        mOutAnimation = outAnimation; +        return this; +    } + +    /** +     * @return +     * Current priority +     * +     * @see #PRIORITY_HIGH +     * @see #PRIORITY_NORMAL +     * @see #PRIORITY_LOW +     */ +    public int getPriority() { +        return mPriority; +    } + +    /** +     * <p>Set priority for this message</p> +     * <p><b>Note</b>: This only affects the order in which the messages get shown, +     * not the stacking order of the views.</p> +     * +     * <p>Example: In the queue there are 3 messages [A, B, C], +     * all of them with priority {@link #PRIORITY_NORMAL}, currently message A is being shown +     * so we add a new message D with priority {@link #PRIORITY_HIGH}, after A goes away, given that +     * D has a higher priority than B an the reset, D will be shown, then once that D is gone, +     * B will be shown, and then finally C.</p> +     * +     * @param priority +     * A value indicating priority, although you can use any integer value, usage of already +     * defined is highly encouraged. +     * +     * @see #PRIORITY_HIGH +     * @see #PRIORITY_NORMAL +     * @see #PRIORITY_LOW +     */ +    public void setPriority(int priority) { +        mPriority = priority; +    } + +    /** +     * @return +     * Provided parent to add {@link #getView()} to using {@link #getLayoutParams()}. +     */ +    public ViewGroup getParent() { +        return mParent; +    } + +    /** +     * Provide a different parent than Activity decor view +     * @param parent +     * Provided parent to add {@link #getView()} to using {@link #getLayoutParams()}. +     * +     */ +    public void setParent(ViewGroup parent) { +        mParent = parent; +    } + +    /** +     * Provide a different parent than Activity decor view +     * +     * @param parentId +     * Provided parent id to add {@link #getView()} to using {@link #getLayoutParams()}. +     * +     */ +    public void setParent(int parentId) { +        setParent((ViewGroup) mActivity.findViewById(parentId)); +    } + +    /** +     * The style for a {@link AppMsg}. +     * +     * @author e.shishkin +     */ +    public static class Style { + +        private final int duration; +        private final int background; + +        /** +         * Construct an {@link AppMsg.Style} object. +         * +         * @param duration How long to display the message. Either +         *                 {@link #LENGTH_SHORT} or {@link #LENGTH_LONG} +         * @param resId    resource for AppMsg background +         */ +        public Style(int duration, int resId) { +            this.duration = duration; +            this.background = resId; +        } + +        /** +         * Return the duration in milliseconds. +         */ +        public int getDuration() { +            return duration; +        } + +        /** +         * Return the resource id of background. +         */ +        public int getBackground() { +            return background; +        } + +        @Override +        public boolean equals(Object o) { +            if (!(o instanceof AppMsg.Style)) { +                return false; +            } +            Style style = (Style) o; +            return style.duration == duration +                    && style.background == background; +        } + +    } + +} diff --git a/libraries/Android-AppMsg/library/src/com/devspark/appmsg/MsgManager.java b/libraries/Android-AppMsg/library/src/com/devspark/appmsg/MsgManager.java new file mode 100644 index 000000000..962648566 --- /dev/null +++ b/libraries/Android-AppMsg/library/src/com/devspark/appmsg/MsgManager.java @@ -0,0 +1,340 @@ +/* + * Copyright 2012 Evgeny Shishkin + * + * 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.devspark.appmsg; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.WeakHashMap; + +import static android.app.Application.ActivityLifecycleCallbacks; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; +import static com.devspark.appmsg.AppMsg.LENGTH_STICKY; + +/** + * @author Evgeny Shishkin + */ +class MsgManager extends Handler implements Comparator<AppMsg> { + +    private static final int MESSAGE_DISPLAY = 0xc2007; +    private static final int MESSAGE_ADD_VIEW = 0xc20074dd; +    private static final int MESSAGE_REMOVE = 0xc2007de1; + +    private static WeakHashMap<Activity, MsgManager> sManagers; +    private static ReleaseCallbacks sReleaseCallbacks; + +    private final Queue<AppMsg> msgQueue; +    private final Queue<AppMsg> stickyQueue; + +    private MsgManager() { +        msgQueue = new PriorityQueue<AppMsg>(1, this); +        stickyQueue = new LinkedList<AppMsg>(); +    } + +    /** +     * @return A {@link MsgManager} instance to be used for given {@link android.app.Activity}. +     */ +    static synchronized MsgManager obtain(Activity activity) { +        if (sManagers == null) { +            sManagers = new WeakHashMap<Activity, MsgManager>(1); +        } +        MsgManager manager = sManagers.get(activity); +        if (manager == null) { +            manager = new MsgManager(); +            ensureReleaseOnDestroy(activity); +            sManagers.put(activity, manager); +        } + +        return manager; +    } + +    static void ensureReleaseOnDestroy(Activity activity) { +        if (SDK_INT < ICE_CREAM_SANDWICH) { +            return; +        } +        if (sReleaseCallbacks == null) { +            sReleaseCallbacks = new ReleaseCallbacksIcs(); +        } +        sReleaseCallbacks.register(activity.getApplication()); +    } + + +    static synchronized void release(Activity activity) { +        if (sManagers != null) { +            final MsgManager manager = sManagers.remove(activity); +            if (manager != null) { +                manager.clearAllMsg(); +            } +        } +    } + +    static synchronized void clearAll() { +        if (sManagers != null) { +            final Iterator<MsgManager> iterator = sManagers.values().iterator(); +            while (iterator.hasNext()) { +                final MsgManager manager = iterator.next(); +                if (manager != null) { +                    manager.clearAllMsg(); +                } +                iterator.remove(); +            } +            sManagers.clear(); +        } +    } + +    /** +     * Inserts a {@link AppMsg} to be displayed. +     * +     * @param appMsg +     */ +    void add(AppMsg appMsg) { +        msgQueue.add(appMsg); +        if (appMsg.mInAnimation == null) { +            appMsg.mInAnimation = AnimationUtils.loadAnimation(appMsg.getActivity(), +                    android.R.anim.fade_in); +        } +        if (appMsg.mOutAnimation == null) { +            appMsg.mOutAnimation = AnimationUtils.loadAnimation(appMsg.getActivity(), +                    android.R.anim.fade_out); +        } +        displayMsg(); +    } + +    /** +     * Removes all {@link AppMsg} from the queue. +     */ +    void clearMsg(AppMsg appMsg) { +        if(msgQueue.contains(appMsg) || stickyQueue.contains(appMsg)){ +            // Avoid the message from being removed twice. +            removeMessages(MESSAGE_DISPLAY, appMsg); +            removeMessages(MESSAGE_ADD_VIEW, appMsg); +            removeMessages(MESSAGE_REMOVE, appMsg); +            msgQueue.remove(appMsg); +            stickyQueue.remove(appMsg); +            removeMsg(appMsg); +        } +    } + +    /** +     * Removes all {@link AppMsg} from the queue. +     */ +    void clearAllMsg() { +        removeMessages(MESSAGE_DISPLAY); +        removeMessages(MESSAGE_ADD_VIEW); +        removeMessages(MESSAGE_REMOVE); +        clearShowing(); +        msgQueue.clear(); +        stickyQueue.clear(); +    } + +    void clearShowing() { +        final Collection<AppMsg> showing = new HashSet<AppMsg>(); +        obtainShowing(msgQueue, showing); +        obtainShowing(stickyQueue, showing); +        for (AppMsg msg : showing) { +            clearMsg(msg); +        } +    } + +    static void obtainShowing(Collection<AppMsg> from, Collection<AppMsg> appendTo) { +        for (AppMsg msg : from) { +            if (msg.isShowing()) { +                appendTo.add(msg); +            } +        } +    } + +    /** +     * Displays the next {@link AppMsg} within the queue. +     */ +    private void displayMsg() { +        if (msgQueue.isEmpty()) { +            return; +        } +        // First peek whether the AppMsg is being displayed. +        final AppMsg appMsg = msgQueue.peek(); +        final Message msg; +        if (!appMsg.isShowing()) { +            // Display the AppMsg +            msg = obtainMessage(MESSAGE_ADD_VIEW); +            msg.obj = appMsg; +            sendMessage(msg); +        } else if (appMsg.getDuration() != LENGTH_STICKY) { +            msg = obtainMessage(MESSAGE_DISPLAY); +            sendMessageDelayed(msg, appMsg.getDuration() +                    + appMsg.mInAnimation.getDuration() + appMsg.mOutAnimation.getDuration()); +        } +    } + +    /** +     * Removes the {@link AppMsg}'s view after it's display duration. +     * +     * @param appMsg The {@link AppMsg} added to a {@link ViewGroup} and should be removed.s +     */ +    private void removeMsg(final AppMsg appMsg) { +        clearMsg(appMsg); +        final View view = appMsg.getView(); +        ViewGroup parent = ((ViewGroup) view.getParent()); +        if (parent != null) { +            appMsg.mOutAnimation.setAnimationListener(new OutAnimationListener(appMsg)); +            view.clearAnimation(); +            view.startAnimation(appMsg.mOutAnimation); +        } + +        Message msg = obtainMessage(MESSAGE_DISPLAY); +        sendMessage(msg); +    } + +    private void addMsgToView(AppMsg appMsg) { +        View view = appMsg.getView(); +        if (view.getParent() == null) { // Not added yet +            final ViewGroup targetParent = appMsg.getParent(); +            final ViewGroup.LayoutParams params = appMsg.getLayoutParams(); +            if (targetParent != null) { +                targetParent.addView(view, params); +            } else { +                appMsg.getActivity().addContentView(view, params); +            } +        } +        view.clearAnimation(); +        view.startAnimation(appMsg.mInAnimation); +        if (view.getVisibility() != View.VISIBLE) { +            view.setVisibility(View.VISIBLE); +        } + +        final int duration = appMsg.getDuration(); +        if (duration != LENGTH_STICKY) { +            final Message msg = obtainMessage(MESSAGE_REMOVE); +            msg.obj = appMsg; +            sendMessageDelayed(msg, duration); +        } else { // We are sticky, we don't get removed just yet +            stickyQueue.add(msgQueue.poll()); +        } +    } + +    @Override +    public void handleMessage(Message msg) { +        final AppMsg appMsg; +        switch (msg.what) { +            case MESSAGE_DISPLAY: +                displayMsg(); +                break; +            case MESSAGE_ADD_VIEW: +                appMsg = (AppMsg) msg.obj; +                addMsgToView(appMsg); +                break; +            case MESSAGE_REMOVE: +                appMsg = (AppMsg) msg.obj; +                removeMsg(appMsg); +                break; +            default: +                super.handleMessage(msg); +                break; +        } +    } + +    @Override +    public int compare(AppMsg lhs, AppMsg rhs) { +        return inverseCompareInt(lhs.mPriority, rhs.mPriority); +    } + +    static int inverseCompareInt(int lhs, int rhs) { +        return lhs < rhs ? 1 : (lhs == rhs ? 0 : -1); +    } + +    private static class OutAnimationListener implements Animation.AnimationListener { + +        private final AppMsg appMsg; + +        private OutAnimationListener(AppMsg appMsg) { +            this.appMsg = appMsg; +        } + +        @Override +        public void onAnimationStart(Animation animation) { + +        } + +        @Override +        public void onAnimationEnd(Animation animation) { +            final View view = appMsg.getView(); +            if (appMsg.isFloating()) { +                final ViewGroup parent = ((ViewGroup) view.getParent()); +                if (parent != null) { +                    parent.post(new Runnable() { // One does not simply removeView +                        @Override +                        public void run() { +                            parent.removeView(view); +                        } +                    }); +                } +            } else { +                view.setVisibility(View.GONE); +            } +        } + +        @Override +        public void onAnimationRepeat(Animation animation) { + +        } +    } + +    interface ReleaseCallbacks { +        void register(Application application); +    } + +    @TargetApi(ICE_CREAM_SANDWICH) +    static class ReleaseCallbacksIcs implements ActivityLifecycleCallbacks, ReleaseCallbacks { +        private WeakReference<Application> mLastApp; +        public void register(Application app) { +            if (mLastApp != null && mLastApp.get() == app) { +                return; // Already registered with this app +            } else { +                mLastApp = new WeakReference<Application>(app); +            } +            app.registerActivityLifecycleCallbacks(this); +        } + +        @Override +        public void onActivityDestroyed(Activity activity) { +            release(activity); +        } +        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} +        @Override public void onActivityStarted(Activity activity) {} +        @Override public void onActivityResumed(Activity activity) {} +        @Override public void onActivityPaused(Activity activity) {} +        @Override public void onActivityStopped(Activity activity) {} +        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} +    } +}
\ No newline at end of file | 
