package wei.mark.standout;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.actionbarsherlock.R;
import wei.mark.standout.constants.StandOutFlags;
import wei.mark.standout.ui.Window;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
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.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
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.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
/**
* Extend this class to easily create and manage floating StandOut windows.
*
* @author Mark Wei <markwei@gmail.com>
*
* Contributors: Jason <github.com/jasonconnery>
*
*/
public abstract class StandOutWindow extends Service {
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";
/**
* 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));
}
/**
* This allows windows of different applications to communicate with each
* other.
*
* <p>
* 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, or DISREGARD_ID.
* @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
* Provide the class of the sending window if you want a result.
* @param fromId
* Provide the id of the sending window if you want a result.
* @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 = sWindowCache.isCached(id, cls);
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
static WindowCache sWindowCache;
static Window sFocusedWindow;
// static constructors
static {
sWindowCache = new WindowCache();
sFocusedWindow = null;
}
// internal system services
WindowManager mWindowManager;
private NotificationManager mNotificationManager;
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)) {
if (!isExistingId(id) && id != DISREGARD_ID) {
Log.w(TAG,
"Sending data to non-existant window. If this is not intended, make sure toId is either an existing window's id or DISREGARD_ID.");
}
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.
*/
public 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.
*/
public abstract int getAppIcon();
/**
* Create a new {@link View} corresponding to the id, and add it as a child
* to the frame. The view will become the contents of this StandOut window.
* The view MUST be newly created, and you MUST attach it to the frame.
*
* <p>
* If you are inflating your view from XML, make sure you use
* {@link LayoutInflater#inflate(int, ViewGroup, boolean)} to attach your
* view to frame. Set the ViewGroup to be frame, and the boolean to true.
*
* <p>
* If you are creating your view programmatically, make sure you use
* {@link FrameLayout#addView(View)} to add your view to the frame.
*
* @param id
* The id representing the window.
* @param frame
* The {@link FrameLayout} to attach your view as a child to.
*/
public abstract void createAndAttachView(int id, FrameLayout frame);
/**
* 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 window
* The window 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 window. The layout params
* returned will be reused whenever possible, minimizing the number
* of times getParams() will be called.
*/
public abstract StandOutLayoutParams getParams(int id, Window window);
/**
* 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 StandOutFlags}. This
* method will be called many times, so keep it fast.
*
* <p>
* Use bitwise OR (|) to set flags, and bitwise XOR (^) to unset flags. To
* test if a flag is set, use {@link Utils#isSet(int, int)}.
*
* @param id
* The id of the window.
* @return A combination of flags.
*/
public int getFlags(int id) {
return 0;
}
/**
* Implement this method to set a custom title for the window corresponding
* to the id.
*
* @param id
* The id of the window.
* @return The title of the window.
*/
public String getTitle(int id) {
return getAppName();
}
/**
* Implement this method to set a custom icon for the window corresponding
* to the id.
*
* @param id
* The id of the window.
* @return The icon of the window.
*/
public int getIcon(int id) {
return getAppIcon();
}
/**
* 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
@SuppressWarnings("deprecation")
public 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.
*/
@SuppressWarnings("deprecation")
public 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.
*/
public 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.
*/
public 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.
*/
public Animation getCloseAnimation(int id) {
return AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
}
/**
* Implement this method to set a custom theme for all windows in this
* implementation.
*
* @return The theme to set on the window, or 0 for device default.
*/
public int getThemeStyle() {
return 0;
}
/**
* You probably want to leave this method alone and implement
* {@link #getDropDownItems(int)} instead. Only implement this method if you
* want more control over the drop down menu.
*
* <p>
* Implement this method to set a custom drop down menu when the user clicks
* on the icon of the window corresponding to the id. The icon is only shown
* when {@link StandOutFlags#FLAG_DECORATION_SYSTEM} is set.
*
* @param id
* The id of the window.
* @return The drop down menu to be anchored to the icon, or null to have no
* dropdown menu.
*/
public PopupWindow getDropDown(final int id) {
final List<DropDownListItem> items;
List<DropDownListItem> dropDownListItems = getDropDownItems(id);
if (dropDownListItems != null) {
items = dropDownListItems;
} else {
items = new ArrayList<StandOutWindow.DropDownListItem>();
}
// add default drop down items
items.add(new DropDownListItem(
android.R.drawable.ic_menu_close_clear_cancel, "Quit "
+ getAppName(), new Runnable() {
@Override
public void run() {
closeAll();
}
}));
// turn item list into views in PopupWindow
LinearLayout list = new LinearLayout(this);
list.setOrientation(LinearLayout.VERTICAL);
final PopupWindow dropDown = new PopupWindow(list,
StandOutLayoutParams.WRAP_CONTENT,
StandOutLayoutParams.WRAP_CONTENT, true);
for (final DropDownListItem item : items) {
ViewGroup listItem = (ViewGroup) mLayoutInflater.inflate(
R.layout.so_drop_down_list_item, null);
list.addView(listItem);
ImageView icon = (ImageView) listItem.findViewById(R.id.icon);
icon.setImageResource(item.icon);
TextView description = (TextView) listItem
.findViewById(R.id.description);
description.setText(item.description);
listItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
item.action.run();
dropDown.dismiss();
}
});
}
Drawable background = getResources().getDrawable(
android.R.drawable.editbox_dropdown_dark_frame);
dropDown.setBackgroundDrawable(background);
return dropDown;
}
/**
* Implement this method to populate the drop down menu when the user clicks
* on the icon of the window corresponding to the id. The icon is only shown
* when {@link StandOutFlags#FLAG_DECORATION_SYSTEM} is set.
*
* @param id
* The id of the window.
* @return The list of items to show in the drop down menu, or null or empty
* to have no dropdown menu.
*/
public List<DropDownListItem> getDropDownItems(int id) {
return null;
}
/**
* 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 view
* The view where the event originated from.
* @param event
* See linked method.
*/
public boolean onTouchBody(int id, Window window, View view,
MotionEvent event) {
return false;
}
/**
* Implement this method to be alerted to when the window corresponding to
* the id is moved.
*
* @param id
* The id of the view, provided as a courtesy.
* @param window
* The window corresponding to the id, provided as a courtesy.
* @param view
* The view where the event originated from.
* @param event
* See linked method.
* @see {@link #onTouchHandleMove(int, Window, View, MotionEvent)}
*/
public void onMove(int id, Window window, View view, MotionEvent event) {
}
/**
* Implement this method to be alerted to when the window corresponding to
* the id is resized.
*
* @param id
* The id of the view, provided as a courtesy.
* @param window
* The window corresponding to the id, provided as a courtesy.
* @param view
* The view where the event originated from.
* @param event
* See linked method.
* @see {@link #onTouchHandleResize(int, Window, View, MotionEvent)}
*/
public void onResize(int id, Window window, 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)
*/
public boolean onShow(int id, Window 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)
*/
public boolean onHide(int id, Window 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)
*/
public boolean onClose(int id, Window 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()
*/
public 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.
*/
public 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 window, provided as a courtesy.
* @param view
* The window about to be updated.
* @param params
* The updated layout params.
* @return Return true to cancel the window from being updated, or false to
* continue.
* @see #updateViewLayout(int, Window, StandOutLayoutParams)
*/
public boolean onUpdate(int id, Window window, StandOutLayoutParams 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 window is brought to the front by the window manager.
*
* @param id
* The id of the window, provided as a courtesy.
* @param view
* The window about to be brought to the front.
* @return Return true to cancel the window from being brought to the front,
* or false to continue.
* @see #bringToFront(int)
*/
public boolean onBringToFront(int id, Window window) {
return false;
}
/**
* Implement this callback to be alerted when a window corresponding to the
* id is about to have its focus changed. This callback will occur before
* the window's focus is changed.
*
* @param id
* The id of the window, provided as a courtesy.
* @param view
* The window about to be brought to the front.
* @param focus
* Whether the window is gaining or losing focus.
* @return Return true to cancel the window's focus from being changed, or
* false to continue.
* @see #focus(int)
*/
public boolean onFocusChange(int id, Window window, boolean focus) {
return false;
}
/**
* Implement this callback to be alerted when a window corresponding to the
* id receives a key event. This callback will occur before the window
* handles the event with {@link Window#dispatchKeyEvent(KeyEvent)}.
*
* @param id
* The id of the window, provided as a courtesy.
* @param view
* The window about to receive the key event.
* @param event
* The key event.
* @return Return true to cancel the window from handling the key event, or
* false to let the window handle the key event.
* @see {@link Window#dispatchKeyEvent(KeyEvent)}
*/
public boolean onKeyEvent(int id, Window window, KeyEvent event) {
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.
*/
public final synchronized Window show(int id) {
// get the window corresponding to the id
Window cachedWindow = getWindow(id);
final Window window;
// check cache first
if (cachedWindow != null) {
window = cachedWindow;
} else {
window = new Window(this, id);
}
if (window.visibility == Window.VISIBILITY_VISIBLE) {
throw new IllegalStateException("Tried to show(" + id
+ ") a window that is already shown.");
}
// alert callbacks and cancel if instructed
if (onShow(id, window)) {
Log.d(TAG, "Window " + id + " show cancelled by implementation.");
return null;
}
window.visibility = Window.VISIBILITY_VISIBLE;
// get animation
Animation animation = getShowAnimation(id);
// get the params corresponding to the id
StandOutLayoutParams params = window.getLayoutParams();
try {
// add the view to the window manager
mWindowManager.addView(window, params);
// animate
if (animation != null) {
window.getChildAt(0).startAnimation(animation);
}
} catch (Exception ex) {
ex.printStackTrace();
}
// add view to internal map
sWindowCache.putCache(id, getClass(), 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.");
}
}
focus(id);
return window;
}
/**
* Hide a window corresponding to the id. Show a notification for the hidden
* window.
*
* @param id
* The id of the window.
*/
public final synchronized void hide(int id) {
// get the view corresponding to the id
final Window window = getWindow(id);
if (window == null) {
throw new IllegalArgumentException("Tried to hide(" + id
+ ") a null window.");
}
if (window.visibility == Window.VISIBILITY_GONE) {
throw new IllegalStateException("Tried to hide(" + id
+ ") a window that is not shown.");
}
// alert callbacks and cancel if instructed
if (onHide(id, window)) {
Log.w(TAG, "Window " + id + " hide cancelled by implementation.");
return;
}
// check if hide enabled
if (Utils.isSet(window.flags, StandOutFlags.FLAG_WINDOW_HIDE_ENABLE)) {
window.visibility = Window.VISIBILITY_TRANSITION;
// get the hidden notification for this view
Notification notification = getHiddenNotification(id);
// get animation
Animation animation = getHideAnimation(id);
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 window from the window manager
mWindowManager.removeView(window);
window.visibility = Window.VISIBILITY_GONE;
}
});
window.getChildAt(0).startAnimation(animation);
} else {
// remove the window 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.
*/
public final synchronized void close(final int id) {
// get the view corresponding to the id
final Window window = getWindow(id);
if (window == null) {
throw new IllegalArgumentException("Tried to close(" + id
+ ") a null window.");
}
if (window.visibility == Window.VISIBILITY_TRANSITION) {
return;
}
// alert callbacks and cancel if instructed
if (onClose(id, window)) {
Log.w(TAG, "Window " + id + " close cancelled by implementation.");
return;
}
// remove hidden notification
mNotificationManager.cancel(getClass().hashCode() + id);
unfocus(window);
window.visibility = Window.VISIBILITY_TRANSITION;
// get animation
Animation animation = getCloseAnimation(id);
// remove window
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 window from the window manager
mWindowManager.removeView(window);
window.visibility = Window.VISIBILITY_GONE;
// remove view from internal map
sWindowCache.removeCache(id,
StandOutWindow.this.getClass());
// if we just released the last window, quit
if (getExistingIds().size() == 0) {
// tell Android to remove the persistent
// notification
// the Service will be shutdown by the system on low
// memory
startedForeground = false;
stopForeground(true);
}
}
});
window.getChildAt(0).startAnimation(animation);
} else {
// remove the window from the window manager
mWindowManager.removeView(window);
// remove view from internal map
sWindowCache.removeCache(id, getClass());
// if we just released the last window, quit
if (sWindowCache.getCacheSize(getClass()) == 0) {
// tell Android to remove the persistent notification
// the Service will be shutdown by the system on low memory
startedForeground = false;
stopForeground(true);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Close all existing windows.
*/
public final synchronized void closeAll() {
// alert callbacks and cancel if instructed
if (onCloseAll()) {
Log.w(TAG, "Windows close all cancelled by implementation.");
return;
}
// add ids to temporary set to avoid concurrent modification
LinkedList<Integer> ids = new LinkedList<Integer>();
for (int id : getExistingIds()) {
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
* Provide the id of the sending window if you want a result.
* @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.
*/
public final void sendData(int fromId,
Class<? extends StandOutWindow> toCls, int toId, int requestCode,
Bundle data) {
StandOutWindow.sendData(this, toCls, toId, requestCode, data,
getClass(), fromId);
}
/**
* 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.
*/
public final synchronized void bringToFront(int id) {
Window window = getWindow(id);
if (window == null) {
throw new IllegalArgumentException("Tried to bringToFront(" + id
+ ") a null window.");
}
if (window.visibility == Window.VISIBILITY_GONE) {
throw new IllegalStateException("Tried to bringToFront(" + id
+ ") a window that is not shown.");
}
if (window.visibility == Window.VISIBILITY_TRANSITION) {
return;
}
// alert callbacks and cancel if instructed
if (onBringToFront(id, window)) {
Log.w(TAG, "Window " + id
+ " bring to front cancelled by implementation.");
return;
}
StandOutLayoutParams params = window.getLayoutParams();
// 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();
}
}
/**
* Request focus for the window corresponding to this id. A maximum of one
* window can have focus, and that window will receive all key events,
* including Back and Menu.
*
* @param id
* The id of the window.
* @return True if focus changed successfully, false if it failed.
*/
public final synchronized boolean focus(int id) {
// check if that window is focusable
final Window window = getWindow(id);
if (window == null) {
throw new IllegalArgumentException("Tried to focus(" + id
+ ") a null window.");
}
if (!Utils.isSet(window.flags,
StandOutFlags.FLAG_WINDOW_FOCUSABLE_DISABLE)) {
// remove focus from previously focused window
if (sFocusedWindow != null) {
unfocus(sFocusedWindow);
}
return window.onFocus(true);
}
return false;
}
/**
* Remove focus for the window corresponding to this id. Once a window is
* unfocused, it will stop receiving key events.
*
* @param id
* The id of the window.
* @return True if focus changed successfully, false if it failed.
*/
public final synchronized boolean unfocus(int id) {
Window window = getWindow(id);
return unfocus(window);
}
/**
* Courtesy method for your implementation to use if you want to. Gets a
* unique id to assign to a new window.
*
* @return The unique id.
*/
public final int getUniqueId() {
int unique = DEFAULT_ID;
for (int id : getExistingIds()) {
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.
*/
public final boolean isExistingId(int id) {
return sWindowCache.isCached(id, getClass());
}
/**
* Return the ids of all shown or hidden windows.
*
* @return A set of ids, or an empty set.
*/
public final Set<Integer> getExistingIds() {
return sWindowCache.getCacheIds(getClass());
}
/**
* 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 or hidden.
*
* @param id
* The id of the window.
* @return The window if it is shown/hidden, or null if it is closed.
*/
public final Window getWindow(int id) {
return sWindowCache.getCache(id, getClass());
}
/**
* Return the window that currently has focus.
*
* @return The window that has focus.
*/
public final Window getFocusedWindow() {
return sFocusedWindow;
}
/**
* Sets the window that currently has focus.
*/
public final void setFocusedWindow(Window window) {
sFocusedWindow = window;
}
/**
* Change the title of the window, if such a title exists. A title exists if
* {@link StandOutFlags#FLAG_DECORATION_SYSTEM} is set, or if your own view
* contains a TextView with id R.id.title.
*
* @param id
* The id of the window.
* @param text
* The new title.
*/
public final void setTitle(int id, String text) {
Window window = getWindow(id);
if (window != null) {
View title = window.findViewById(R.id.title);
if (title instanceof TextView) {
((TextView) title).setText(text);
}
}
}
/**
* Change the icon of the window, if such a icon exists. A icon exists if
* {@link StandOutFlags#FLAG_DECORATION_SYSTEM} is set, or if your own view
* contains a TextView with id R.id.window_icon.
*
* @param id
* The id of the window.
* @param drawableRes
* The new icon.
*/
public final void setIcon(int id, int drawableRes) {
Window window = getWindow(id);
if (window != null) {
View icon = window.findViewById(R.id.window_icon);
if (icon instanceof ImageView) {
((ImageView) icon).setImageResource(drawableRes);
}
}
}
/**
* Internal touch handler for handling moving the window.
*
* @see {@link View#onTouchEvent(MotionEvent)}
*
* @param id
* @param window
* @param view
* @param event
* @return
*/
public boolean onTouchHandleMove(int id, Window window, View view,
MotionEvent event) {
StandOutLayoutParams params = window.getLayoutParams();
// how much you have to move in either direction in order for the
// gesture to be a move and not tap
int totalDeltaX = window.touchInfo.lastX - window.touchInfo.firstX;
int totalDeltaY = window.touchInfo.lastY - window.touchInfo.firstY;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
window.touchInfo.lastX = (int) event.getRawX();
window.touchInfo.lastY = (int) event.getRawY();
window.touchInfo.firstX = window.touchInfo.lastX;
window.touchInfo.firstY = window.touchInfo.lastY;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) event.getRawX() - window.touchInfo.lastX;
int deltaY = (int) event.getRawY() - window.touchInfo.lastY;
window.touchInfo.lastX = (int) event.getRawX();
window.touchInfo.lastY = (int) event.getRawY();
if (window.touchInfo.moving
|| Math.abs(totalDeltaX) >= params.threshold
|| Math.abs(totalDeltaY) >= params.threshold) {
window.touchInfo.moving = true;
// if window is moveable
if (Utils.isSet(window.flags,
StandOutFlags.FLAG_BODY_MOVE_ENABLE)) {
// update the position of the window
if (event.getPointerCount() == 1) {
params.x += deltaX;
params.y += deltaY;
}
window.edit().setPosition(params.x, params.y).commit();
}
}
break;
case MotionEvent.ACTION_UP:
window.touchInfo.moving = false;
if (event.getPointerCount() == 1) {
// bring to front on tap
boolean tap = Math.abs(totalDeltaX) < params.threshold
&& Math.abs(totalDeltaY) < params.threshold;
if (tap
&& Utils.isSet(
window.flags,
StandOutFlags.FLAG_WINDOW_BRING_TO_FRONT_ON_TAP)) {
StandOutWindow.this.bringToFront(id);
}
}
// bring to front on touch
else if (Utils.isSet(window.flags,
StandOutFlags.FLAG_WINDOW_BRING_TO_FRONT_ON_TOUCH)) {
StandOutWindow.this.bringToFront(id);
}
break;
}
onMove(id, window, view, event);
return true;
}
/**
* Internal touch handler for handling resizing the window.
*
* @see {@link View#onTouchEvent(MotionEvent)}
*
* @param id
* @param window
* @param view
* @param event
* @return
*/
public boolean onTouchHandleResize(int id, Window window, View view,
MotionEvent event) {
StandOutLayoutParams params = (StandOutLayoutParams) window
.getLayoutParams();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
window.touchInfo.lastX = (int) event.getRawX();
window.touchInfo.lastY = (int) event.getRawY();
window.touchInfo.firstX = window.touchInfo.lastX;
window.touchInfo.firstY = window.touchInfo.lastY;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) event.getRawX() - window.touchInfo.lastX;
int deltaY = (int) event.getRawY() - window.touchInfo.lastY;
// update the size of the window
params.width += deltaX;
params.height += deltaY;
// keep window between min/max width/height
if (params.width >= params.minWidth
&& params.width <= params.maxWidth) {
window.touchInfo.lastX = (int) event.getRawX();
}
if (params.height >= params.minHeight
&& params.height <= params.maxHeight) {
window.touchInfo.lastY = (int) event.getRawY();
}
window.edit().setSize(params.width, params.height).commit();
break;
case MotionEvent.ACTION_UP:
break;
}
onResize(id, window, view, event);
return true;
}
/**
* Remove focus for the window, which could belong to another application.
* Since we don't allow windows from different applications to directly
* interact with each other, except for
* {@link #sendData(Context, Class, int, int, Bundle, Class, int)}, this
* method is private.
*
* @param window
* The window to unfocus.
* @return True if focus changed successfully, false if it failed.
*/
public synchronized boolean unfocus(Window window) {
if (window == null) {
throw new IllegalArgumentException(
"Tried to unfocus a null window.");
}
return window.onFocus(false);
}
/**
* Update the window corresponding to this id with the given params.
*
* @param id
* The id of the window.
* @param params
* The updated layout params to apply.
*/
public void updateViewLayout(int id, StandOutLayoutParams params) {
Window window = getWindow(id);
if (window == null) {
throw new IllegalArgumentException("Tried to updateViewLayout("
+ id + ") a null window.");
}
if (window.visibility == Window.VISIBILITY_GONE) {
return;
}
if (window.visibility == Window.VISIBILITY_TRANSITION) {
return;
}
// alert callbacks and cancel if instructed
if (onUpdate(id, window, params)) {
Log.w(TAG, "Window " + id + " update cancelled by implementation.");
return;
}
try {
window.setLayoutParams(params);
mWindowManager.updateViewLayout(window, params);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* LayoutParams specific to floating StandOut windows.
*
* @author Mark Wei <markwei@gmail.com>
*
*/
public class StandOutLayoutParams extends WindowManager.LayoutParams {
/**
* Special value for x position that represents the left of the screen.
*/
public static final int LEFT = 0;
/**
* Special value for y position that represents the top of the screen.
*/
public static final int TOP = 0;
/**
* Special value for x position that represents the right of the screen.
*/
public static final int RIGHT = Integer.MAX_VALUE;
/**
* Special value for y position that represents the bottom of the
* screen.
*/
public static final int BOTTOM = Integer.MAX_VALUE;
/**
* Special value for x or y position that represents the center of the
* screen.
*/
public static final int CENTER = Integer.MIN_VALUE;
/**
* Special value for x or y position which requests that the system
* determine the position.
*/
public static final int AUTO_POSITION = Integer.MIN_VALUE + 1;
/**
* The distance that distinguishes a tap from a drag.
*/
public int threshold;
/**
* Optional constraints of the window.
*/
public int minWidth, minHeight, maxWidth, maxHeight;
/**
* @param id
* The id of the window.
*/
public StandOutLayoutParams(int id) {
super(200, 200, TYPE_PHONE,
StandOutLayoutParams.FLAG_NOT_TOUCH_MODAL
| StandOutLayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
int windowFlags = getFlags(id);
setFocusFlag(false);
if (!Utils.isSet(windowFlags,
StandOutFlags.FLAG_WINDOW_EDGE_LIMITS_ENABLE)) {
// windows may be moved beyond edges
flags |= FLAG_LAYOUT_NO_LIMITS;
}
x = getX(id, width);
y = getY(id, height);
gravity = Gravity.TOP | Gravity.LEFT;
threshold = 10;
minWidth = minHeight = 0;
maxWidth = maxHeight = Integer.MAX_VALUE;
}
/**
* @param id
* The id of the window.
* @param w
* The width of the window.
* @param h
* The height of the window.
*/
public StandOutLayoutParams(int id, int w, int h) {
this(id);
width = w;
height = h;
}
/**
* @param id
* The id of the window.
* @param w
* The width of the window.
* @param h
* The height of the window.
* @param xpos
* The x position of the window.
* @param ypos
* The y position of the window.
*/
@SuppressWarnings("deprecation")
public StandOutLayoutParams(int id, int w, int h, int xpos, int ypos) {
this(id, w, h);
if (xpos != AUTO_POSITION) {
x = xpos;
}
if (ypos != AUTO_POSITION) {
y = ypos;
}
Display display = mWindowManager.getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
if (x == RIGHT) {
x = width - w;
} else if (x == CENTER) {
x = (width - w) / 2;
}
if (y == BOTTOM) {
y = height - h;
} else if (y == CENTER) {
y = (height - h) / 2;
}
}
/**
* @param id
* The id of the window.
* @param w
* The width of the window.
* @param h
* The height of the window.
* @param xpos
* The x position of the window.
* @param ypos
* The y position of the window.
* @param minWidth
* The minimum width of the window.
* @param minHeight
* The mininum height of the window.
*/
public StandOutLayoutParams(int id, int w, int h, int xpos, int ypos,
int minWidth, int minHeight) {
this(id, w, h, xpos, ypos);
this.minWidth = minWidth;
this.minHeight = minHeight;
}
/**
* @param id
* The id of the window.
* @param w
* The width of the window.
* @param h
* The height of the window.
* @param xpos
* The x position of the window.
* @param ypos
* The y position of the window.
* @param minWidth
* The minimum width of the window.
* @param minHeight
* The mininum height of the window.
* @param threshold
* The touch distance threshold that distinguishes a tap from
* a drag.
*/
public StandOutLayoutParams(int id, int w, int h, int xpos, int ypos,
int minWidth, int minHeight, int threshold) {
this(id, w, h, xpos, ypos, minWidth, minHeight);
this.threshold = threshold;
}
// helper to create cascading windows
@SuppressWarnings("deprecation")
private int getX(int id, int width) {
Display display = mWindowManager.getDefaultDisplay();
int displayWidth = display.getWidth();
int types = sWindowCache.size();
int initialX = 100 * types;
int variableX = 100 * id;
int rawX = initialX + variableX;
return rawX % (displayWidth - width);
}
// helper to create cascading windows
@SuppressWarnings("deprecation")
private int getY(int id, int height) {
Display display = mWindowManager.getDefaultDisplay();
int displayWidth = display.getWidth();
int displayHeight = display.getHeight();
int types = sWindowCache.size();
int initialY = 100 * types;
int variableY = x + 200 * (100 * id) / (displayWidth - width);
int rawY = initialY + variableY;
return rawY % (displayHeight - height);
}
public void setFocusFlag(boolean focused) {
if (focused) {
flags = flags ^ StandOutLayoutParams.FLAG_NOT_FOCUSABLE;
} else {
flags = flags | StandOutLayoutParams.FLAG_NOT_FOCUSABLE;
}
}
}
protected class DropDownListItem {
public int icon;
public String description;
public Runnable action;
public DropDownListItem(int icon, String description, Runnable action) {
super();
this.icon = icon;
this.description = description;
this.action = action;
}
@Override
public String toString() {
return description;
}
}
}