package com.actionbarsherlock;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.internal.ActionBarSherlockCompat;
import com.actionbarsherlock.internal.ActionBarSherlockNative;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
/**
* <p>Helper for implementing the action bar design pattern across all versions
* of Android.</p>
*
* <p>This class will manage interaction with a custom action bar based on the
* Android 4.0 source code. The exposed API mirrors that of its native
* counterpart and you should refer to its documentation for instruction.</p>
*
* @author Jake Wharton <jakewharton@gmail.com>
*/
public abstract class ActionBarSherlock {
protected static final String TAG = "ActionBarSherlock";
protected static final boolean DEBUG = false;
private static final Class<?>[] CONSTRUCTOR_ARGS = new Class[] { Activity.class, int.class };
private static final HashMap<Implementation, Class<? extends ActionBarSherlock>> IMPLEMENTATIONS =
new HashMap<Implementation, Class<? extends ActionBarSherlock>>();
static {
//Register our two built-in implementations
registerImplementation(ActionBarSherlockCompat.class);
registerImplementation(ActionBarSherlockNative.class);
}
/**
* <p>Denotes an implementation of ActionBarSherlock which provides an
* action bar-enhanced experience.</p>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Implementation {
static final int DEFAULT_API = -1;
static final int DEFAULT_DPI = -1;
int api() default DEFAULT_API;
int dpi() default DEFAULT_DPI;
}
/** Activity interface for menu creation callback. */
public interface OnCreatePanelMenuListener {
public boolean onCreatePanelMenu(int featureId, Menu menu);
}
/** Activity interface for menu creation callback. */
public interface OnCreateOptionsMenuListener {
public boolean onCreateOptionsMenu(Menu menu);
}
/** Activity interface for menu item selection callback. */
public interface OnMenuItemSelectedListener {
public boolean onMenuItemSelected(int featureId, MenuItem item);
}
/** Activity interface for menu item selection callback. */
public interface OnOptionsItemSelectedListener {
public boolean onOptionsItemSelected(MenuItem item);
}
/** Activity interface for menu preparation callback. */
public interface OnPreparePanelListener {
public boolean onPreparePanel(int featureId, View view, Menu menu);
}
/** Activity interface for menu preparation callback. */
public interface OnPrepareOptionsMenuListener {
public boolean onPrepareOptionsMenu(Menu menu);
}
/** Activity interface for action mode finished callback. */
public interface OnActionModeFinishedListener {
public void onActionModeFinished(ActionMode mode);
}
/** Activity interface for action mode started callback. */
public interface OnActionModeStartedListener {
public void onActionModeStarted(ActionMode mode);
}
/**
* If set, the logic in these classes will assume that an {@link Activity}
* is dispatching all of the required events to the class. This flag should
* only be used internally or if you are creating your own base activity
* modeled after one of the included types (e.g., {@code SherlockActivity}).
*/
public static final int FLAG_DELEGATE = 1;
/**
* Register an ActionBarSherlock implementation.
*
* @param implementationClass Target implementation class which extends
* {@link ActionBarSherlock}. This class must also be annotated with
* {@link Implementation}.
*/
public static void registerImplementation(Class<? extends ActionBarSherlock> implementationClass) {
if (!implementationClass.isAnnotationPresent(Implementation.class)) {
throw new IllegalArgumentException("Class " + implementationClass.getSimpleName() + " is not annotated with @Implementation");
} else if (IMPLEMENTATIONS.containsValue(implementationClass)) {
if (DEBUG) Log.w(TAG, "Class " + implementationClass.getSimpleName() + " already registered");
return;
}
Implementation impl = implementationClass.getAnnotation(Implementation.class);
if (DEBUG) Log.i(TAG, "Registering " + implementationClass.getSimpleName() + " with qualifier " + impl);
IMPLEMENTATIONS.put(impl, implementationClass);
}
/**
* Unregister an ActionBarSherlock implementation. <strong>This should be
* considered very volatile and you should only use it if you know what
* you are doing.</strong> You have been warned.
*
* @param implementationClass Target implementation class.
* @return Boolean indicating whether the class was removed.
*/
public static boolean unregisterImplementation(Class<? extends ActionBarSherlock> implementationClass) {
return IMPLEMENTATIONS.values().remove(implementationClass);
}
/**
* Wrap an activity with an action bar abstraction which will enable the
* use of a custom implementation on platforms where a native version does
* not exist.
*
* @param activity Activity to wrap.
* @return Instance to interact with the action bar.
*/
public static ActionBarSherlock wrap(Activity activity) {
return wrap(activity, 0);
}
/**
* Wrap an activity with an action bar abstraction which will enable the
* use of a custom implementation on platforms where a native version does
* not exist.
*
* @param activity Owning activity.
* @param flags Option flags to control behavior.
* @return Instance to interact with the action bar.
*/
public static ActionBarSherlock wrap(Activity activity, int flags) {
//Create a local implementation map we can modify
HashMap<Implementation, Class<? extends ActionBarSherlock>> impls =
new HashMap<Implementation, Class<? extends ActionBarSherlock>>(IMPLEMENTATIONS);
boolean hasQualfier;
/* DPI FILTERING */
hasQualfier = false;
for (Implementation key : impls.keySet()) {
//Only honor TVDPI as a specific qualifier
if (key.dpi() == DisplayMetrics.DENSITY_TV) {
hasQualfier = true;
break;
}
}
if (hasQualfier) {
final boolean isTvDpi = activity.getResources().getDisplayMetrics().densityDpi == DisplayMetrics.DENSITY_TV;
for (Iterator<Implementation> keys = impls.keySet().iterator(); keys.hasNext(); ) {
int keyDpi = keys.next().dpi();
if ((isTvDpi && keyDpi != DisplayMetrics.DENSITY_TV)
|| (!isTvDpi && keyDpi == DisplayMetrics.DENSITY_TV)) {
keys.remove();
}
}
}
/* API FILTERING */
hasQualfier = false;
for (Implementation key : impls.keySet()) {
if (key.api() != Implementation.DEFAULT_API) {
hasQualfier = true;
break;
}
}
if (hasQualfier) {
final int runtimeApi = Build.VERSION.SDK_INT;
int bestApi = 0;
for (Iterator<Implementation> keys = impls.keySet().iterator(); keys.hasNext(); ) {
int keyApi = keys.next().api();
if (keyApi > runtimeApi) {
keys.remove();
} else if (keyApi > bestApi) {
bestApi = keyApi;
}
}
for (Iterator<Implementation> keys = impls.keySet().iterator(); keys.hasNext(); ) {
if (keys.next().api() != bestApi) {
keys.remove();
}
}
}
if (impls.size() > 1) {
throw new IllegalStateException("More than one implementation matches configuration.");
}
if (impls.isEmpty()) {
throw new IllegalStateException("No implementations match configuration.");
}
Class<? extends ActionBarSherlock> impl = impls.values().iterator().next();
if (DEBUG) Log.i(TAG, "Using implementation: " + impl.getSimpleName());
try {
Constructor<? extends ActionBarSherlock> ctor = impl.getConstructor(CONSTRUCTOR_ARGS);
return ctor.newInstance(activity, flags);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/** Activity which is displaying the action bar. Also used for context. */
protected final Activity mActivity;
/** Whether delegating actions for the activity or managing ourselves. */
protected final boolean mIsDelegate;
/** Reference to our custom menu inflater which supports action items. */
protected MenuInflater mMenuInflater;
protected ActionBarSherlock(Activity activity, int flags) {
if (DEBUG) Log.d(TAG, "[<ctor>] activity: " + activity + ", flags: " + flags);
mActivity = activity;
mIsDelegate = (flags & FLAG_DELEGATE) != 0;
}
/**
* Get the current action bar instance.
*
* @return Action bar instance.
*/
public abstract ActionBar getActionBar();
///////////////////////////////////////////////////////////////////////////
// Lifecycle and interaction callbacks when delegating
///////////////////////////////////////////////////////////////////////////
/**
* Notify action bar of a configuration change event. Should be dispatched
* after the call to the superclass implementation.
*
* <blockquote><pre>
* @Override
* public void onConfigurationChanged(Configuration newConfig) {
* super.onConfigurationChanged(newConfig);
* mSherlock.dispatchConfigurationChanged(newConfig);
* }
* </pre></blockquote>
*
* @param newConfig The new device configuration.
*/
public void dispatchConfigurationChanged(Configuration newConfig) {}
/**
* Notify the action bar that the activity has finished its resuming. This
* should be dispatched after the call to the superclass implementation.
*
* <blockquote><pre>
* @Override
* protected void onPostResume() {
* super.onPostResume();
* mSherlock.dispatchPostResume();
* }
* </pre></blockquote>
*/
public void dispatchPostResume() {}
/**
* Notify the action bar that the activity is pausing. This should be
* dispatched before the call to the superclass implementation.
*
* <blockquote><pre>
* @Override
* protected void onPause() {
* mSherlock.dispatchPause();
* super.onPause();
* }
* </pre></blockquote>
*/
public void dispatchPause() {}
/**
* Notify the action bar that the activity is stopping. This should be
* called before the superclass implementation.
*
* <blockquote><p>
* @Override
* protected void onStop() {
* mSherlock.dispatchStop();
* super.onStop();
* }
* </p></blockquote>
*/
public void dispatchStop() {}
/**
* Indicate that the menu should be recreated by calling
* {@link OnCreateOptionsMenuListener#onCreateOptionsMenu(com.actionbarsherlock.view.Menu)}.
*/
public abstract void dispatchInvalidateOptionsMenu();
/**
* Notify the action bar that it should display its overflow menu if it is
* appropriate for the device. The implementation should conditionally
* call the superclass method only if this method returns {@code false}.
*
* <blockquote><p>
* @Override
* public void openOptionsMenu() {
* if (!mSherlock.dispatchOpenOptionsMenu()) {
* super.openOptionsMenu();
* }
* }
* </p></blockquote>
*
* @return {@code true} if the opening of the menu was handled internally.
*/
public boolean dispatchOpenOptionsMenu() {
return false;
}
/**
* Notify the action bar that it should close its overflow menu if it is
* appropriate for the device. This implementation should conditionally
* call the superclass method only if this method returns {@code false}.
*
* <blockquote><pre>
* @Override
* public void closeOptionsMenu() {
* if (!mSherlock.dispatchCloseOptionsMenu()) {
* super.closeOptionsMenu();
* }
* }
* </pre></blockquote>
*
* @return {@code true} if the closing of the menu was handled internally.
*/
public boolean dispatchCloseOptionsMenu() {
return false;
}
/**
* Notify the class that the activity has finished its creation. This
* should be called after the superclass implementation.
*
* <blockquote><pre>
* @Override
* protected void onPostCreate(Bundle savedInstanceState) {
* mSherlock.dispatchPostCreate(savedInstanceState);
* super.onPostCreate(savedInstanceState);
* }
* </pre></blockquote>
*
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle
* contains the data it most recently supplied in
* {@link Activity#}onSaveInstanceState(Bundle)}.
* <strong>Note: Otherwise it is null.</strong>
*/
public void dispatchPostCreate(Bundle savedInstanceState) {}
/**
* Notify the action bar that the title has changed and the action bar
* should be updated to reflect the change. This should be called before
* the superclass implementation.
*
* <blockquote><pre>
* @Override
* protected void onTitleChanged(CharSequence title, int color) {
* mSherlock.dispatchTitleChanged(title, color);
* super.onTitleChanged(title, color);
* }
* </pre></blockquote>
*
* @param title New activity title.
* @param color New activity color.
*/
public void dispatchTitleChanged(CharSequence title, int color) {}
/**
* Notify the action bar the user has created a key event. This is used to
* toggle the display of the overflow action item with the menu key and to
* close the action mode or expanded action item with the back key.
*
* <blockquote><pre>
* @Override
* public boolean dispatchKeyEvent(KeyEvent event) {
* if (mSherlock.dispatchKeyEvent(event)) {
* return true;
* }
* return super.dispatchKeyEvent(event);
* }
* </pre></blockquote>
*
* @param event Description of the key event.
* @return {@code true} if the event was handled.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
return false;
}
/**
* Notify the action bar that the Activity has triggered a menu creation
* which should happen on the conclusion of {@link Activity#onCreate}. This
* will be used to gain a reference to the native menu for native and
* overflow binding as well as to indicate when compatibility create should
* occur for the first time.
*
* @param menu Activity native menu.
* @return {@code true} since we always want to say that we have a native
*/
public abstract boolean dispatchCreateOptionsMenu(android.view.Menu menu);
/**
* Notify the action bar that the Activity has triggered a menu preparation
* which usually means that the user has requested the overflow menu via a
* hardware menu key. You should return the result of this method call and
* not call the superclass implementation.
*
* <blockquote><p>
* @Override
* public final boolean onPrepareOptionsMenu(android.view.Menu menu) {
* return mSherlock.dispatchPrepareOptionsMenu(menu);
* }
* </p></blockquote>
*
* @param menu Activity native menu.
* @return {@code true} if menu display should proceed.
*/
public abstract boolean dispatchPrepareOptionsMenu(android.view.Menu menu);
/**
* Notify the action bar that a native options menu item has been selected.
* The implementation should return the result of this method call.
*
* <blockquote><p>
* @Override
* public final boolean onOptionsItemSelected(android.view.MenuItem item) {
* return mSherlock.dispatchOptionsItemSelected(item);
* }
* </p></blockquote>
*
* @param item Options menu item.
* @return @{code true} if the selection was handled.
*/
public abstract boolean dispatchOptionsItemSelected(android.view.MenuItem item);
/**
* Notify the action bar that the overflow menu has been opened. The
* implementation should conditionally return {@code true} if this method
* returns {@code true}, otherwise return the result of the superclass
* method.
*
* <blockquote><p>
* @Override
* public final boolean onMenuOpened(int featureId, android.view.Menu menu) {
* if (mSherlock.dispatchMenuOpened(featureId, menu)) {
* return true;
* }
* return super.onMenuOpened(featureId, menu);
* }
* </p></blockquote>
*
* @param featureId Window feature which triggered the event.
* @param menu Activity native menu.
* @return {@code true} if the event was handled by this method.
*/
public boolean dispatchMenuOpened(int featureId, android.view.Menu menu) {
return false;
}
/**
* Notify the action bar that the overflow menu has been closed. This
* method should be called before the superclass implementation.
*
* <blockquote><p>
* @Override
* public void onPanelClosed(int featureId, android.view.Menu menu) {
* mSherlock.dispatchPanelClosed(featureId, menu);
* super.onPanelClosed(featureId, menu);
* }
* </p></blockquote>
*
* @param featureId
* @param menu
*/
public void dispatchPanelClosed(int featureId, android.view.Menu menu) {}
/**
* Notify the action bar that the activity has been destroyed. This method
* should be called before the superclass implementation.
*
* <blockquote><p>
* @Override
* public void onDestroy() {
* mSherlock.dispatchDestroy();
* super.onDestroy();
* }
* </p></blockquote>
*/
public void dispatchDestroy() {}
public void dispatchSaveInstanceState(Bundle outState) {}
public void dispatchRestoreInstanceState(Bundle savedInstanceState) {}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* Internal method to trigger the menu creation process.
*
* @return {@code true} if menu creation should proceed.
*/
protected final boolean callbackCreateOptionsMenu(Menu menu) {
if (DEBUG) Log.d(TAG, "[callbackCreateOptionsMenu] menu: " + menu);
boolean result = true;
if (mActivity instanceof OnCreatePanelMenuListener) {
OnCreatePanelMenuListener listener = (OnCreatePanelMenuListener)mActivity;
result = listener.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu);
} else if (mActivity instanceof OnCreateOptionsMenuListener) {
OnCreateOptionsMenuListener listener = (OnCreateOptionsMenuListener)mActivity;
result = listener.onCreateOptionsMenu(menu);
}
if (DEBUG) Log.d(TAG, "[callbackCreateOptionsMenu] returning " + result);
return result;
}
/**
* Internal method to trigger the menu preparation process.
*
* @return {@code true} if menu preparation should proceed.
*/
protected final boolean callbackPrepareOptionsMenu(Menu menu) {
if (DEBUG) Log.d(TAG, "[callbackPrepareOptionsMenu] menu: " + menu);
boolean result = true;
if (mActivity instanceof OnPreparePanelListener) {
OnPreparePanelListener listener = (OnPreparePanelListener)mActivity;
result = listener.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu);
} else if (mActivity instanceof OnPrepareOptionsMenuListener) {
OnPrepareOptionsMenuListener listener = (OnPrepareOptionsMenuListener)mActivity;
result = listener.onPrepareOptionsMenu(menu);
}
if (DEBUG) Log.d(TAG, "[callbackPrepareOptionsMenu] returning " + result);
return result;
}
/**
* Internal method for dispatching options menu selection to the owning
* activity callback.
*
* @param item Selected options menu item.
* @return {@code true} if the item selection was handled in the callback.
*/
protected final boolean callbackOptionsItemSelected(MenuItem item) {
if (DEBUG) Log.d(TAG, "[callbackOptionsItemSelected] item: " + item.getTitleCondensed());
boolean result = false;
if (mActivity instanceof OnMenuItemSelectedListener) {
OnMenuItemSelectedListener listener = (OnMenuItemSelectedListener)mActivity;
result = listener.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
} else if (mActivity instanceof OnOptionsItemSelectedListener) {
OnOptionsItemSelectedListener listener = (OnOptionsItemSelectedListener)mActivity;
result = listener.onOptionsItemSelected(item);
}
if (DEBUG) Log.d(TAG, "[callbackOptionsItemSelected] returning " + result);
return result;
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* Query for the availability of a certain feature.
*
* @param featureId The feature ID to check.
* @return {@code true} if feature is enabled, {@code false} otherwise.
*/
public abstract boolean hasFeature(int featureId);
/**
* Enable extended screen features. This must be called before
* {@code setContentView()}. May be called as many times as desired as long
* as it is before {@code setContentView()}. If not called, no extended
* features will be available. You can not turn off a feature once it is
* requested.
*
* @param featureId The desired features, defined as constants by Window.
* @return Returns true if the requested feature is supported and now
* enabled.
*/
public abstract boolean requestFeature(int featureId);
/**
* Set extra options that will influence the UI for this window.
*
* @param uiOptions Flags specifying extra options for this window.
*/
public abstract void setUiOptions(int uiOptions);
/**
* Set extra options that will influence the UI for this window. Only the
* bits filtered by mask will be modified.
*
* @param uiOptions Flags specifying extra options for this window.
* @param mask Flags specifying which options should be modified. Others
* will remain unchanged.
*/
public abstract void setUiOptions(int uiOptions, int mask);
/**
* Set the content of the activity inside the action bar.
*
* @param layoutResId Layout resource ID.
*/
public abstract void setContentView(int layoutResId);
/**
* Set the content of the activity inside the action bar.
*
* @param view The desired content to display.
*/
public void setContentView(View view) {
if (DEBUG) Log.d(TAG, "[setContentView] view: " + view);
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
/**
* Set the content of the activity inside the action bar.
*
* @param view The desired content to display.
* @param params Layout parameters to apply to the view.
*/
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
/**
* Variation on {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
* to add an additional content view to the screen. Added after any
* existing ones on the screen -- existing views are NOT removed.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
public abstract void addContentView(View view, ViewGroup.LayoutParams params);
/**
* Change the title associated with this activity.
*/
public abstract void setTitle(CharSequence title);
/**
* Change the title associated with this activity.
*/
public void setTitle(int resId) {
if (DEBUG) Log.d(TAG, "[setTitle] resId: " + resId);
setTitle(mActivity.getString(resId));
}
/**
* Sets the visibility of the progress bar in the title.
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
*
* @param visible Whether to show the progress bars in the title.
*/
public abstract void setProgressBarVisibility(boolean visible);
/**
* Sets the visibility of the indeterminate progress bar in the title.
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
*
* @param visible Whether to show the progress bars in the title.
*/
public abstract void setProgressBarIndeterminateVisibility(boolean visible);
/**
* Sets whether the horizontal progress bar in the title should be indeterminate (the circular
* is always indeterminate).
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
*
* @param indeterminate Whether the horizontal progress bar should be indeterminate.
*/
public abstract void setProgressBarIndeterminate(boolean indeterminate);
/**
* Sets the progress for the progress bars in the title.
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
*
* @param progress The progress for the progress bar. Valid ranges are from
* 0 to 10000 (both inclusive). If 10000 is given, the progress
* bar will be completely filled and will fade out.
*/
public abstract void setProgress(int progress);
/**
* Sets the secondary progress for the progress bar in the title. This
* progress is drawn between the primary progress (set via
* {@link #setProgress(int)} and the background. It can be ideal for media
* scenarios such as showing the buffering progress while the default
* progress shows the play progress.
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
*
* @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from
* 0 to 10000 (both inclusive).
*/
public abstract void setSecondaryProgress(int secondaryProgress);
/**
* Get a menu inflater instance which supports the newer menu attributes.
*
* @return Menu inflater instance.
*/
public MenuInflater getMenuInflater() {
if (DEBUG) Log.d(TAG, "[getMenuInflater]");
// Make sure that action views can get an appropriate theme.
if (mMenuInflater == null) {
if (getActionBar() != null) {
mMenuInflater = new MenuInflater(getThemedContext(), mActivity);
} else {
mMenuInflater = new MenuInflater(mActivity);
}
}
return mMenuInflater;
}
protected abstract Context getThemedContext();
/**
* Start an action mode.
*
* @param callback Callback that will manage lifecycle events for this
* context mode.
* @return The ContextMode that was started, or null if it was canceled.
* @see ActionMode
*/
public abstract ActionMode startActionMode(ActionMode.Callback callback);
}