Java tutorial
/* 50AH-code ========= 50 Android Hacks (http://manning.com/sessa/) book source code Copyright (c) 2012 Manning Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 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() { } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// /** * 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()); } 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); }