<commit_before>package wei.mark.standout;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

/**
 * Extend this class to easily create and manage floating StandOut windows.
 * 
 * @author <NAME> <<EMAIL>>
 * 
 */
public abstract class StandOutWindow extends Service {
	private static final String TAG = "StandOutWindow";

	/**
	 * StandOut window id: You may use this sample id for your first window.
	 */
	public static final int DEFAULT_ID = 0;

	/**
	 * Special StandOut window id: You may NOT use this id for any windows.
	 */
	public static final int ONGOING_NOTIFICATION_ID = -1;

	/**
	 * StandOut window id: You may use this id when you want it to be
	 * disregarded. The system makes no distinction for this id; it is only used
	 * to improve code readability.
	 */
	public static final int DISREGARD_ID = -2;

	/**
	 * Intent action: Show a new window corresponding to the id.
	 */
	public static final String ACTION_SHOW = "SHOW";

	/**
	 * Intent action: Restore a previously hidden window corresponding to the
	 * id. The window should be previously hidden with {@link #ACTION_HIDE}.
	 */
	public static final String ACTION_RESTORE = "RESTORE";

	/**
	 * Intent action: Close an existing window with an existing id.
	 */
	public static final String ACTION_CLOSE = "CLOSE";

	/**
	 * Intent action: Close all existing windows.
	 */
	public static final String ACTION_CLOSE_ALL = "CLOSE_ALL";

	/**
	 * Intent action: Send data to a new or existing window.
	 */
	public static final String ACTION_SEND_DATA = "SEND_DATA";

	/**
	 * Intent action: Hide an existing window with an existing id. To enable the
	 * ability to restore this window, make sure you implement
	 * {@link #getHiddenNotification(int)}.
	 */
	public static final String ACTION_HIDE = "HIDE";

	/**
	 * Flags to be returned from {@link StandOutWindow#getFlags(int)}. This
	 * class was created to avoid polluting the flags namespace.
	 * 
	 * @author <NAME> <<EMAIL>>
	 * 
	 */
	public static class StandOutFlags {

		private static int flag_counter = 0;

		/**
		 * This default flag indicates that the window requires no window
		 * decorations (titlebar, hide/close buttons, resize handle, etc).
		 */
		public static final int FLAG_DECORATION_NONE = 0x00000000;

		/**
		 * Setting this flag indicates that the window wants the system provided
		 * window decorations (titlebar, hide/close buttons, resize handle,
		 * etc).
		 */
		public static final int FLAG_DECORATION_SYSTEM = 1 << flag_counter++;

		/**
		 * If {@link #FLAG_DECORATION_SYSTEM} is set, setting this flag
		 * indicates that the window decorator should NOT provide a close
		 * button.
		 */
		public static final int FLAG_DECORATION_CLOSE_DISABLE = 1 << flag_counter++;

		/**
		 * If {@link #FLAG_DECORATION_SYSTEM} is set, setting this flag
		 * indicates that the window decorator should NOT provide a resize
		 * handle.
		 */
		public static final int FLAG_DECORATION_RESIZE_DISABLE = 1 << flag_counter++;

		/**
		 * If {@link #FLAG_DECORATION_SYSTEM} is set, setting this flag
		 * indicates that the window decorator should NOT provide a resize
		 * handle.
		 */
		public static final int FLAG_DECORATION_MOVE_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the window can be moved by dragging
		 * the body.
		 * 
		 * <p>
		 * Note that if {@link #FLAG_DECORATION_SYSTEM} is set, the window can
		 * always be moved by dragging the titlebar.
		 */
		public static final int FLAG_BODY_MOVE_ENABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the window should be brought to the
		 * front upon user interaction.
		 * 
		 * <p>
		 * Note that if you set this flag, there is a noticeable flashing of the
		 * window during {@link MotionEvent#ACTION_UP}. This the hack that
		 * allows the system to bring the window to the front.
		 */
		public static final int FLAG_WINDOW_BRING_TO_FRONT_ON_TOUCH = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the window should be brought to the
		 * front upon user tap.
		 * 
		 * <p>
		 * Note that if you set this flag, there is a noticeable flashing of the
		 * window during {@link MotionEvent#ACTION_UP}. This the hack that
		 * allows the system to bring the window to the front.
		 */
		public static final int FLAG_WINDOW_BRING_TO_FRONT_ON_TAP = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should keep the window's
		 * position within the edges of the screen. If this flag is not set, the
		 * window will be able to be dragged off of the screen.
		 * 
		 * <p>
		 * If this flag is set, the window's {@link Gravity} is recommended to
		 * be {@link Gravity#TOP} | {@link Gravity#LEFT}. If the gravity is
		 * anything other than TOP|LEFT, then even though the window will be
		 * displayed within the edges, it will behave as if the user can drag it
		 * off the screen.
		 * 
		 */
		public static final int FLAG_WINDOW_EDGE_LIMITS_ENABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that windows are able to be hidden, that
		 * {@link #getHiddenIcon(int)}, {@link #getHiddenTitle(int)}, and
		 * {@link #getHiddenMessage(int)} are implemented, and that the system
		 * window decorator should provide a hide button if
		 * {@link #FLAG_DECORATION_SYSTEM} is set.
		 */
		public static final int FLAG_HIDE_ENABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable all
		 * compatibility workarounds. The default behavior is to run
		 * {@link #fixCompatibility(View)} on the view returned by the
		 * implementation.
		 * 
		 * @see #fixCompatibility(View)
		 */
		public static final int FLAG_FIX_COMPATIBILITY_ALL_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable EditText
		 * compatibility workarounds.
		 * 
		 * @see #fixCompatibility(View)
		 * 
		 */
		public static final int FLAG_FIX_COMPATIBILITY_EDITTEXT_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable ListView
		 * compatibility workarounds.
		 * 
		 * @see #fixCompatibility(View)
		 */
		public static final int FLAG_FIX_COMPATIBILITY_LISTVIEW_DISABLE = 1 << flag_counter++;
	}

	/**
	 * Show a new window corresponding to the id, or restore a previously hidden
	 * window.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that will be used
	 *            to create and manage the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * 
	 * @see #show(int)
	 */
	public static void show(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		context.startService(getShowIntent(context, cls, id));
	}

	/**
	 * Hide the existing window corresponding to the id. To enable the ability
	 * to restore this window, make sure you implement
	 * {@link #getHiddenNotification(int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. The window must previously be
	 *            shown.
	 * @see #hide(int)
	 */
	public static void hide(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		context.startService(getShowIntent(context, cls, id));
	}

	/**
	 * Close an existing window with an existing id.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. The window must previously be
	 *            shown.
	 * @see #close(int)
	 */
	public static void close(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		context.startService(getCloseIntent(context, cls, id));
	}

	/**
	 * Close all existing windows.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @see #closeAll()
	 */
	public static void closeAll(Context context,
			Class<? extends StandOutWindow> cls) {
		context.startService(getCloseAllIntent(context, cls));
	}

	/**
	 * Send {@link Parceleable} data in a {@link Bundle} to a new or existing
	 * windows. The implementation of the recipient window can handle what to do
	 * with the data. To receive a result, provide the class and id of the
	 * sender.
	 * 
	 * @param context
	 *            A Context of the application package implementing the class of
	 *            the sending window.
	 * @param toCls
	 *            The Service's class extending {@link StandOutWindow} that is
	 *            managing the receiving window.
	 * @param toId
	 *            The id of the receiving window.
	 * @param requestCode
	 *            Provide a request code to declare what kind of data is being
	 *            sent.
	 * @param data
	 *            A bundle of parceleable data to be sent to the receiving
	 *            window.
	 * @param fromCls
	 *            If the sending window wants a result, provide the class of the
	 *            sending window.
	 * @param fromId
	 *            If the sending window wants a result, provide the id of the
	 *            sending window.
	 * @see #sendData(int, Class, int, int, Bundle)
	 */
	public static void sendData(Context context,
			Class<? extends StandOutWindow> toCls, int toId, int requestCode,
			Bundle data, Class<? extends StandOutWindow> fromCls, int fromId) {
		context.startService(getSendDataIntent(context, toCls, toId,
				requestCode, data, fromCls, fromId));
	}

	/**
	 * See {@link #show(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that will be used
	 *            to create and manage the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getShowIntent(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		boolean cached = isCached(cls, id);
		String action = cached ? ACTION_RESTORE : ACTION_SHOW;
		Uri uri = cached ? Uri.parse("standout://" + cls + '/' + id) : null;
		return new Intent(context, cls).putExtra("id", id).setAction(action)
				.setData(uri);
	}

	/**
	 * See {@link #hide(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getHideIntent(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		return new Intent(context, cls).putExtra("id", id).setAction(
				ACTION_HIDE);
	}

	/**
	 * See {@link #close(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getCloseIntent(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		return new Intent(context, cls).putExtra("id", id).setAction(
				ACTION_CLOSE);
	}

	/**
	 * See {@link #closeAll(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getCloseAllIntent(Context context,
			Class<? extends StandOutWindow> cls) {
		return new Intent(context, cls).setAction(ACTION_CLOSE_ALL);
	}

	/**
	 * See {@link #sendData(Context, Class, int, int, Bundle, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing the class of
	 *            the sending window.
	 * @param toCls
	 *            The Service's class extending {@link StandOutWindow} that is
	 *            managing the receiving window.
	 * @param toId
	 *            The id of the receiving window.
	 * @param requestCode
	 *            Provide a request code to declare what kind of data is being
	 *            sent.
	 * @param data
	 *            A bundle of parceleable data to be sent to the receiving
	 *            window.
	 * @param fromCls
	 *            If the sending window wants a result, provide the class of the
	 *            sending window.
	 * @param fromId
	 *            If the sending window wants a result, provide the id of the
	 *            sending window.
	 * @return An {@link Intnet} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getSendDataIntent(Context context,
			Class<? extends StandOutWindow> toCls, int toId, int requestCode,
			Bundle data, Class<? extends StandOutWindow> fromCls, int fromId) {
		return new Intent(context, toCls).putExtra("id", toId)
				.putExtra("requestCode", requestCode)
				.putExtra("wei.mark.standout.data", data)
				.putExtra("wei.mark.standout.fromCls", fromCls)
				.putExtra("fromId", fromId).setAction(ACTION_SEND_DATA);
	}

	// internal map of ids to shown/hidden views
	private static Map<Class<? extends StandOutWindow>, Map<Integer, View>> sViews;

	// static constructors
	static {
		sViews = new HashMap<Class<? extends StandOutWindow>, Map<Integer, View>>();
	}

	/**
	 * Returns whether the window corresponding to the class and id exists in
	 * the {@link #sViews} cache.
	 * 
	 * @param cls
	 *            Class corresponding to the window.
	 * @param id
	 *            The id representing the window.
	 * @return True if the window corresponding to the class and id exists in
	 *         the cache, or false if it does not exist.
	 */
	private static boolean isCached(Class<? extends StandOutWindow> cls, int id) {
		Map<Integer, View> l2 = sViews.get(cls);
		return l2 != null && l2.containsKey(id);
	}

	// internal system services
	private WindowManager mWindowManager;
	private NotificationManager mNotificationManager;
	private LayoutInflater mLayoutInflater;

	// internal state variables
	private boolean startedForeground;

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();

		mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
		mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		mLayoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		startedForeground = false;
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		super.onStartCommand(intent, flags, startId);

		// intent should be created with
		// getShowIntent(), getHideIntent(), getCloseIntent()
		if (intent != null) {
			String action = intent.getAction();
			int id = intent.getIntExtra("id", DEFAULT_ID);

			// this will interfere with getPersistentNotification()
			if (id == ONGOING_NOTIFICATION_ID) {
				throw new RuntimeException(
						"ID cannot equals StandOutWindow.ONGOING_NOTIFICATION_ID");
			}

			if (ACTION_SHOW.equals(action) || ACTION_RESTORE.equals(action)) {
				show(id);
			} else if (ACTION_HIDE.equals(action)) {
				hide(id);
			} else if (ACTION_CLOSE.equals(action)) {
				close(id);
			} else if (ACTION_CLOSE_ALL.equals(action)) {
				closeAll();
			} else if (ACTION_SEND_DATA.equals(action)) {
				Bundle data = intent.getBundleExtra("wei.mark.standout.data");
				int requestCode = intent.getIntExtra("requestCode", 0);
				@SuppressWarnings("unchecked")
				Class<? extends StandOutWindow> fromCls = (Class<? extends StandOutWindow>) intent
						.getSerializableExtra("wei.mark.standout.fromCls");
				int fromId = intent.getIntExtra("fromId", DEFAULT_ID);

				onReceiveData(id, requestCode, data, fromCls, fromId);
			}
		} else {
			Log.w(TAG, "Tried to onStartCommand() with a null intent");
		}

		// the service is started in foreground in show()
		// so we don't expect Android to kill this service
		return START_NOT_STICKY;
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		// closes all windows
		closeAll();
	}

	/**
	 * Return the name of every window in this implementation. The name will
	 * appear in the default implementations of the system window decoration
	 * title and notification titles.
	 * 
	 * @return The name.
	 */
	protected abstract String getAppName();

	/**
	 * Return the icon resource for every window in this implementation. The
	 * icon will appear in the default implementations of the system window
	 * decoration and notifications.
	 * 
	 * @return The icon.
	 */
	protected abstract int getAppIcon();

	/**
	 * Create a new {@link View} corresponding to the id, and add it as a child
	 * to the root. The view will become the contents of this StandOut window.
	 * The view MUST be newly created, and you MUST attach it to root.
	 * 
	 * <p>
	 * If you are inflating your view from XML, make sure you use
	 * {@link LayoutInflater#inflate(int, ViewGroup, boolean)} to attach your
	 * view to root. Set the ViewGroup to be root, and the boolean to true.
	 * 
	 * <p>
	 * If you are creating your view programmatically, make sure you use
	 * {@link ViewGroup#addView(View)} to add your view to root.
	 * 
	 * @param id
	 *            The id representing the window.
	 * @param root
	 *            The {@link ViewGroup} to attach your view as a child to.
	 * @return A new {@link View} corresponding to the id. The view will be the
	 *         content of this StandOut window. The view MUST be newly created.
	 */
	protected abstract View createAndAttachView(int id, ViewGroup root);

	/**
	 * Return the {@link StandOutWindow#LayoutParams} for the corresponding id.
	 * The system will set the layout params on the view for this StandOut
	 * window. The layout params may be reused.
	 * 
	 * 
	 * @param id
	 *            The id of the window.
	 * @param view
	 *            The view corresponding to the id. Given as courtesy, so you
	 *            may get the existing layout params.
	 * @return The {@link StandOutWindow#LayoutParams} corresponding to the id.
	 *         The layout params will be set on the view. The layout params may
	 *         be reused.
	 */
	protected abstract StandOutWindow.LayoutParams getParams(int id, View view);

	/**
	 * Implement this method to change modify the behavior and appearance of the
	 * window corresponding to the id.
	 * 
	 * <p>
	 * You may use any of the flags defined in {@link StandOutWindow} such as
	 * {@link #FLAG_DECORATION_NONE}.
	 * 
	 * <p>
	 * Use bitwise OR (|) to set flags, and bitwise XOR (^) to unset flags. To
	 * test if a flag is set, use (getFlags(id) & flag) != 0.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return Bitwise OR'd flags
	 */
	protected int getFlags(int id) {
		return StandOutFlags.FLAG_DECORATION_NONE;
	}

	/**
	 * Return the title for the persistent notification. This is called every
	 * time {@link #show(int)} is called.
	 * 
	 * @param id
	 *            The id of the window shown.
	 * @return The title for the persistent notification.
	 */
	protected String getPersistentNotificationTitle(int id) {
		return getAppName() + " Running";
	}

	/**
	 * Return the message for the persistent notification. This is called every
	 * time {@link #show(int)} is called.
	 * 
	 * @param id
	 *            The id of the window shown.
	 * @return The message for the persistent notification.
	 */
	protected String getPersistentNotificationMessage(int id) {
		return "";
	}

	/**
	 * Return the intent for the persistent notification. This is called every
	 * time {@link #show(int)} is called.
	 * 
	 * <p>
	 * The returned intent will be packaged into a {@link PendingIntent} to be
	 * invoked when the user clicks the notification.
	 * 
	 * @param id
	 *            The id of the window shown.
	 * @return The intent for the persistent notification.
	 */
	protected Intent getPersistentNotificationIntent(int id) {
		return null;
	}

	/**
	 * Return the icon resource for every hidden window in this implementation.
	 * The icon will appear in the default implementations of the hidden
	 * notifications.
	 * 
	 * @return The icon.
	 */
	protected int getHiddenIcon() {
		return getAppIcon();
	}

	/**
	 * Return the title for the hidden notification corresponding to the window
	 * being hidden.
	 * 
	 * @param id
	 *            The id of the hidden window.
	 * @return The title for the hidden notification.
	 */
	protected String getHiddenNotificationTitle(int id) {
		return getAppName() + " Hidden";
	}

	/**
	 * Return the message for the hidden notification corresponding to the
	 * window being hidden.
	 * 
	 * @param id
	 *            The id of the hidden window.
	 * @return The message for the hidden notification.
	 */
	protected String getHiddenNotificationMessage(int id) {
		return "";
	}

	/**
	 * Return the intent for the hidden notification corresponding to the window
	 * being hidden.
	 * 
	 * <p>
	 * The returned intent will be packaged into a {@link PendingIntent} to be
	 * invoked when the user clicks the notification.
	 * 
	 * @param id
	 *            The id of the hidden window.
	 * @return The intent for the hidden notification.
	 */
	protected Intent getHiddenNotificationIntent(int id) {
		return null;
	}

	/**
	 * Return a persistent {@link Notification} for the corresponding id. You
	 * must return a notification for AT LEAST the first id to be requested.
	 * Once the persistent notification is shown, further calls to
	 * {@link #getPersistentNotification(int)} may return null. This way Android
	 * can start the StandOut window service in the foreground and will not kill
	 * the service on low memory.
	 * 
	 * <p>
	 * As a courtesy, the system will request a notification for every new id
	 * shown. Your implementation is encouraged to include the
	 * {@link PendingIntent#FLAG_UPDATE_CURRENT} flag in the notification so
	 * that there is only one system-wide persistent notification.
	 * 
	 * <p>
	 * See the StandOutExample project for an implementation of
	 * {@link #getPersistentNotification(int)} that keeps one system-wide
	 * persistent notification that creates a new window on every click.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The {@link Notification} corresponding to the id, or null if
	 *         you've previously returned a notification.
	 */
	protected Notification getPersistentNotification(int id) {
		// basic notification stuff
		// http://developer.android.com/guide/topics/ui/notifiers/notifications.html
		int icon = getAppIcon();
		long when = System.currentTimeMillis();
		Context c = getApplicationContext();
		String contentTitle = getPersistentNotificationTitle(id);
		String contentText = getPersistentNotificationMessage(id);
		String tickerText = String.format("%s: %s", contentTitle, contentText);

		// getPersistentNotification() is called for every new window
		// so we replace the old notification with a new one that has
		// a bigger id
		Intent notificationIntent = getPersistentNotificationIntent(id);

		PendingIntent contentIntent = null;

		if (notificationIntent != null) {
			contentIntent = PendingIntent.getService(this, 0,
					notificationIntent,
					// flag updates existing persistent notification
					PendingIntent.FLAG_UPDATE_CURRENT);
		}

		Notification notification = new Notification(icon, tickerText, when);
		notification.setLatestEventInfo(c, contentTitle, contentText,
				contentIntent);
		return notification;
	}

	/**
	 * Return a hidden {@link Notification} for the corresponding id. The system
	 * will request a notification for every id that is hidden.
	 * 
	 * <p>
	 * If null is returned, StandOut will assume you do not wish to support
	 * hiding this window, and will {@link #close(int)} it for you.
	 * 
	 * <p>
	 * See the StandOutExample project for an implementation of
	 * {@link #getHiddenNotification(int)} that for every hidden window keeps a
	 * notification which restores that window upon user's click.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The {@link Notification} corresponding to the id or null.
	 */
	protected Notification getHiddenNotification(int id) {
		// same basics as getPersistentNotification()
		int icon = getHiddenIcon();
		long when = System.currentTimeMillis();
		Context c = getApplicationContext();
		String contentTitle = getHiddenNotificationTitle(id);
		String contentText = getHiddenNotificationMessage(id);
		String tickerText = String.format("%s: %s", contentTitle, contentText);

		// the difference here is we are providing the same id
		Intent notificationIntent = getHiddenNotificationIntent(id);

		PendingIntent contentIntent = null;

		if (notificationIntent != null) {
			contentIntent = PendingIntent.getService(this, 0,
					notificationIntent,
					// flag updates existing persistent notification
					PendingIntent.FLAG_UPDATE_CURRENT);
		}

		Notification notification = new Notification(icon, tickerText, when);
		notification.setLatestEventInfo(c, contentTitle, contentText,
				contentIntent);
		return notification;
	}

	/**
	 * Return the animation to play when the window corresponding to the id is
	 * shown.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The animation to play or null.
	 */
	protected Animation getShowAnimation(int id) {
		return AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
	}

	/**
	 * Return the animation to play when the window corresponding to the id is
	 * hidden.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The animation to play or null.
	 */
	protected Animation getHideAnimation(int id) {
		return AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
	}

	/**
	 * Return the animation to play when the window corresponding to the id is
	 * closed.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The animation to play or null.
	 */
	protected Animation getCloseAnimation(int id) {
		return AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
	}

	/**
	 * Implement this method to be alerted to touch events in the body of the
	 * window corresponding to the id.
	 * 
	 * <p>
	 * Note that even if you set {@link #FLAG_DECORATION_SYSTEM}, you will not
	 * receive touch events from the system window decorations.
	 * 
	 * @see {@link View.OnTouchListener#onTouch(View, MotionEvent)}
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param window
	 *            The window corresponding to the id, provided as a courtesy.
	 * @param touchInfo
	 *            The touch information of the window, provided as a courtesy.
	 * @param view
	 *            The view where the event originated from.
	 * @param event
	 *            See linked method.
	 */
	protected boolean onTouchBody(int id, View window,
			WindowTouchInfo touchInfo, View view, MotionEvent event) {
		return false;
	}

	/**
	 * Implement this method to be alerted to when the window corresponding to
	 * the id is moved.
	 * 
	 * @see {@link View.OnTouchListener#onTouch(View, MotionEvent)}
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param window
	 *            The window corresponding to the id, provided as a courtesy.
	 * @param touchInfo
	 *            The touch information of the window, provided as a courtesy.
	 * @param view
	 *            The view where the event originated from.
	 * @param event
	 *            See linked method.
	 */
	protected void onMove(int id, View window, WindowTouchInfo touchInfo,
			View view, MotionEvent event) {
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be shown. This callback will occur before the view is
	 * added to the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be shown.
	 * @return Return true to cancel the view from being shown, or false to
	 *         continue.
	 * @see #show(int)
	 */
	protected boolean onShow(int id, View window) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be hidden. This callback will occur before the view is
	 * removed from the window manager and {@link #getHiddenNotification(int)}
	 * is called.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be hidden.
	 * @return Return true to cancel the view from being hidden, or false to
	 *         continue.
	 * @see #hide(int)
	 */
	protected boolean onHide(int id, View window) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be closed. This callback will occur before the view is
	 * removed from the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be closed.
	 * @return Return true to cancel the view from being closed, or false to
	 *         continue.
	 * @see #close(int)
	 */
	protected boolean onClose(int id, View window) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when all windows are about to be
	 * closed. This callback will occur before any views are removed from the
	 * window manager.
	 * 
	 * @return Return true to cancel the views from being closed, or false to
	 *         continue.
	 * @see #closeAll()
	 */
	protected boolean onCloseAll() {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id has received some data. The sender is described by fromCls and fromId
	 * if the sender wants a result. To send a result, use
	 * {@link #sendData(int, Class, int, int, Bundle)}.
	 * 
	 * @param id
	 *            The id of your receiving window.
	 * @param requestCode
	 *            The sending window provided this request code to declare what
	 *            kind of data is being sent.
	 * @param data
	 *            A bundle of parceleable data that was sent to your receiving
	 *            window.
	 * @param fromCls
	 *            The sending window's class. Provided if the sender wants a
	 *            result.
	 * @param fromId
	 *            The sending window's id. Provided if the sender wants a
	 *            result.
	 */
	protected void onReceiveData(int id, int requestCode, Bundle data,
			Class<? extends StandOutWindow> fromCls, int fromId) {
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be updated in the layout. This callback will occur before
	 * the view is updated by the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be closed.
	 * @param params
	 *            The updated layout params.
	 * @return Return true to cancel the view from being updated, or false to
	 *         continue.
	 * @see #updateViewLayout(int, View, LayoutParams)
	 */
	protected boolean onUpdate(int id, View window,
			StandOutWindow.LayoutParams params) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be bought to the front. This callback will occur before
	 * the view is brought to the front by the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be brought to the front.
	 * @return Return true to cancel the view from being brought to the front,
	 *         or false to continue.
	 * @see #bringToFront(int)
	 */
	protected boolean onBringToFront(int id, View window) {
		return false;
	}

	/**
	 * Show or restore a window corresponding to the id. Return the window that
	 * was shown/restored.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The window shown.
	 */
	protected final synchronized View show(int id) {
		// get the view corresponding to the id
		View window = getWrappedView(id);

		if (window == null) {
			Log.w(TAG, "Tried to show(" + id + ") a null view");
			return null;
		}

		// alert callbacks and cancel if instructed
		if (onShow(id, window))
			return null;

		// get animation
		Animation animation = getShowAnimation(id);

		WrappedTag tag = (WrappedTag) window.getTag();
		tag.shown = true;

		// get the params corresponding to the id
		StandOutWindow.LayoutParams params = (LayoutParams) window
				.getLayoutParams();
		if (params == null) {
			params = getParams(id, window);
		}

		try {
			// add the view to the window manager
			mWindowManager.addView(window, params);

			// animate
			if (animation != null) {
				((ViewGroup) window).getChildAt(0).startAnimation(animation);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		// add view to internal map
		putCache(id, window);

		// get the persistent notification
		Notification notification = getPersistentNotification(id);

		// show the notification
		if (notification != null) {
			notification.flags = notification.flags
					| Notification.FLAG_NO_CLEAR;

			// only show notification if not shown before
			if (!startedForeground) {
				// tell Android system to show notification
				startForeground(
						getClass().hashCode() + ONGOING_NOTIFICATION_ID,
						notification);
				startedForeground = true;
			} else {
				// update notification if shown before
				mNotificationManager.notify(getClass().hashCode()
						+ ONGOING_NOTIFICATION_ID, notification);
			}
		} else {
			// notification can only be null if it was provided before
			if (!startedForeground) {
				throw new RuntimeException("Your StandOutWindow service must"
						+ "provide a persistent notification."
						+ "The notification prevents Android"
						+ "from killing your service in low"
						+ "memory situations.");
			}
		}

		return window;
	}

	/**
	 * Hide a window corresponding to the id. Show a notification for the hidden
	 * window.
	 * 
	 * @param id
	 *            The id of the window.
	 */
	protected final synchronized void hide(int id) {
		int flags = getFlags(id);

		// check if hide enabled
		if ((flags & StandOutFlags.FLAG_HIDE_ENABLE) != 0) {
			// get the hidden notification for this view
			Notification notification = getHiddenNotification(id);

			// get the view corresponding to the id
			final View window = getWrappedView(id);

			if (window == null) {
				Log.w(TAG, "Tried to hide(" + id + ") a null view");
				return;
			}

			// alert callbacks and cancel if instructed
			if (onHide(id, window))
				return;

			// get animation
			Animation animation = getHideAnimation(id);

			WrappedTag tag = (WrappedTag) window.getTag();
			tag.shown = false;

			try {
				// animate
				if (animation != null) {
					animation.setAnimationListener(new AnimationListener() {

						@Override
						public void onAnimationStart(Animation animation) {
						}

						@Override
						public void onAnimationRepeat(Animation animation) {
						}

						@Override
						public void onAnimationEnd(Animation animation) {
							// remove the view from the window manager
							mWindowManager.removeView(window);
						}
					});
					((ViewGroup) window).getChildAt(0)
							.startAnimation(animation);
				} else {
					// remove the view from the window manager
					mWindowManager.removeView(window);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			// display the notification
			notification.flags = notification.flags
					| Notification.FLAG_NO_CLEAR
					| Notification.FLAG_AUTO_CANCEL;

			mNotificationManager.notify(getClass().hashCode() + id,
					notification);
		} else {
			// if hide not enabled, close window
			close(id);
		}
	}

	/**
	 * Close a window corresponding to the id.
	 * 
	 * @param id
	 *            The id of the window.
	 */
	protected final synchronized void close(int id) {
		// get the view corresponding to the id
		final View window = getWrappedView(id);

		if (window == null) {
			Log.w(TAG, "Tried to close(" + id + ") a null view");
			return;
		}

		// alert callbacks and cancel if instructed
		if (onClose(id, window))
			return;

		// remove view from internal map
		removeCache(id);

		// get animation
		Animation animation = getCloseAnimation(id);

		WrappedTag tag = (WrappedTag) window.getTag();

		if (tag.shown) {
			try {
				// animate
				if (animation != null) {
					animation.setAnimationListener(new AnimationListener() {

						@Override
						public void onAnimationStart(Animation animation) {
						}

						@Override
						public void onAnimationRepeat(Animation animation) {
						}

						@Override
						public void onAnimationEnd(Animation animation) {
							// remove the view from the window manager
							mWindowManager.removeView(window);
						}
					});
					((ViewGroup) window).getChildAt(0)
							.startAnimation(animation);
				} else {
					// remove the view from the window manager
					mWindowManager.removeView(window);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		} else {
			tag.shown = false;
			// cancel hidden notification
			mNotificationManager.cancel(id);
		}

		// if we just released the last view, quit
		if (getCacheSize() == 0) {
			// tell Android to remove the persistent notification
			// the Service will be shutdown by the system on low memory
			startedForeground = false;
			stopForeground(true);
		}
	}

	/**
	 * Close all existing windows.
	 */
	protected final synchronized void closeAll() {
		// alert callbacks and cancel if instructed
		if (onCloseAll())
			return;

		// add ids to temporary set to avoid concurrent modification
		LinkedList<Integer> ids = new LinkedList<Integer>();
		for (int id : getCacheIds()) {
			ids.add(id);
		}

		// close each window
		for (int id : ids) {
			close(id);
		}
	}

	/**
	 * Send {@link Parceleable} data in a {@link Bundle} to a new or existing
	 * windows. The implementation of the recipient window can handle what to do
	 * with the data. To receive a result, provide the id of the sender.
	 * 
	 * @param fromId
	 *            If your window wants a result, provide the id of the sending
	 *            window.
	 * @param toCls
	 *            The Service's class extending {@link StandOutWindow} that is
	 *            managing the receiving window.
	 * @param toId
	 *            The id of the receiving window.
	 * @param requestCode
	 *            Provide a request code to declare what kind of data is being
	 *            sent.
	 * @param data
	 *            A bundle of parceleable data to be sent to the receiving
	 *            window.
	 */
	protected final void sendData(int fromId,
			Class<? extends StandOutWindow> toCls, int toId, int requestCode,
			Bundle data) {
		StandOutWindow.sendData(this, toCls, toId, requestCode, data,
				getClass(), fromId);
	}

	/**
	 * Update the window corresponding to this view with the given params.
	 * 
	 * @param window
	 *            The window to update.
	 * @param params
	 *            The updated layout params to apply.
	 */
	protected final void updateViewLayout(int id, View window,
			StandOutWindow.LayoutParams params) {
		// alert callbacks and cancel if instructed
		if (onUpdate(id, window, params))
			return;

		if (window == null) {
			Log.w(TAG, "Tried to updateViewLayout() a null window");
			return;
		}

		try {
			mWindowManager.updateViewLayout(window, params);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Bring the window corresponding to this id in front of all other windows.
	 * The window may flicker as it is removed and restored by the system.
	 * 
	 * @param id
	 *            The id of the window to bring to the front.
	 */
	protected final synchronized void bringToFront(int id) {
		View window = getWrappedView(id);
		if (window == null) {
			Log.w(TAG, "Tried to bringToFront() a null view");
			return;
		}

		// alert callbacks and cancel if instructed
		if (onBringToFront(id, window))
			return;

		StandOutWindow.LayoutParams params = (LayoutParams) window
				.getLayoutParams();
		if (params == null) {
			params = getParams(id, window);
		}

		// remove from window manager then add back
		try {
			mWindowManager.removeView(window);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		try {
			mWindowManager.addView(window, params);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Gets a unique id to assign to a new window.
	 * 
	 * @return The unique id.
	 */
	protected final int getUniqueId() {
		Map<Integer, View> l2 = sViews.get(getClass());
		if (l2 == null) {
			l2 = new HashMap<Integer, View>();
			sViews.put(getClass(), l2);
		}

		int unique = DEFAULT_ID;
		for (int id : l2.keySet()) {
			unique = Math.max(unique, id + 1);
		}
		return unique;
	}

	/**
	 * Return whether the window corresponding to the id exists. This is useful
	 * for testing if the id is being restored (return true) or shown for the
	 * first time (return false).
	 * 
	 * @param id
	 *            The id of the window.
	 * @return True if the window corresponding to the id is either shown or
	 *         hidden, or false if it has never been shown or was previously
	 *         closed.
	 */
	protected final boolean isExistingId(int id) {
		return isCached(getClass(), id);
	}

	/**
	 * Return the window corresponding to the id, if it exists in cache. The
	 * window will not be created with
	 * {@link #createAndAttachView(int, ViewGroup)}. This means the returned
	 * value will be null if the window is not shown/hidden.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The window if it is shown/hidden, or null if it is closed.
	 */
	protected final View getWindow(int id) {
		return getCache(id);
	}

	/**
	 * Wraps the view from getView() into a frame that is easier to manage. The
	 * frame allows us to pass touch input to implementations and set a
	 * {@link WrappedTag} to keep track of the id, visibility, etc.
	 * 
	 * @param id
	 *            The id representing the wrapped view.
	 * @return The wrapped view from cache, or created from the implementation.
	 */
	private View getWrappedView(final int id) {
		// try get the wrapped view from the internal map
		View cachedView = getCache(id);

		// if the wrapped view exists, then return it rather than creating one
		if (cachedView != null) {
			return cachedView;
		}

		// create the wrapping frame and body
		final FrameLayout window = new FrameLayout(this);
		View content;
		FrameLayout body;

		int flags = getFlags(id);

		if ((flags & StandOutFlags.FLAG_DECORATION_SYSTEM) != 0) {
			// requested system window decorations
			content = getSystemWindowContent(id);
			body = (FrameLayout) content.findViewById(R.id.body);
		} else {
			// did not request decorations. will provide own implementation
			content = new FrameLayout(this);
			body = (FrameLayout) content;
		}

		window.addView(content);
		content.setTag(window);

		// body should always send touch events to onTouchBody()
		final boolean bodyMoveEnabled = (flags & StandOutFlags.FLAG_BODY_MOVE_ENABLE) != 0;
		body.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				// pass all touch events to the implementation
				WrappedTag tag = (WrappedTag) window.getTag();
				WindowTouchInfo touchInfo = tag.touchInfo;

				boolean consumed = false;

				consumed = onTouchWindow(id, window, touchInfo, v, event)
						|| consumed;

				// if set FLAG_BODY_MOVE_ENABLE, move the window
				if (bodyMoveEnabled) {
					consumed = onTouchHandleMove(id, window, touchInfo, v,
							event) || consumed;
				}

				consumed = onTouchBody(id, window, touchInfo, v, event)
						|| consumed;

				return consumed;
			}
		});

		// attach the view corresponding to the id from the implementation
		View view = createAndAttachView(id, body);

		// make sure the implemention created a view
		if (view == null) {
			throw new RuntimeException(
					"Your view must not be null in createAndAttachView()");
		}
		// make sure the implementation attached the view
		if (view != body && view.getParent() == null) {
			throw new RuntimeException(
					"You must attach your view to the given root ViewGroup in createAndAttachView()");
		}

		// clean up view and implement StandOut specific workarounds
		if ((flags & StandOutFlags.FLAG_FIX_COMPATIBILITY_ALL_DISABLE) == 0) {
			fixCompatibility(view, id);
			addFunctionality(view, id);
		}

		// wrap the existing tag and attach it to the frame
		window.setTag(new WrappedTag(id, false, view.getTag()));

		window.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
					case MotionEvent.ACTION_OUTSIDE:
						WrappedTag tag = (WrappedTag) window.getTag();
						WindowTouchInfo touchInfo = tag.touchInfo;
						onTouchBody(id, window, touchInfo, v, event);
						return true;
				}
				return false;
			}
		});

		return window;
	}

	/**
	 * Implement StandOut specific additional functionalities.
	 * 
	 * <p>
	 * Currently, this method does the following:
	 * 
	 * <p>
	 * Attach resize handles: For every View found to have id R.id.corner,
	 * attach an OnTouchListener that implements resizing the window.
	 * 
	 * @param view
	 *            The view hierarchy to implement additional functionality.
	 * @param id
	 *            The id of the window.
	 */
	private void addFunctionality(View view, final int id) {
		View corner = view.findViewById(R.id.corner);
		if (corner != null) {
			corner.setOnTouchListener(new OnTouchListener() {

				@Override
				public boolean onTouch(View v, MotionEvent event) {
					View window = getWindow(id);
					WindowTouchInfo touchInfo = ((WrappedTag) window.getTag()).touchInfo;
					// handle dragging to move
					boolean consumed = onTouchHandleResize(id, window,
							touchInfo, v, event);

					return consumed;
				}
			});
		}
	}

	/**
	 * Iterate through each View in the view hiearchy and implement StandOut
	 * specific compatibility workarounds.
	 * 
	 * <p>
	 * Currently, this method does the following:
	 * 
	 * <p>
	 * Fix {@link EditText} because they are not focusable and don't open the
	 * IME. To make EditText usable, hook up an onclick listener and start a new
	 * Activity for the sole purpose of getting user input.
	 * 
	 * <p>
	 * Fix {@link ListView} because they are not focusable and don't dispatch
	 * onItemClick events. To make ListView usable, write our own click
	 * detection code using an {@link OnTouchListener}.
	 * 
	 * @param root
	 *            The root view hierarchy to iterate through and check.<commit_msg>introduce flags to control behavior of additional functionality like resize handles<commit_after>package wei.mark.standout;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

/**
 * Extend this class to easily create and manage floating StandOut windows.
 * 
 * @author <NAME> <<EMAIL>>
 * 
 */
public abstract class StandOutWindow extends Service {
	private static final String TAG = "StandOutWindow";

	/**
	 * StandOut window id: You may use this sample id for your first window.
	 */
	public static final int DEFAULT_ID = 0;

	/**
	 * Special StandOut window id: You may NOT use this id for any windows.
	 */
	public static final int ONGOING_NOTIFICATION_ID = -1;

	/**
	 * StandOut window id: You may use this id when you want it to be
	 * disregarded. The system makes no distinction for this id; it is only used
	 * to improve code readability.
	 */
	public static final int DISREGARD_ID = -2;

	/**
	 * Intent action: Show a new window corresponding to the id.
	 */
	public static final String ACTION_SHOW = "SHOW";

	/**
	 * Intent action: Restore a previously hidden window corresponding to the
	 * id. The window should be previously hidden with {@link #ACTION_HIDE}.
	 */
	public static final String ACTION_RESTORE = "RESTORE";

	/**
	 * Intent action: Close an existing window with an existing id.
	 */
	public static final String ACTION_CLOSE = "CLOSE";

	/**
	 * Intent action: Close all existing windows.
	 */
	public static final String ACTION_CLOSE_ALL = "CLOSE_ALL";

	/**
	 * Intent action: Send data to a new or existing window.
	 */
	public static final String ACTION_SEND_DATA = "SEND_DATA";

	/**
	 * Intent action: Hide an existing window with an existing id. To enable the
	 * ability to restore this window, make sure you implement
	 * {@link #getHiddenNotification(int)}.
	 */
	public static final String ACTION_HIDE = "HIDE";

	/**
	 * Flags to be returned from {@link StandOutWindow#getFlags(int)}. This
	 * class was created to avoid polluting the flags namespace.
	 * 
	 * @author <NAME> <<EMAIL>>
	 * 
	 */
	public static class StandOutFlags {

		private static int flag_counter = 0;

		/**
		 * This default flag indicates that the window requires no window
		 * decorations (titlebar, hide/close buttons, resize handle, etc).
		 */
		public static final int FLAG_DECORATION_NONE = 0x00000000;

		/**
		 * Setting this flag indicates that the window wants the system provided
		 * window decorations (titlebar, hide/close buttons, resize handle,
		 * etc).
		 */
		public static final int FLAG_DECORATION_SYSTEM = 1 << flag_counter++;

		/**
		 * If {@link #FLAG_DECORATION_SYSTEM} is set, setting this flag
		 * indicates that the window decorator should NOT provide a close
		 * button.
		 */
		public static final int FLAG_DECORATION_CLOSE_DISABLE = 1 << flag_counter++;

		/**
		 * If {@link #FLAG_DECORATION_SYSTEM} is set, setting this flag
		 * indicates that the window decorator should NOT provide a resize
		 * handle.
		 */
		public static final int FLAG_DECORATION_RESIZE_DISABLE = 1 << flag_counter++;

		/**
		 * If {@link #FLAG_DECORATION_SYSTEM} is set, setting this flag
		 * indicates that the window decorator should NOT provide a resize
		 * handle.
		 */
		public static final int FLAG_DECORATION_MOVE_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the window can be moved by dragging
		 * the body.
		 * 
		 * <p>
		 * Note that if {@link #FLAG_DECORATION_SYSTEM} is set, the window can
		 * always be moved by dragging the titlebar.
		 */
		public static final int FLAG_BODY_MOVE_ENABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the window should be brought to the
		 * front upon user interaction.
		 * 
		 * <p>
		 * Note that if you set this flag, there is a noticeable flashing of the
		 * window during {@link MotionEvent#ACTION_UP}. This the hack that
		 * allows the system to bring the window to the front.
		 */
		public static final int FLAG_WINDOW_BRING_TO_FRONT_ON_TOUCH = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the window should be brought to the
		 * front upon user tap.
		 * 
		 * <p>
		 * Note that if you set this flag, there is a noticeable flashing of the
		 * window during {@link MotionEvent#ACTION_UP}. This the hack that
		 * allows the system to bring the window to the front.
		 */
		public static final int FLAG_WINDOW_BRING_TO_FRONT_ON_TAP = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should keep the window's
		 * position within the edges of the screen. If this flag is not set, the
		 * window will be able to be dragged off of the screen.
		 * 
		 * <p>
		 * If this flag is set, the window's {@link Gravity} is recommended to
		 * be {@link Gravity#TOP} | {@link Gravity#LEFT}. If the gravity is
		 * anything other than TOP|LEFT, then even though the window will be
		 * displayed within the edges, it will behave as if the user can drag it
		 * off the screen.
		 * 
		 */
		public static final int FLAG_WINDOW_EDGE_LIMITS_ENABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that windows are able to be hidden, that
		 * {@link #getHiddenIcon(int)}, {@link #getHiddenTitle(int)}, and
		 * {@link #getHiddenMessage(int)} are implemented, and that the system
		 * window decorator should provide a hide button if
		 * {@link #FLAG_DECORATION_SYSTEM} is set.
		 */
		public static final int FLAG_HIDE_ENABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable all
		 * compatibility workarounds. The default behavior is to run
		 * {@link StandOutWindow#fixCompatibility(View, int)} on the view
		 * returned by the implementation.
		 * 
		 * @see {@link StandOutWindow#fixCompatibility(View, int)}
		 */
		public static final int FLAG_FIX_COMPATIBILITY_ALL_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable EditText
		 * compatibility workarounds.
		 * 
		 * @see {@link StandOutWindow#fixCompatibility(View, int)}
		 * 
		 */
		public static final int FLAG_FIX_COMPATIBILITY_EDITTEXT_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable ListView
		 * compatibility workarounds.
		 * 
		 * @see {@link StandOutWindow#fixCompatibility(View, int)}
		 */
		public static final int FLAG_FIX_COMPATIBILITY_LISTVIEW_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable all
		 * additional functionality. The default behavior is to run
		 * {@link StandOutWindow#addFunctionality(View, int)} on the view
		 * returned by the implementation.
		 * 
		 * @see {@link StandOutWindow#addFunctionality(View, int)}
		 */
		public static final int FLAG_ADD_FUNCTIONALITY_ALL_DISABLE = 1 << flag_counter++;

		/**
		 * Setting this flag indicates that the system should disable adding the
		 * resize handle additional functionality.
		 * 
		 * @see {@link StandOutWindow#addFunctionality(View, int)}
		 */
		public static final int FLAG_ADD_FUNCTIONALITY_RESIZE_DISABLE = 1 << flag_counter++;
	}

	/**
	 * Show a new window corresponding to the id, or restore a previously hidden
	 * window.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that will be used
	 *            to create and manage the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * 
	 * @see #show(int)
	 */
	public static void show(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		context.startService(getShowIntent(context, cls, id));
	}

	/**
	 * Hide the existing window corresponding to the id. To enable the ability
	 * to restore this window, make sure you implement
	 * {@link #getHiddenNotification(int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. The window must previously be
	 *            shown.
	 * @see #hide(int)
	 */
	public static void hide(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		context.startService(getShowIntent(context, cls, id));
	}

	/**
	 * Close an existing window with an existing id.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. The window must previously be
	 *            shown.
	 * @see #close(int)
	 */
	public static void close(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		context.startService(getCloseIntent(context, cls, id));
	}

	/**
	 * Close all existing windows.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @see #closeAll()
	 */
	public static void closeAll(Context context,
			Class<? extends StandOutWindow> cls) {
		context.startService(getCloseAllIntent(context, cls));
	}

	/**
	 * Send {@link Parceleable} data in a {@link Bundle} to a new or existing
	 * windows. The implementation of the recipient window can handle what to do
	 * with the data. To receive a result, provide the class and id of the
	 * sender.
	 * 
	 * @param context
	 *            A Context of the application package implementing the class of
	 *            the sending window.
	 * @param toCls
	 *            The Service's class extending {@link StandOutWindow} that is
	 *            managing the receiving window.
	 * @param toId
	 *            The id of the receiving window.
	 * @param requestCode
	 *            Provide a request code to declare what kind of data is being
	 *            sent.
	 * @param data
	 *            A bundle of parceleable data to be sent to the receiving
	 *            window.
	 * @param fromCls
	 *            If the sending window wants a result, provide the class of the
	 *            sending window.
	 * @param fromId
	 *            If the sending window wants a result, provide the id of the
	 *            sending window.
	 * @see #sendData(int, Class, int, int, Bundle)
	 */
	public static void sendData(Context context,
			Class<? extends StandOutWindow> toCls, int toId, int requestCode,
			Bundle data, Class<? extends StandOutWindow> fromCls, int fromId) {
		context.startService(getSendDataIntent(context, toCls, toId,
				requestCode, data, fromCls, fromId));
	}

	/**
	 * See {@link #show(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that will be used
	 *            to create and manage the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getShowIntent(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		boolean cached = isCached(cls, id);
		String action = cached ? ACTION_RESTORE : ACTION_SHOW;
		Uri uri = cached ? Uri.parse("standout://" + cls + '/' + id) : null;
		return new Intent(context, cls).putExtra("id", id).setAction(action)
				.setData(uri);
	}

	/**
	 * See {@link #hide(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getHideIntent(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		return new Intent(context, cls).putExtra("id", id).setAction(
				ACTION_HIDE);
	}

	/**
	 * See {@link #close(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @param id
	 *            The id representing this window. If the id exists, and the
	 *            corresponding window was previously hidden, then that window
	 *            will be restored.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getCloseIntent(Context context,
			Class<? extends StandOutWindow> cls, int id) {
		return new Intent(context, cls).putExtra("id", id).setAction(
				ACTION_CLOSE);
	}

	/**
	 * See {@link #closeAll(Context, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing this class.
	 * @param cls
	 *            The Service extending {@link StandOutWindow} that is managing
	 *            the window.
	 * @return An {@link Intent} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getCloseAllIntent(Context context,
			Class<? extends StandOutWindow> cls) {
		return new Intent(context, cls).setAction(ACTION_CLOSE_ALL);
	}

	/**
	 * See {@link #sendData(Context, Class, int, int, Bundle, Class, int)}.
	 * 
	 * @param context
	 *            A Context of the application package implementing the class of
	 *            the sending window.
	 * @param toCls
	 *            The Service's class extending {@link StandOutWindow} that is
	 *            managing the receiving window.
	 * @param toId
	 *            The id of the receiving window.
	 * @param requestCode
	 *            Provide a request code to declare what kind of data is being
	 *            sent.
	 * @param data
	 *            A bundle of parceleable data to be sent to the receiving
	 *            window.
	 * @param fromCls
	 *            If the sending window wants a result, provide the class of the
	 *            sending window.
	 * @param fromId
	 *            If the sending window wants a result, provide the id of the
	 *            sending window.
	 * @return An {@link Intnet} to use with
	 *         {@link Context#startService(Intent)}.
	 */
	public static Intent getSendDataIntent(Context context,
			Class<? extends StandOutWindow> toCls, int toId, int requestCode,
			Bundle data, Class<? extends StandOutWindow> fromCls, int fromId) {
		return new Intent(context, toCls).putExtra("id", toId)
				.putExtra("requestCode", requestCode)
				.putExtra("wei.mark.standout.data", data)
				.putExtra("wei.mark.standout.fromCls", fromCls)
				.putExtra("fromId", fromId).setAction(ACTION_SEND_DATA);
	}

	// internal map of ids to shown/hidden views
	private static Map<Class<? extends StandOutWindow>, Map<Integer, View>> sViews;

	// static constructors
	static {
		sViews = new HashMap<Class<? extends StandOutWindow>, Map<Integer, View>>();
	}

	/**
	 * Returns whether the window corresponding to the class and id exists in
	 * the {@link #sViews} cache.
	 * 
	 * @param cls
	 *            Class corresponding to the window.
	 * @param id
	 *            The id representing the window.
	 * @return True if the window corresponding to the class and id exists in
	 *         the cache, or false if it does not exist.
	 */
	private static boolean isCached(Class<? extends StandOutWindow> cls, int id) {
		Map<Integer, View> l2 = sViews.get(cls);
		return l2 != null && l2.containsKey(id);
	}

	// internal system services
	private WindowManager mWindowManager;
	private NotificationManager mNotificationManager;
	private LayoutInflater mLayoutInflater;

	// internal state variables
	private boolean startedForeground;

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();

		mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
		mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		mLayoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		startedForeground = false;
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		super.onStartCommand(intent, flags, startId);

		// intent should be created with
		// getShowIntent(), getHideIntent(), getCloseIntent()
		if (intent != null) {
			String action = intent.getAction();
			int id = intent.getIntExtra("id", DEFAULT_ID);

			// this will interfere with getPersistentNotification()
			if (id == ONGOING_NOTIFICATION_ID) {
				throw new RuntimeException(
						"ID cannot equals StandOutWindow.ONGOING_NOTIFICATION_ID");
			}

			if (ACTION_SHOW.equals(action) || ACTION_RESTORE.equals(action)) {
				show(id);
			} else if (ACTION_HIDE.equals(action)) {
				hide(id);
			} else if (ACTION_CLOSE.equals(action)) {
				close(id);
			} else if (ACTION_CLOSE_ALL.equals(action)) {
				closeAll();
			} else if (ACTION_SEND_DATA.equals(action)) {
				Bundle data = intent.getBundleExtra("wei.mark.standout.data");
				int requestCode = intent.getIntExtra("requestCode", 0);
				@SuppressWarnings("unchecked")
				Class<? extends StandOutWindow> fromCls = (Class<? extends StandOutWindow>) intent
						.getSerializableExtra("wei.mark.standout.fromCls");
				int fromId = intent.getIntExtra("fromId", DEFAULT_ID);

				onReceiveData(id, requestCode, data, fromCls, fromId);
			}
		} else {
			Log.w(TAG, "Tried to onStartCommand() with a null intent");
		}

		// the service is started in foreground in show()
		// so we don't expect Android to kill this service
		return START_NOT_STICKY;
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		// closes all windows
		closeAll();
	}

	/**
	 * Return the name of every window in this implementation. The name will
	 * appear in the default implementations of the system window decoration
	 * title and notification titles.
	 * 
	 * @return The name.
	 */
	protected abstract String getAppName();

	/**
	 * Return the icon resource for every window in this implementation. The
	 * icon will appear in the default implementations of the system window
	 * decoration and notifications.
	 * 
	 * @return The icon.
	 */
	protected abstract int getAppIcon();

	/**
	 * Create a new {@link View} corresponding to the id, and add it as a child
	 * to the root. The view will become the contents of this StandOut window.
	 * The view MUST be newly created, and you MUST attach it to root.
	 * 
	 * <p>
	 * If you are inflating your view from XML, make sure you use
	 * {@link LayoutInflater#inflate(int, ViewGroup, boolean)} to attach your
	 * view to root. Set the ViewGroup to be root, and the boolean to true.
	 * 
	 * <p>
	 * If you are creating your view programmatically, make sure you use
	 * {@link ViewGroup#addView(View)} to add your view to root.
	 * 
	 * @param id
	 *            The id representing the window.
	 * @param root
	 *            The {@link ViewGroup} to attach your view as a child to.
	 * @return A new {@link View} corresponding to the id. The view will be the
	 *         content of this StandOut window. The view MUST be newly created.
	 */
	protected abstract View createAndAttachView(int id, ViewGroup root);

	/**
	 * Return the {@link StandOutWindow#LayoutParams} for the corresponding id.
	 * The system will set the layout params on the view for this StandOut
	 * window. The layout params may be reused.
	 * 
	 * 
	 * @param id
	 *            The id of the window.
	 * @param view
	 *            The view corresponding to the id. Given as courtesy, so you
	 *            may get the existing layout params.
	 * @return The {@link StandOutWindow#LayoutParams} corresponding to the id.
	 *         The layout params will be set on the view. The layout params may
	 *         be reused.
	 */
	protected abstract StandOutWindow.LayoutParams getParams(int id, View view);

	/**
	 * Implement this method to change modify the behavior and appearance of the
	 * window corresponding to the id.
	 * 
	 * <p>
	 * You may use any of the flags defined in {@link StandOutWindow} such as
	 * {@link #FLAG_DECORATION_NONE}.
	 * 
	 * <p>
	 * Use bitwise OR (|) to set flags, and bitwise XOR (^) to unset flags. To
	 * test if a flag is set, use (getFlags(id) & flag) != 0.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return Bitwise OR'd flags
	 */
	protected int getFlags(int id) {
		return StandOutFlags.FLAG_DECORATION_NONE;
	}

	/**
	 * Return the title for the persistent notification. This is called every
	 * time {@link #show(int)} is called.
	 * 
	 * @param id
	 *            The id of the window shown.
	 * @return The title for the persistent notification.
	 */
	protected String getPersistentNotificationTitle(int id) {
		return getAppName() + " Running";
	}

	/**
	 * Return the message for the persistent notification. This is called every
	 * time {@link #show(int)} is called.
	 * 
	 * @param id
	 *            The id of the window shown.
	 * @return The message for the persistent notification.
	 */
	protected String getPersistentNotificationMessage(int id) {
		return "";
	}

	/**
	 * Return the intent for the persistent notification. This is called every
	 * time {@link #show(int)} is called.
	 * 
	 * <p>
	 * The returned intent will be packaged into a {@link PendingIntent} to be
	 * invoked when the user clicks the notification.
	 * 
	 * @param id
	 *            The id of the window shown.
	 * @return The intent for the persistent notification.
	 */
	protected Intent getPersistentNotificationIntent(int id) {
		return null;
	}

	/**
	 * Return the icon resource for every hidden window in this implementation.
	 * The icon will appear in the default implementations of the hidden
	 * notifications.
	 * 
	 * @return The icon.
	 */
	protected int getHiddenIcon() {
		return getAppIcon();
	}

	/**
	 * Return the title for the hidden notification corresponding to the window
	 * being hidden.
	 * 
	 * @param id
	 *            The id of the hidden window.
	 * @return The title for the hidden notification.
	 */
	protected String getHiddenNotificationTitle(int id) {
		return getAppName() + " Hidden";
	}

	/**
	 * Return the message for the hidden notification corresponding to the
	 * window being hidden.
	 * 
	 * @param id
	 *            The id of the hidden window.
	 * @return The message for the hidden notification.
	 */
	protected String getHiddenNotificationMessage(int id) {
		return "";
	}

	/**
	 * Return the intent for the hidden notification corresponding to the window
	 * being hidden.
	 * 
	 * <p>
	 * The returned intent will be packaged into a {@link PendingIntent} to be
	 * invoked when the user clicks the notification.
	 * 
	 * @param id
	 *            The id of the hidden window.
	 * @return The intent for the hidden notification.
	 */
	protected Intent getHiddenNotificationIntent(int id) {
		return null;
	}

	/**
	 * Return a persistent {@link Notification} for the corresponding id. You
	 * must return a notification for AT LEAST the first id to be requested.
	 * Once the persistent notification is shown, further calls to
	 * {@link #getPersistentNotification(int)} may return null. This way Android
	 * can start the StandOut window service in the foreground and will not kill
	 * the service on low memory.
	 * 
	 * <p>
	 * As a courtesy, the system will request a notification for every new id
	 * shown. Your implementation is encouraged to include the
	 * {@link PendingIntent#FLAG_UPDATE_CURRENT} flag in the notification so
	 * that there is only one system-wide persistent notification.
	 * 
	 * <p>
	 * See the StandOutExample project for an implementation of
	 * {@link #getPersistentNotification(int)} that keeps one system-wide
	 * persistent notification that creates a new window on every click.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The {@link Notification} corresponding to the id, or null if
	 *         you've previously returned a notification.
	 */
	protected Notification getPersistentNotification(int id) {
		// basic notification stuff
		// http://developer.android.com/guide/topics/ui/notifiers/notifications.html
		int icon = getAppIcon();
		long when = System.currentTimeMillis();
		Context c = getApplicationContext();
		String contentTitle = getPersistentNotificationTitle(id);
		String contentText = getPersistentNotificationMessage(id);
		String tickerText = String.format("%s: %s", contentTitle, contentText);

		// getPersistentNotification() is called for every new window
		// so we replace the old notification with a new one that has
		// a bigger id
		Intent notificationIntent = getPersistentNotificationIntent(id);

		PendingIntent contentIntent = null;

		if (notificationIntent != null) {
			contentIntent = PendingIntent.getService(this, 0,
					notificationIntent,
					// flag updates existing persistent notification
					PendingIntent.FLAG_UPDATE_CURRENT);
		}

		Notification notification = new Notification(icon, tickerText, when);
		notification.setLatestEventInfo(c, contentTitle, contentText,
				contentIntent);
		return notification;
	}

	/**
	 * Return a hidden {@link Notification} for the corresponding id. The system
	 * will request a notification for every id that is hidden.
	 * 
	 * <p>
	 * If null is returned, StandOut will assume you do not wish to support
	 * hiding this window, and will {@link #close(int)} it for you.
	 * 
	 * <p>
	 * See the StandOutExample project for an implementation of
	 * {@link #getHiddenNotification(int)} that for every hidden window keeps a
	 * notification which restores that window upon user's click.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The {@link Notification} corresponding to the id or null.
	 */
	protected Notification getHiddenNotification(int id) {
		// same basics as getPersistentNotification()
		int icon = getHiddenIcon();
		long when = System.currentTimeMillis();
		Context c = getApplicationContext();
		String contentTitle = getHiddenNotificationTitle(id);
		String contentText = getHiddenNotificationMessage(id);
		String tickerText = String.format("%s: %s", contentTitle, contentText);

		// the difference here is we are providing the same id
		Intent notificationIntent = getHiddenNotificationIntent(id);

		PendingIntent contentIntent = null;

		if (notificationIntent != null) {
			contentIntent = PendingIntent.getService(this, 0,
					notificationIntent,
					// flag updates existing persistent notification
					PendingIntent.FLAG_UPDATE_CURRENT);
		}

		Notification notification = new Notification(icon, tickerText, when);
		notification.setLatestEventInfo(c, contentTitle, contentText,
				contentIntent);
		return notification;
	}

	/**
	 * Return the animation to play when the window corresponding to the id is
	 * shown.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The animation to play or null.
	 */
	protected Animation getShowAnimation(int id) {
		return AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
	}

	/**
	 * Return the animation to play when the window corresponding to the id is
	 * hidden.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The animation to play or null.
	 */
	protected Animation getHideAnimation(int id) {
		return AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
	}

	/**
	 * Return the animation to play when the window corresponding to the id is
	 * closed.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The animation to play or null.
	 */
	protected Animation getCloseAnimation(int id) {
		return AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
	}

	/**
	 * Implement this method to be alerted to touch events in the body of the
	 * window corresponding to the id.
	 * 
	 * <p>
	 * Note that even if you set {@link #FLAG_DECORATION_SYSTEM}, you will not
	 * receive touch events from the system window decorations.
	 * 
	 * @see {@link View.OnTouchListener#onTouch(View, MotionEvent)}
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param window
	 *            The window corresponding to the id, provided as a courtesy.
	 * @param touchInfo
	 *            The touch information of the window, provided as a courtesy.
	 * @param view
	 *            The view where the event originated from.
	 * @param event
	 *            See linked method.
	 */
	protected boolean onTouchBody(int id, View window,
			WindowTouchInfo touchInfo, View view, MotionEvent event) {
		return false;
	}

	/**
	 * Implement this method to be alerted to when the window corresponding to
	 * the id is moved.
	 * 
	 * @see {@link View.OnTouchListener#onTouch(View, MotionEvent)}
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param window
	 *            The window corresponding to the id, provided as a courtesy.
	 * @param touchInfo
	 *            The touch information of the window, provided as a courtesy.
	 * @param view
	 *            The view where the event originated from.
	 * @param event
	 *            See linked method.
	 */
	protected void onMove(int id, View window, WindowTouchInfo touchInfo,
			View view, MotionEvent event) {
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be shown. This callback will occur before the view is
	 * added to the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be shown.
	 * @return Return true to cancel the view from being shown, or false to
	 *         continue.
	 * @see #show(int)
	 */
	protected boolean onShow(int id, View window) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be hidden. This callback will occur before the view is
	 * removed from the window manager and {@link #getHiddenNotification(int)}
	 * is called.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be hidden.
	 * @return Return true to cancel the view from being hidden, or false to
	 *         continue.
	 * @see #hide(int)
	 */
	protected boolean onHide(int id, View window) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be closed. This callback will occur before the view is
	 * removed from the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be closed.
	 * @return Return true to cancel the view from being closed, or false to
	 *         continue.
	 * @see #close(int)
	 */
	protected boolean onClose(int id, View window) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when all windows are about to be
	 * closed. This callback will occur before any views are removed from the
	 * window manager.
	 * 
	 * @return Return true to cancel the views from being closed, or false to
	 *         continue.
	 * @see #closeAll()
	 */
	protected boolean onCloseAll() {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id has received some data. The sender is described by fromCls and fromId
	 * if the sender wants a result. To send a result, use
	 * {@link #sendData(int, Class, int, int, Bundle)}.
	 * 
	 * @param id
	 *            The id of your receiving window.
	 * @param requestCode
	 *            The sending window provided this request code to declare what
	 *            kind of data is being sent.
	 * @param data
	 *            A bundle of parceleable data that was sent to your receiving
	 *            window.
	 * @param fromCls
	 *            The sending window's class. Provided if the sender wants a
	 *            result.
	 * @param fromId
	 *            The sending window's id. Provided if the sender wants a
	 *            result.
	 */
	protected void onReceiveData(int id, int requestCode, Bundle data,
			Class<? extends StandOutWindow> fromCls, int fromId) {
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be updated in the layout. This callback will occur before
	 * the view is updated by the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be closed.
	 * @param params
	 *            The updated layout params.
	 * @return Return true to cancel the view from being updated, or false to
	 *         continue.
	 * @see #updateViewLayout(int, View, LayoutParams)
	 */
	protected boolean onUpdate(int id, View window,
			StandOutWindow.LayoutParams params) {
		return false;
	}

	/**
	 * Implement this callback to be alerted when a window corresponding to the
	 * id is about to be bought to the front. This callback will occur before
	 * the view is brought to the front by the window manager.
	 * 
	 * @param id
	 *            The id of the view, provided as a courtesy.
	 * @param view
	 *            The view about to be brought to the front.
	 * @return Return true to cancel the view from being brought to the front,
	 *         or false to continue.
	 * @see #bringToFront(int)
	 */
	protected boolean onBringToFront(int id, View window) {
		return false;
	}

	/**
	 * Show or restore a window corresponding to the id. Return the window that
	 * was shown/restored.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The window shown.
	 */
	protected final synchronized View show(int id) {
		// get the view corresponding to the id
		View window = getWrappedView(id);

		if (window == null) {
			Log.w(TAG, "Tried to show(" + id + ") a null view");
			return null;
		}

		// alert callbacks and cancel if instructed
		if (onShow(id, window))
			return null;

		// get animation
		Animation animation = getShowAnimation(id);

		WrappedTag tag = (WrappedTag) window.getTag();
		tag.shown = true;

		// get the params corresponding to the id
		StandOutWindow.LayoutParams params = (LayoutParams) window
				.getLayoutParams();
		if (params == null) {
			params = getParams(id, window);
		}

		try {
			// add the view to the window manager
			mWindowManager.addView(window, params);

			// animate
			if (animation != null) {
				((ViewGroup) window).getChildAt(0).startAnimation(animation);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		// add view to internal map
		putCache(id, window);

		// get the persistent notification
		Notification notification = getPersistentNotification(id);

		// show the notification
		if (notification != null) {
			notification.flags = notification.flags
					| Notification.FLAG_NO_CLEAR;

			// only show notification if not shown before
			if (!startedForeground) {
				// tell Android system to show notification
				startForeground(
						getClass().hashCode() + ONGOING_NOTIFICATION_ID,
						notification);
				startedForeground = true;
			} else {
				// update notification if shown before
				mNotificationManager.notify(getClass().hashCode()
						+ ONGOING_NOTIFICATION_ID, notification);
			}
		} else {
			// notification can only be null if it was provided before
			if (!startedForeground) {
				throw new RuntimeException("Your StandOutWindow service must"
						+ "provide a persistent notification."
						+ "The notification prevents Android"
						+ "from killing your service in low"
						+ "memory situations.");
			}
		}

		return window;
	}

	/**
	 * Hide a window corresponding to the id. Show a notification for the hidden
	 * window.
	 * 
	 * @param id
	 *            The id of the window.
	 */
	protected final synchronized void hide(int id) {
		int flags = getFlags(id);

		// check if hide enabled
		if ((flags & StandOutFlags.FLAG_HIDE_ENABLE) != 0) {
			// get the hidden notification for this view
			Notification notification = getHiddenNotification(id);

			// get the view corresponding to the id
			final View window = getWrappedView(id);

			if (window == null) {
				Log.w(TAG, "Tried to hide(" + id + ") a null view");
				return;
			}

			// alert callbacks and cancel if instructed
			if (onHide(id, window))
				return;

			// get animation
			Animation animation = getHideAnimation(id);

			WrappedTag tag = (WrappedTag) window.getTag();
			tag.shown = false;

			try {
				// animate
				if (animation != null) {
					animation.setAnimationListener(new AnimationListener() {

						@Override
						public void onAnimationStart(Animation animation) {
						}

						@Override
						public void onAnimationRepeat(Animation animation) {
						}

						@Override
						public void onAnimationEnd(Animation animation) {
							// remove the view from the window manager
							mWindowManager.removeView(window);
						}
					});
					((ViewGroup) window).getChildAt(0)
							.startAnimation(animation);
				} else {
					// remove the view from the window manager
					mWindowManager.removeView(window);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			// display the notification
			notification.flags = notification.flags
					| Notification.FLAG_NO_CLEAR
					| Notification.FLAG_AUTO_CANCEL;

			mNotificationManager.notify(getClass().hashCode() + id,
					notification);
		} else {
			// if hide not enabled, close window
			close(id);
		}
	}

	/**
	 * Close a window corresponding to the id.
	 * 
	 * @param id
	 *            The id of the window.
	 */
	protected final synchronized void close(int id) {
		// get the view corresponding to the id
		final View window = getWrappedView(id);

		if (window == null) {
			Log.w(TAG, "Tried to close(" + id + ") a null view");
			return;
		}

		// alert callbacks and cancel if instructed
		if (onClose(id, window))
			return;

		// remove view from internal map
		removeCache(id);

		// get animation
		Animation animation = getCloseAnimation(id);

		WrappedTag tag = (WrappedTag) window.getTag();

		if (tag.shown) {
			try {
				// animate
				if (animation != null) {
					animation.setAnimationListener(new AnimationListener() {

						@Override
						public void onAnimationStart(Animation animation) {
						}

						@Override
						public void onAnimationRepeat(Animation animation) {
						}

						@Override
						public void onAnimationEnd(Animation animation) {
							// remove the view from the window manager
							mWindowManager.removeView(window);
						}
					});
					((ViewGroup) window).getChildAt(0)
							.startAnimation(animation);
				} else {
					// remove the view from the window manager
					mWindowManager.removeView(window);
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		} else {
			tag.shown = false;
			// cancel hidden notification
			mNotificationManager.cancel(id);
		}

		// if we just released the last view, quit
		if (getCacheSize() == 0) {
			// tell Android to remove the persistent notification
			// the Service will be shutdown by the system on low memory
			startedForeground = false;
			stopForeground(true);
		}
	}

	/**
	 * Close all existing windows.
	 */
	protected final synchronized void closeAll() {
		// alert callbacks and cancel if instructed
		if (onCloseAll())
			return;

		// add ids to temporary set to avoid concurrent modification
		LinkedList<Integer> ids = new LinkedList<Integer>();
		for (int id : getCacheIds()) {
			ids.add(id);
		}

		// close each window
		for (int id : ids) {
			close(id);
		}
	}

	/**
	 * Send {@link Parceleable} data in a {@link Bundle} to a new or existing
	 * windows. The implementation of the recipient window can handle what to do
	 * with the data. To receive a result, provide the id of the sender.
	 * 
	 * @param fromId
	 *            If your window wants a result, provide the id of the sending
	 *            window.
	 * @param toCls
	 *            The Service's class extending {@link StandOutWindow} that is
	 *            managing the receiving window.
	 * @param toId
	 *            The id of the receiving window.
	 * @param requestCode
	 *            Provide a request code to declare what kind of data is being
	 *            sent.
	 * @param data
	 *            A bundle of parceleable data to be sent to the receiving
	 *            window.
	 */
	protected final void sendData(int fromId,
			Class<? extends StandOutWindow> toCls, int toId, int requestCode,
			Bundle data) {
		StandOutWindow.sendData(this, toCls, toId, requestCode, data,
				getClass(), fromId);
	}

	/**
	 * Update the window corresponding to this view with the given params.
	 * 
	 * @param window
	 *            The window to update.
	 * @param params
	 *            The updated layout params to apply.
	 */
	protected final void updateViewLayout(int id, View window,
			StandOutWindow.LayoutParams params) {
		// alert callbacks and cancel if instructed
		if (onUpdate(id, window, params))
			return;

		if (window == null) {
			Log.w(TAG, "Tried to updateViewLayout() a null window");
			return;
		}

		try {
			mWindowManager.updateViewLayout(window, params);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Bring the window corresponding to this id in front of all other windows.
	 * The window may flicker as it is removed and restored by the system.
	 * 
	 * @param id
	 *            The id of the window to bring to the front.
	 */
	protected final synchronized void bringToFront(int id) {
		View window = getWrappedView(id);
		if (window == null) {
			Log.w(TAG, "Tried to bringToFront() a null view");
			return;
		}

		// alert callbacks and cancel if instructed
		if (onBringToFront(id, window))
			return;

		StandOutWindow.LayoutParams params = (LayoutParams) window
				.getLayoutParams();
		if (params == null) {
			params = getParams(id, window);
		}

		// remove from window manager then add back
		try {
			mWindowManager.removeView(window);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		try {
			mWindowManager.addView(window, params);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Gets a unique id to assign to a new window.
	 * 
	 * @return The unique id.
	 */
	protected final int getUniqueId() {
		Map<Integer, View> l2 = sViews.get(getClass());
		if (l2 == null) {
			l2 = new HashMap<Integer, View>();
			sViews.put(getClass(), l2);
		}

		int unique = DEFAULT_ID;
		for (int id : l2.keySet()) {
			unique = Math.max(unique, id + 1);
		}
		return unique;
	}

	/**
	 * Return whether the window corresponding to the id exists. This is useful
	 * for testing if the id is being restored (return true) or shown for the
	 * first time (return false).
	 * 
	 * @param id
	 *            The id of the window.
	 * @return True if the window corresponding to the id is either shown or
	 *         hidden, or false if it has never been shown or was previously
	 *         closed.
	 */
	protected final boolean isExistingId(int id) {
		return isCached(getClass(), id);
	}

	/**
	 * Return the window corresponding to the id, if it exists in cache. The
	 * window will not be created with
	 * {@link #createAndAttachView(int, ViewGroup)}. This means the returned
	 * value will be null if the window is not shown/hidden.
	 * 
	 * @param id
	 *            The id of the window.
	 * @return The window if it is shown/hidden, or null if it is closed.
	 */
	protected final View getWindow(int id) {
		return getCache(id);
	}

	/**
	 * Wraps the view from getView() into a frame that is easier to manage. The
	 * frame allows us to pass touch input to implementations and set a
	 * {@link WrappedTag} to keep track of the id, visibility, etc.
	 * 
	 * @param id
	 *            The id representing the wrapped view.
	 * @return The wrapped view from cache, or created from the implementation.
	 */
	private View getWrappedView(final int id) {
		// try get the wrapped view from the internal map
		View cachedView = getCache(id);

		// if the wrapped view exists, then return it rather than creating one
		if (cachedView != null) {
			return cachedView;
		}

		// create the wrapping frame and body
		final FrameLayout window = new FrameLayout(this);
		View content;
		FrameLayout body;

		int flags = getFlags(id);

		if ((flags & StandOutFlags.FLAG_DECORATION_SYSTEM) != 0) {
			// requested system window decorations
			content = getSystemWindowContent(id);
			body = (FrameLayout) content.findViewById(R.id.body);
		} else {
			// did not request decorations. will provide own implementation
			content = new FrameLayout(this);
			body = (FrameLayout) content;
		}

		window.addView(content);
		content.setTag(window);

		// body should always send touch events to onTouchBody()
		final boolean bodyMoveEnabled = (flags & StandOutFlags.FLAG_BODY_MOVE_ENABLE) != 0;
		body.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				// pass all touch events to the implementation
				WrappedTag tag = (WrappedTag) window.getTag();
				WindowTouchInfo touchInfo = tag.touchInfo;

				boolean consumed = false;

				consumed = onTouchWindow(id, window, touchInfo, v, event)
						|| consumed;

				// if set FLAG_BODY_MOVE_ENABLE, move the window
				if (bodyMoveEnabled) {
					consumed = onTouchHandleMove(id, window, touchInfo, v,
							event) || consumed;
				}

				consumed = onTouchBody(id, window, touchInfo, v, event)
						|| consumed;

				return consumed;
			}
		});

		// attach the view corresponding to the id from the implementation
		View view = createAndAttachView(id, body);

		// make sure the implemention created a view
		if (view == null) {
			throw new RuntimeException(
					"Your view must not be null in createAndAttachView()");
		}
		// make sure the implementation attached the view
		if (view != body && view.getParent() == null) {
			throw new RuntimeException(
					"You must attach your view to the given root ViewGroup in createAndAttachView()");
		}

		// implement StandOut specific workarounds
		if ((flags & StandOutFlags.FLAG_FIX_COMPATIBILITY_ALL_DISABLE) == 0) {
			fixCompatibility(view, id);
		}
		// implement StandOut specific additional functionality
		if ((flags & StandOutFlags.FLAG_ADD_FUNCTIONALITY_ALL_DISABLE) == 0) {
			addFunctionality(view, id);
		}

		// wrap the existing tag and attach it to the frame
		window.setTag(new WrappedTag(id, false, view.getTag()));

		window.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
					case MotionEvent.ACTION_OUTSIDE:
						WrappedTag tag = (WrappedTag) window.getTag();
						WindowTouchInfo touchInfo = tag.touchInfo;
						onTouchBody(id, window, touchInfo, v, event);
						return true;
				}
				return false;
			}
		});

		return window;
	}

	/**
	 * Implement StandOut specific additional functionalities.
	 * 
	 * <p>
	 * Currently, this method does the following:
	 * 
	 * <p>
	 * Attach resize handles: For every View found to have id R.id.corner,
	 * attach an OnTouchListener that implements resizing the window.
	 * 
	 * @param view
	 *            The view hierarchy to implement additional functionality.
	 * @param id
	 *            The id of the window.
	 */
	private void addFunctionality(View view, final int id) {
		int flags = getFlags(id);

		if ((flags & StandOutFlags.FLAG_ADD_FUNCTIONALITY_RESIZE_DISABLE) == 0) {
			View corner = view.findViewById(R.id.corner);
			if (corner != null) {
				corner.setOnTouchListener(new OnTouchListener() {

					@Override
					public boolean onTouch(View v, MotionEvent event) {
						View window = getWindow(id);
						WindowTouchInfo touchInfo = ((WrappedTag) window
								.getTag()).touchInfo;
						// handle dragging to move
						boolean consumed = onTouchHandleResize(id, window,
								touchInfo, v, event);

						return consumed;
					}
				});
			}
		}
	}

	/**
	 * Iterate through each View in the view hiearchy and implement StandOut
	 * specific compatibility workarounds.
	 * 
	 * <p>
	 * Currently, this method does the following:
	 * 
	 * <p>
	 * Fix {@link EditText} because they are not focusable and don't open the
	 * IME. To make EditText usable, hook up an onclick listener and start a new
	 * Activity for the sole purpose of getting user input.
	 * 
	 * <p>
	 * Fix {@link ListView} because they are not focusable and don't dispatch
	 * onItemClick events. To make ListView usable, write our own click
	 * detection code using an {@link OnTouchListener}.
	 * 
	 * @param root
	 *            The root view hierarchy to iterate through and check.
{'fraction_non_alphanumeric': 0.08765415993497436, 'fraction_numerical': 0.0009633427995143146, 'mean_word_length': 2.9364038552693947, 'pattern_counts': {'":': 0, '<': 163, '<?xml version=': 0, '>': 111, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5037932', 'n_tokens_mistral': 30914, 'n_tokens_neox': 26591, 'n_words': 10896}