summaryrefslogtreecommitdiffstats
path: root/actionbarsherlock/src/com
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2012-10-30 17:34:20 -0700
committerJohn Reck <jreck@google.com>2012-10-31 15:01:13 -0700
commit50e6182c8d6b9e6eba61fa3906c0ff348764efd8 (patch)
tree3664da0c76170e0a7d2ec0f71fd59cf7aa34c954 /actionbarsherlock/src/com
parent7d4315674d8e661afd82b5ce8fcbcbcb1c0a218a (diff)
downloadandroid_packages_apps_Snap-50e6182c8d6b9e6eba61fa3906c0ff348764efd8.tar.gz
android_packages_apps_Snap-50e6182c8d6b9e6eba61fa3906c0ff348764efd8.tar.bz2
android_packages_apps_Snap-50e6182c8d6b9e6eba61fa3906c0ff348764efd8.zip
Gingerbread support-ish
This reverts commit 1336062985c9824d7fd796b1cebd5eaa8e4163ca. Change-Id: I718ec56315dc207a3e612abb4576bddab9afa2ee
Diffstat (limited to 'actionbarsherlock/src/com')
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java721
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java231
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java148
-rw-r--r--actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java1193
4 files changed, 2293 insertions, 0 deletions
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 000000000..6f568c698
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getInteger;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseBooleanArray;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import com.actionbarsherlock.R;
+import com.actionbarsherlock.internal.view.View_HasStateListenerSupport;
+import com.actionbarsherlock.internal.view.View_OnAttachStateChangeListener;
+import com.actionbarsherlock.internal.view.menu.ActionMenuView.ActionMenuChildView;
+import com.actionbarsherlock.view.ActionProvider;
+import com.actionbarsherlock.view.MenuItem;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter
+ implements ActionProvider.SubUiVisibilityListener {
+ //UNUSED private static final String TAG = "ActionMenuPresenter";
+
+ private View mOverflowButton;
+ private boolean mReserveOverflow;
+ private boolean mReserveOverflowSet;
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+ private boolean mMaxItemsSet;
+ private boolean mStrictWidthLimit;
+ private boolean mWidthLimitSet;
+ private boolean mExpandedActionViewsExclusive;
+
+ private int mMinCellSize;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+
+ private OverflowPopup mOverflowPopup;
+ private ActionButtonSubmenu mActionButtonPopup;
+
+ private OpenOverflowRunnable mPostedOpenRunnable;
+
+ final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
+ int mOpenSubMenuId;
+
+ public ActionMenuPresenter(Context context) {
+ super(context, R.layout.abs__action_menu_layout,
+ R.layout.abs__action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+
+ if (!mReserveOverflowSet) {
+ mReserveOverflow = reserveOverflow(mContext);
+ }
+
+ if (!mWidthLimitSet) {
+ mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+ }
+
+ // Measure for initial configuration
+ if (!mMaxItemsSet) {
+ mMaxItems = getResources_getInteger(context, R.integer.abs__max_action_buttons);
+ }
+
+ int width = mWidthLimit;
+ if (mReserveOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mSystemContext);
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mOverflowButton.measure(spec, spec);
+ }
+ width -= mOverflowButton.getMeasuredWidth();
+ } else {
+ mOverflowButton = null;
+ }
+
+ mActionItemWidthLimit = width;
+
+ mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ public static boolean reserveOverflow(Context context) {
+ //Check for theme-forced overflow action item
+ TypedArray a = context.getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
+ boolean result = a.getBoolean(R.styleable.SherlockTheme_absForceOverflow, false);
+ a.recycle();
+ if (result) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB);
+ } else {
+ return !HasPermanentMenuKey.get(context);
+ }
+ }
+
+ private static class HasPermanentMenuKey {
+ public static boolean get(Context context) {
+ return ViewConfiguration.get(context).hasPermanentMenuKey();
+ }
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (!mMaxItemsSet) {
+ mMaxItems = getResources_getInteger(mContext,
+ R.integer.abs__max_action_buttons);
+ if (mMenu != null) {
+ mMenu.onItemsChanged(true);
+ }
+ }
+ }
+
+ public void setWidthLimit(int width, boolean strict) {
+ mWidthLimit = width;
+ mStrictWidthLimit = strict;
+ mWidthLimitSet = true;
+ }
+
+ public void setReserveOverflow(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ mReserveOverflowSet = true;
+ }
+
+ public void setItemLimit(int itemCount) {
+ mMaxItems = itemCount;
+ mMaxItemsSet = true;
+ }
+
+ public void setExpandedActionViewsExclusive(boolean isExclusive) {
+ mExpandedActionViewsExclusive = isExclusive;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ View actionView = item.getActionView();
+ if (actionView == null || item.hasCollapsibleActionView()) {
+ if (!(convertView instanceof ActionMenuItemView)) {
+ convertView = null;
+ }
+ actionView = super.getItemView(item, convertView, parent);
+ }
+ actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+
+ final ActionMenuView menuParent = (ActionMenuView) parent;
+ final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
+ if (!menuParent.checkLayoutParams(lp)) {
+ actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
+ }
+ return actionView;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+ itemView.initialize(item, 0);
+
+ final ActionMenuView menuView = (ActionMenuView) mMenuView;
+ ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
+ actionItemView.setItemInvoker(menuView);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ super.updateMenuView(cleared);
+
+ if (mMenu != null) {
+ final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
+ final int count = actionItems.size();
+ for (int i = 0; i < count; i++) {
+ final ActionProvider provider = actionItems.get(i).getActionProvider();
+ if (provider != null) {
+ provider.setSubUiVisibilityListener(this);
+ }
+ }
+ }
+
+ final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
+ mMenu.getNonActionItems() : null;
+
+ boolean hasOverflow = false;
+ if (mReserveOverflow && nonActionItems != null) {
+ final int count = nonActionItems.size();
+ if (count == 1) {
+ hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
+ } else {
+ hasOverflow = count > 0;
+ }
+ }
+
+ if (hasOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mSystemContext);
+ }
+ ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+ if (parent != mMenuView) {
+ if (parent != null) {
+ parent.removeView(mOverflowButton);
+ }
+ ActionMenuView menuView = (ActionMenuView) mMenuView;
+ menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
+ }
+ } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+ ((ViewGroup) mMenuView).removeView(mOverflowButton);
+ }
+
+ ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) {
+ if (mOverflowButton == null) return false;
+ anchor = mOverflowButton;
+ }
+
+ mOpenSubMenuId = subMenu.getItem().getItemId();
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+ mActionButtonPopup.setAnchorView(anchor);
+ mActionButtonPopup.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Display the overflow menu if one is present.
+ * @return true if the overflow menu was shown, false otherwise.
+ */
+ public boolean showOverflowMenu() {
+ if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
+ mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
+ OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+ mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+ // Post this for later; we might still need a layout for the anchor to be right.
+ ((View) mMenuView).post(mPostedOpenRunnable);
+
+ // ActionMenuPresenter uses null as a callback argument here
+ // to indicate overflow is opening.
+ super.onSubMenuSelected(null);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Hide the overflow menu if it is currently showing.
+ *
+ * @return true if the overflow menu was hidden, false otherwise.
+ */
+ public boolean hideOverflowMenu() {
+ if (mPostedOpenRunnable != null && mMenuView != null) {
+ ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+ mPostedOpenRunnable = null;
+ return true;
+ }
+
+ MenuPopupHelper popup = mOverflowPopup;
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ boolean result = hideOverflowMenu();
+ result |= hideSubMenus();
+ return result;
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mActionButtonPopup != null) {
+ mActionButtonPopup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mOverflowPopup != null && mOverflowPopup.isShowing();
+ }
+
+ /**
+ * @return true if space has been reserved in the action menu for an overflow item.
+ */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ int requestedItems = 0;
+ int firstActionWidth = 0;
+ boolean hasOverflow = false;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ } else if (item.requestsActionButton()) {
+ requestedItems++;
+ } else {
+ hasOverflow = true;
+ }
+ if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
+ // Overflow everything if we have an expanded action view and we're
+ // space constrained.
+ maxActions = 0;
+ }
+ }
+
+ // Reserve a spot for the overflow item if needed.
+ if (mReserveOverflow &&
+ (hasOverflow || requiredItems + requestedItems > maxActions)) {
+ maxActions--;
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ int cellSize = 0;
+ int cellsRemaining = 0;
+ if (mStrictWidthLimit) {
+ cellsRemaining = widthLimit / mMinCellSize;
+ final int cellSizeRemaining = widthLimit % mMinCellSize;
+ cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
+ }
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ cellsRemaining -= ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ item.setIsActionButton(true);
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
+ (!mStrictWidthLimit || cellsRemaining > 0);
+
+ if (isAction) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ final int cells = ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ cellsRemaining -= cells;
+ if (cells == 0) {
+ isAction = false;
+ }
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ if (mStrictWidthLimit) {
+ isAction &= widthLimit >= 0;
+ } else {
+ // Did this push the entire first item past the limit?
+ isAction &= widthLimit + firstActionWidth > 0;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ // Give back the action slot
+ if (areYouMyGroupie.isActionButton()) maxActions++;
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ if (isAction) maxActions--;
+
+ item.setIsActionButton(isAction);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState();
+ state.openSubMenuId = mOpenSubMenuId;
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState saved = (SavedState) state;
+ if (saved.openSubMenuId > 0) {
+ MenuItem item = mMenu.findItem(saved.openSubMenuId);
+ if (item != null) {
+ SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ onSubMenuSelected(subMenu);
+ }
+ }
+ }
+
+ @Override
+ public void onSubUiVisibilityChanged(boolean isVisible) {
+ if (isVisible) {
+ // Not a submenu, but treat it like one.
+ super.onSubMenuSelected(null);
+ } else {
+ mMenu.close(false);
+ }
+ }
+
+ private static class SavedState implements Parcelable {
+ public int openSubMenuId;
+
+ SavedState() {
+ }
+
+ SavedState(Parcel in) {
+ openSubMenuId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(openSubMenuId);
+ }
+
+ @SuppressWarnings("unused")
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private class OverflowMenuButton extends ImageButton implements ActionMenuChildView, View_HasStateListenerSupport {
+ private final Set<View_OnAttachStateChangeListener> mListeners = new HashSet<View_OnAttachStateChangeListener>();
+
+ public OverflowMenuButton(Context context) {
+ super(context, null, R.attr.actionOverflowButtonStyle);
+
+ setClickable(true);
+ setFocusable(true);
+ setVisibility(VISIBLE);
+ setEnabled(true);
+ }
+
+ @Override
+ public boolean performClick() {
+ if (super.performClick()) {
+ return true;
+ }
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+ showOverflowMenu();
+ return true;
+ }
+
+ public boolean needsDividerBefore() {
+ return false;
+ }
+
+ public boolean needsDividerAfter() {
+ return false;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ for (View_OnAttachStateChangeListener listener : mListeners) {
+ listener.onViewDetachedFromWindow(this);
+ }
+ }
+
+ @Override
+ public void addOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnAttachStateChangeListener(View_OnAttachStateChangeListener listener) {
+ mListeners.remove(listener);
+ }
+ }
+
+ private class OverflowPopup extends MenuPopupHelper {
+ public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly) {
+ super(context, menu, anchorView, overflowOnly);
+ setCallback(mPopupPresenterCallback);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mMenu.close();
+ mOverflowPopup = null;
+ }
+ }
+
+ private class ActionButtonSubmenu extends MenuPopupHelper {
+ //UNUSED private SubMenuBuilder mSubMenu;
+
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+ super(context, subMenu);
+ //UNUSED mSubMenu = subMenu;
+
+ MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+ if (!item.isActionButton()) {
+ // Give a reasonable anchor to nested submenus.
+ setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+ }
+
+ setCallback(mPopupPresenterCallback);
+
+ boolean preserveIconSpacing = false;
+ final int count = subMenu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = subMenu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+ setForceShowIcon(preserveIconSpacing);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mActionButtonPopup = null;
+ mOpenSubMenuId = 0;
+ }
+ }
+
+ private class PopupPresenterCallback implements MenuPresenter.Callback {
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null) return false;
+
+ mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (menu instanceof SubMenuBuilder) {
+ ((SubMenuBuilder) menu).getRootMenu().close(false);
+ }
+ }
+ }
+
+ private class OpenOverflowRunnable implements Runnable {
+ private OverflowPopup mPopup;
+
+ public OpenOverflowRunnable(OverflowPopup popup) {
+ mPopup = popup;
+ }
+
+ public void run() {
+ mMenu.changeMenuMode();
+ final View menuView = (View) mMenuView;
+ if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
+ mOverflowPopup = mPopup;
+ }
+ mPostedOpenRunnable = null;
+ }
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 000000000..6da26f2ae
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import java.util.ArrayList;
+import android.content.Context;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+ private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+
+ protected Context mSystemContext;
+ protected Context mContext;
+ protected MenuBuilder mMenu;
+ protected LayoutInflater mSystemInflater;
+ protected LayoutInflater mInflater;
+ private Callback mCallback;
+
+ private int mMenuLayoutRes;
+ private int mItemLayoutRes;
+
+ protected MenuView mMenuView;
+
+ private int mId;
+
+ /**
+ * Construct a new BaseMenuPresenter.
+ *
+ * @param context Context for generating system-supplied views
+ * @param menuLayoutRes Layout resource ID for the menu container view
+ * @param itemLayoutRes Layout resource ID for a single item view
+ */
+ public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) {
+ mSystemContext = context;
+ mSystemInflater = LayoutInflater.from(context);
+ mMenuLayoutRes = menuLayoutRes;
+ mItemLayoutRes = itemLayoutRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false);
+ mMenuView.initialize(mMenu);
+ updateMenuView(true);
+ }
+
+ return mMenuView;
+ }
+
+ /**
+ * Reuses item views when it can
+ */
+ public void updateMenuView(boolean cleared) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return;
+
+ int childIndex = 0;
+ if (mMenu != null) {
+ mMenu.flagActionItems();
+ ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemCount = visibleItems.size();
+ for (int i = 0; i < itemCount; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (shouldIncludeItem(childIndex, item)) {
+ final View convertView = parent.getChildAt(childIndex);
+ final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
+ ((MenuView.ItemView) convertView).getItemData() : null;
+ final View itemView = getItemView(item, convertView, parent);
+ if (item != oldItem) {
+ // Don't let old states linger with new data.
+ itemView.setPressed(false);
+ if (IS_HONEYCOMB) itemView.jumpDrawablesToCurrentState();
+ }
+ if (itemView != convertView) {
+ addItemView(itemView, childIndex);
+ }
+ childIndex++;
+ }
+ }
+ }
+
+ // Remove leftover views.
+ while (childIndex < parent.getChildCount()) {
+ if (!filterLeftoverView(parent, childIndex)) {
+ childIndex++;
+ }
+ }
+ }
+
+ /**
+ * Add an item view at the given index.
+ *
+ * @param itemView View to add
+ * @param childIndex Index within the parent to insert at
+ */
+ protected void addItemView(View itemView, int childIndex) {
+ final ViewGroup currentParent = (ViewGroup) itemView.getParent();
+ if (currentParent != null) {
+ currentParent.removeView(itemView);
+ }
+ ((ViewGroup) mMenuView).addView(itemView, childIndex);
+ }
+
+ /**
+ * Filter the child view at index and remove it if appropriate.
+ * @param parent Parent to filter from
+ * @param childIndex Index to filter
+ * @return true if the child view at index was removed
+ */
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ parent.removeViewAt(childIndex);
+ return true;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Create a new item view that can be re-bound to other item data later.
+ *
+ * @return The new item view
+ */
+ public MenuView.ItemView createItemView(ViewGroup parent) {
+ return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ /**
+ * Prepare an item view for use. See AdapterView for the basic idea at work here.
+ * This may require creating a new item view, but well-behaved implementations will
+ * re-use the view passed as convertView if present. The returned view will be populated
+ * with data from the item parameter.
+ *
+ * @param item Item to present
+ * @param convertView Existing view to reuse
+ * @param parent Intended parent view - use for inflation.
+ * @return View that presents the requested menu item
+ */
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ MenuView.ItemView itemView;
+ if (convertView instanceof MenuView.ItemView) {
+ itemView = (MenuView.ItemView) convertView;
+ } else {
+ itemView = createItemView(parent);
+ }
+ bindItemView(item, itemView);
+ return (View) itemView;
+ }
+
+ /**
+ * Bind item data to an existing item view.
+ *
+ * @param item Item to bind
+ * @param itemView View to populate with item data
+ */
+ public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+ /**
+ * Filter item by child index and item data.
+ *
+ * @param childIndex Indended presentation index of this item
+ * @param item Item to present
+ * @return true if this item should be included in this menu presentation; false otherwise
+ */
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return true;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder menu) {
+ if (mCallback != null) {
+ return mCallback.onOpenSubMenu(menu);
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setId(int id) {
+ mId = id;
+ }
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java
new file mode 100644
index 000000000..c3f35472c
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.os.Parcelable;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+ /**
+ * Called by menu implementation to notify another component of open/close events.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu is closing.
+ * @param menu
+ * @param allMenusAreClosing
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called when a submenu opens. Useful for notifying the application
+ * of menu state so that it does not attempt to hide the action bar
+ * while a submenu is open or similar.
+ *
+ * @param subMenu Submenu currently being opened
+ * @return true if the Callback will handle presenting the submenu, false if
+ * the presenter should attempt to do so.
+ */
+ public boolean onOpenSubMenu(MenuBuilder subMenu);
+ }
+
+ /**
+ * Initialize this presenter for the given context and menu.
+ * This method is called by MenuBuilder when a presenter is
+ * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+ *
+ * @param context Context for this presenter; used for view creation and resource management
+ * @param menu Menu to host
+ */
+ public void initForMenu(Context context, MenuBuilder menu);
+
+ /**
+ * Retrieve a MenuView to display the menu specified in
+ * {@link #initForMenu(Context, Menu)}.
+ *
+ * @param root Intended parent of the MenuView.
+ * @return A freshly created MenuView.
+ */
+ public MenuView getMenuView(ViewGroup root);
+
+ /**
+ * Update the menu UI in response to a change. Called by
+ * MenuBuilder during the normal course of operation.
+ *
+ * @param cleared true if the menu was entirely cleared
+ */
+ public void updateMenuView(boolean cleared);
+
+ /**
+ * Set a callback object that will be notified of menu events
+ * related to this specific presentation.
+ * @param cb Callback that will be notified of future events
+ */
+ public void setCallback(Callback cb);
+
+ /**
+ * Called by Menu implementations to indicate that a submenu item
+ * has been selected. An active Callback should be notified, and
+ * if applicable the presenter should present the submenu.
+ *
+ * @param subMenu SubMenu being opened
+ * @return true if the the event was handled, false otherwise.
+ */
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+ /**
+ * Called by Menu implementations to indicate that a menu or submenu is
+ * closing. Presenter implementations should close the representation
+ * of the menu indicated as necessary and notify a registered callback.
+ *
+ * @param menu Menu or submenu that is closing.
+ * @param allMenusAreClosing True if all associated menus are closing.
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called by Menu implementations to flag items that will be shown as actions.
+ * @return true if this presenter changed the action status of any items.
+ */
+ public boolean flagActionItems();
+
+ /**
+ * Called when a menu item with a collapsable action view should expand its action view.
+ *
+ * @param menu Menu containing the item to be expanded
+ * @param item Item to be expanded
+ * @return true if this presenter expanded the action view, false otherwise.
+ */
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Called when a menu item with a collapsable action view should collapse its action view.
+ *
+ * @param menu Menu containing the item to be collapsed
+ * @param item Item to be collapsed
+ * @return true if this presenter collapsed the action view, false otherwise.
+ */
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item);
+
+ /**
+ * Returns an ID for determining how to save/restore instance state.
+ * @return a valid ID value.
+ */
+ public int getId();
+
+ /**
+ * Returns a Parcelable describing the current state of the presenter.
+ * It will be passed to the {@link #onRestoreInstanceState(Parcelable)}
+ * method of the presenter sharing the same ID later.
+ * @return The saved instance state
+ */
+ public Parcelable onSaveInstanceState();
+
+ /**
+ * Supplies the previously saved instance state to be restored.
+ * @param state The previously saved instance state
+ */
+ public void onRestoreInstanceState(Parcelable state);
+}
diff --git a/actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java b/actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java
new file mode 100644
index 000000000..1c02d4aca
--- /dev/null
+++ b/actionbarsherlock/src/com/actionbarsherlock/internal/widget/IcsProgressBar.java
@@ -0,0 +1,1193 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.actionbarsherlock.internal.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+import android.widget.RemoteViews.RemoteView;
+
+
+/**
+ * <p>
+ * Visual indicator of progress in some operation. Displays a bar to the user
+ * representing how far the operation has progressed; the application can
+ * change the amount of progress (modifying the length of the bar) as it moves
+ * forward. There is also a secondary progress displayable on a progress bar
+ * which is useful for displaying intermediate progress, such as the buffer
+ * level during a streaming playback progress bar.
+ * </p>
+ *
+ * <p>
+ * A progress bar can also be made indeterminate. In indeterminate mode, the
+ * progress bar shows a cyclic animation without an indication of progress. This mode is used by
+ * applications when the length of the task is unknown. The indeterminate progress bar can be either
+ * a spinning wheel or a horizontal bar.
+ * </p>
+ *
+ * <p>The following code example shows how a progress bar can be used from
+ * a worker thread to update the user interface to notify the user of progress:
+ * </p>
+ *
+ * <pre>
+ * public class MyActivity extends Activity {
+ * private static final int PROGRESS = 0x1;
+ *
+ * private ProgressBar mProgress;
+ * private int mProgressStatus = 0;
+ *
+ * private Handler mHandler = new Handler();
+ *
+ * protected void onCreate(Bundle icicle) {
+ * super.onCreate(icicle);
+ *
+ * setContentView(R.layout.progressbar_activity);
+ *
+ * mProgress = (ProgressBar) findViewById(R.id.progress_bar);
+ *
+ * // Start lengthy operation in a background thread
+ * new Thread(new Runnable() {
+ * public void run() {
+ * while (mProgressStatus &lt; 100) {
+ * mProgressStatus = doWork();
+ *
+ * // Update the progress bar
+ * mHandler.post(new Runnable() {
+ * public void run() {
+ * mProgress.setProgress(mProgressStatus);
+ * }
+ * });
+ * }
+ * }
+ * }).start();
+ * }
+ * }</pre>
+ *
+ * <p>To add a progress bar to a layout file, you can use the {@code &lt;ProgressBar&gt;} element.
+ * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
+ * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
+ * Widget.ProgressBar.Horizontal} style, like so:</p>
+ *
+ * <pre>
+ * &lt;ProgressBar
+ * style="@android:style/Widget.ProgressBar.Horizontal"
+ * ... /&gt;</pre>
+ *
+ * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
+ * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or
+ * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
+ * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
+ * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
+ * below.</p>
+ *
+ * <p>Another common style to apply to the progress bar is {@link
+ * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
+ * version of the spinning wheel&mdash;useful when waiting for content to load.
+ * For example, you can insert this kind of progress bar into your default layout for
+ * a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
+ * appears immediately and when your application receives the content, it replaces the progress bar
+ * with the loaded content. For example:</p>
+ *
+ * <pre>
+ * &lt;LinearLayout
+ * android:orientation="horizontal"
+ * ... &gt;
+ * &lt;ProgressBar
+ * android:layout_width="wrap_content"
+ * android:layout_height="wrap_content"
+ * style="@android:style/Widget.ProgressBar.Small"
+ * android:layout_marginRight="5dp" /&gt;
+ * &lt;TextView
+ * android:layout_width="wrap_content"
+ * android:layout_height="wrap_content"
+ * android:text="@string/loading" /&gt;
+ * &lt;/LinearLayout&gt;</pre>
+ *
+ * <p>Other progress bar styles provided by the system include:</p>
+ * <ul>
+ * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
+ * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
+ * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
+ * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
+ * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
+ * Widget.ProgressBar.Small.Inverse}</li>
+ * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
+ * Widget.ProgressBar.Large.Inverse}</li>
+ * </ul>
+ * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
+ * if your application uses a light colored theme (a white background).</p>
+ *
+ * <p><strong>XML attributes</b></strong>
+ * <p>
+ * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
+ * {@link android.R.styleable#View View Attributes}
+ * </p>
+ *
+ * @attr ref android.R.styleable#ProgressBar_animationResolution
+ * @attr ref android.R.styleable#ProgressBar_indeterminate
+ * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
+ * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
+ * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
+ * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
+ * @attr ref android.R.styleable#ProgressBar_interpolator
+ * @attr ref android.R.styleable#ProgressBar_max
+ * @attr ref android.R.styleable#ProgressBar_maxHeight
+ * @attr ref android.R.styleable#ProgressBar_maxWidth
+ * @attr ref android.R.styleable#ProgressBar_minHeight
+ * @attr ref android.R.styleable#ProgressBar_minWidth
+ * @attr ref android.R.styleable#ProgressBar_progress
+ * @attr ref android.R.styleable#ProgressBar_progressDrawable
+ * @attr ref android.R.styleable#ProgressBar_secondaryProgress
+ */
+@RemoteView
+public class IcsProgressBar extends View {
+ private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+ private static final int MAX_LEVEL = 10000;
+ private static final int ANIMATION_RESOLUTION = 200;
+ private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
+
+ private static final int[] ProgressBar = new int[] {
+ android.R.attr.maxWidth,
+ android.R.attr.maxHeight,
+ android.R.attr.max,
+ android.R.attr.progress,
+ android.R.attr.secondaryProgress,
+ android.R.attr.indeterminate,
+ android.R.attr.indeterminateOnly,
+ android.R.attr.indeterminateDrawable,
+ android.R.attr.progressDrawable,
+ android.R.attr.indeterminateDuration,
+ android.R.attr.indeterminateBehavior,
+ android.R.attr.minWidth,
+ android.R.attr.minHeight,
+ android.R.attr.interpolator,
+ android.R.attr.animationResolution,
+ };
+ private static final int ProgressBar_maxWidth = 0;
+ private static final int ProgressBar_maxHeight = 1;
+ private static final int ProgressBar_max = 2;
+ private static final int ProgressBar_progress = 3;
+ private static final int ProgressBar_secondaryProgress = 4;
+ private static final int ProgressBar_indeterminate = 5;
+ private static final int ProgressBar_indeterminateOnly = 6;
+ private static final int ProgressBar_indeterminateDrawable = 7;
+ private static final int ProgressBar_progressDrawable = 8;
+ private static final int ProgressBar_indeterminateDuration = 9;
+ private static final int ProgressBar_indeterminateBehavior = 10;
+ private static final int ProgressBar_minWidth = 11;
+ private static final int ProgressBar_minHeight = 12;
+ private static final int ProgressBar_interpolator = 13;
+ private static final int ProgressBar_animationResolution = 14;
+
+ int mMinWidth;
+ int mMaxWidth;
+ int mMinHeight;
+ int mMaxHeight;
+
+ private int mProgress;
+ private int mSecondaryProgress;
+ private int mMax;
+
+ private int mBehavior;
+ private int mDuration;
+ private boolean mIndeterminate;
+ private boolean mOnlyIndeterminate;
+ private Transformation mTransformation;
+ private AlphaAnimation mAnimation;
+ private Drawable mIndeterminateDrawable;
+ private int mIndeterminateRealLeft;
+ private int mIndeterminateRealTop;
+ private Drawable mProgressDrawable;
+ private Drawable mCurrentDrawable;
+ Bitmap mSampleTile;
+ private boolean mNoInvalidate;
+ private Interpolator mInterpolator;
+ private RefreshProgressRunnable mRefreshProgressRunnable;
+ private long mUiThreadId;
+ private boolean mShouldStartAnimationDrawable;
+ private long mLastDrawTime;
+
+ private boolean mInDrawing;
+
+ private int mAnimationResolution;
+
+ private AccessibilityManager mAccessibilityManager;
+ private AccessibilityEventSender mAccessibilityEventSender;
+
+ /**
+ * Create a new progress bar with range 0...100 and initial progress of 0.
+ * @param context the application environment
+ */
+ public IcsProgressBar(Context context) {
+ this(context, null);
+ }
+
+ public IcsProgressBar(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.progressBarStyle);
+ }
+
+ public IcsProgressBar(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ /**
+ * @hide
+ */
+ public IcsProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
+ super(context, attrs, defStyle);
+ mUiThreadId = Thread.currentThread().getId();
+ initProgressBar();
+
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, /*R.styleable.*/ProgressBar, defStyle, styleRes);
+
+ mNoInvalidate = true;
+
+ Drawable drawable = a.getDrawable(/*R.styleable.*/ProgressBar_progressDrawable);
+ if (drawable != null) {
+ drawable = tileify(drawable, false);
+ // Calling this method can set mMaxHeight, make sure the corresponding
+ // XML attribute for mMaxHeight is read after calling this method
+ setProgressDrawable(drawable);
+ }
+
+
+ mDuration = a.getInt(/*R.styleable.*/ProgressBar_indeterminateDuration, mDuration);
+
+ mMinWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minWidth, mMinWidth);
+ mMaxWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxWidth, mMaxWidth);
+ mMinHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minHeight, mMinHeight);
+ mMaxHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxHeight, mMaxHeight);
+
+ mBehavior = a.getInt(/*R.styleable.*/ProgressBar_indeterminateBehavior, mBehavior);
+
+ final int resID = a.getResourceId(
+ /*com.android.internal.R.styleable.*/ProgressBar_interpolator,
+ android.R.anim.linear_interpolator); // default to linear interpolator
+ if (resID > 0) {
+ setInterpolator(context, resID);
+ }
+
+ setMax(a.getInt(/*R.styleable.*/ProgressBar_max, mMax));
+
+ setProgress(a.getInt(/*R.styleable.*/ProgressBar_progress, mProgress));
+
+ setSecondaryProgress(
+ a.getInt(/*R.styleable.*/ProgressBar_secondaryProgress, mSecondaryProgress));
+
+ drawable = a.getDrawable(/*R.styleable.*/ProgressBar_indeterminateDrawable);
+ if (drawable != null) {
+ drawable = tileifyIndeterminate(drawable);
+ setIndeterminateDrawable(drawable);
+ }
+
+ mOnlyIndeterminate = a.getBoolean(
+ /*R.styleable.*/ProgressBar_indeterminateOnly, mOnlyIndeterminate);
+
+ mNoInvalidate = false;
+
+ setIndeterminate(mOnlyIndeterminate || a.getBoolean(
+ /*R.styleable.*/ProgressBar_indeterminate, mIndeterminate));
+
+ mAnimationResolution = a.getInteger(/*R.styleable.*/ProgressBar_animationResolution,
+ ANIMATION_RESOLUTION);
+
+ a.recycle();
+
+ mAccessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ /**
+ * Converts a drawable to a tiled version of itself. It will recursively
+ * traverse layer and state list drawables.
+ */
+ private Drawable tileify(Drawable drawable, boolean clip) {
+
+ if (drawable instanceof LayerDrawable) {
+ LayerDrawable background = (LayerDrawable) drawable;
+ final int N = background.getNumberOfLayers();
+ Drawable[] outDrawables = new Drawable[N];
+
+ for (int i = 0; i < N; i++) {
+ int id = background.getId(i);
+ outDrawables[i] = tileify(background.getDrawable(i),
+ (id == android.R.id.progress || id == android.R.id.secondaryProgress));
+ }
+
+ LayerDrawable newBg = new LayerDrawable(outDrawables);
+
+ for (int i = 0; i < N; i++) {
+ newBg.setId(i, background.getId(i));
+ }
+
+ return newBg;
+
+ }/* else if (drawable instanceof StateListDrawable) {
+ StateListDrawable in = (StateListDrawable) drawable;
+ StateListDrawable out = new StateListDrawable();
+ int numStates = in.getStateCount();
+ for (int i = 0; i < numStates; i++) {
+ out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
+ }
+ return out;
+
+ }*/ else if (drawable instanceof BitmapDrawable) {
+ final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+ if (mSampleTile == null) {
+ mSampleTile = tileBitmap;
+ }
+
+ final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+
+ final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
+ Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+ shapeDrawable.getPaint().setShader(bitmapShader);
+
+ return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
+ ClipDrawable.HORIZONTAL) : shapeDrawable;
+ }
+
+ return drawable;
+ }
+
+ Shape getDrawableShape() {
+ final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
+ return new RoundRectShape(roundedCorners, null, null);
+ }
+
+ /**
+ * Convert a AnimationDrawable for use as a barberpole animation.
+ * Each frame of the animation is wrapped in a ClipDrawable and
+ * given a tiling BitmapShader.
+ */
+ private Drawable tileifyIndeterminate(Drawable drawable) {
+ if (drawable instanceof AnimationDrawable) {
+ AnimationDrawable background = (AnimationDrawable) drawable;
+ final int N = background.getNumberOfFrames();
+ AnimationDrawable newBg = new AnimationDrawable();
+ newBg.setOneShot(background.isOneShot());
+
+ for (int i = 0; i < N; i++) {
+ Drawable frame = tileify(background.getFrame(i), true);
+ frame.setLevel(10000);
+ newBg.addFrame(frame, background.getDuration(i));
+ }
+ newBg.setLevel(10000);
+ drawable = newBg;
+ }
+ return drawable;
+ }
+
+ /**
+ * <p>
+ * Initialize the progress bar's default values:
+ * </p>
+ * <ul>
+ * <li>progress = 0</li>
+ * <li>max = 100</li>
+ * <li>animation duration = 4000 ms</li>
+ * <li>indeterminate = false</li>
+ * <li>behavior = repeat</li>
+ * </ul>
+ */
+ private void initProgressBar() {
+ mMax = 100;
+ mProgress = 0;
+ mSecondaryProgress = 0;
+ mIndeterminate = false;
+ mOnlyIndeterminate = false;
+ mDuration = 4000;
+ mBehavior = AlphaAnimation.RESTART;
+ mMinWidth = 24;
+ mMaxWidth = 48;
+ mMinHeight = 24;
+ mMaxHeight = 48;
+ }
+
+ /**
+ * <p>Indicate whether this progress bar is in indeterminate mode.</p>
+ *
+ * @return true if the progress bar is in indeterminate mode
+ */
+ @ViewDebug.ExportedProperty(category = "progress")
+ public synchronized boolean isIndeterminate() {
+ return mIndeterminate;
+ }
+
+ /**
+ * <p>Change the indeterminate mode for this progress bar. In indeterminate
+ * mode, the progress is ignored and the progress bar shows an infinite
+ * animation instead.</p>
+ *
+ * If this progress bar's style only supports indeterminate mode (such as the circular
+ * progress bars), then this will be ignored.
+ *
+ * @param indeterminate true to enable the indeterminate mode
+ */
+ public synchronized void setIndeterminate(boolean indeterminate) {
+ if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
+ mIndeterminate = indeterminate;
+
+ if (indeterminate) {
+ // swap between indeterminate and regular backgrounds
+ mCurrentDrawable = mIndeterminateDrawable;
+ startAnimation();
+ } else {
+ mCurrentDrawable = mProgressDrawable;
+ stopAnimation();
+ }
+ }
+ }
+
+ /**
+ * <p>Get the drawable used to draw the progress bar in
+ * indeterminate mode.</p>
+ *
+ * @return a {@link android.graphics.drawable.Drawable} instance
+ *
+ * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
+ * @see #setIndeterminate(boolean)
+ */
+ public Drawable getIndeterminateDrawable() {
+ return mIndeterminateDrawable;
+ }
+
+ /**
+ * <p>Define the drawable used to draw the progress bar in
+ * indeterminate mode.</p>
+ *
+ * @param d the new drawable
+ *
+ * @see #getIndeterminateDrawable()
+ * @see #setIndeterminate(boolean)
+ */
+ public void setIndeterminateDrawable(Drawable d) {
+ if (d != null) {
+ d.setCallback(this);
+ }
+ mIndeterminateDrawable = d;
+ if (mIndeterminate) {
+ mCurrentDrawable = d;
+ postInvalidate();
+ }
+ }
+
+ /**
+ * <p>Get the drawable used to draw the progress bar in
+ * progress mode.</p>
+ *
+ * @return a {@link android.graphics.drawable.Drawable} instance
+ *
+ * @see #setProgressDrawable(android.graphics.drawable.Drawable)
+ * @see #setIndeterminate(boolean)
+ */
+ public Drawable getProgressDrawable() {
+ return mProgressDrawable;
+ }
+
+ /**
+ * <p>Define the drawable used to draw the progress bar in
+ * progress mode.</p>
+ *
+ * @param d the new drawable
+ *
+ * @see #getProgressDrawable()
+ * @see #setIndeterminate(boolean)
+ */
+ public void setProgressDrawable(Drawable d) {
+ boolean needUpdate;
+ if (mProgressDrawable != null && d != mProgressDrawable) {
+ mProgressDrawable.setCallback(null);
+ needUpdate = true;
+ } else {
+ needUpdate = false;
+ }
+
+ if (d != null) {
+ d.setCallback(this);
+
+ // Make sure the ProgressBar is always tall enough
+ int drawableHeight = d.getMinimumHeight();
+ if (mMaxHeight < drawableHeight) {
+ mMaxHeight = drawableHeight;
+ requestLayout();
+ }
+ }
+ mProgressDrawable = d;
+ if (!mIndeterminate) {
+ mCurrentDrawable = d;
+ postInvalidate();
+ }
+
+ if (needUpdate) {
+ updateDrawableBounds(getWidth(), getHeight());
+ updateDrawableState();
+ doRefreshProgress(android.R.id.progress, mProgress, false, false);
+ doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
+ }
+ }
+
+ /**
+ * @return The drawable currently used to draw the progress bar
+ */
+ Drawable getCurrentDrawable() {
+ return mCurrentDrawable;
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mProgressDrawable || who == mIndeterminateDrawable
+ || super.verifyDrawable(who);
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
+ if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
+ }
+
+ @Override
+ public void postInvalidate() {
+ if (!mNoInvalidate) {
+ super.postInvalidate();
+ }
+ }
+
+ private class RefreshProgressRunnable implements Runnable {
+
+ private int mId;
+ private int mProgress;
+ private boolean mFromUser;
+
+ RefreshProgressRunnable(int id, int progress, boolean fromUser) {
+ mId = id;
+ mProgress = progress;
+ mFromUser = fromUser;
+ }
+
+ public void run() {
+ doRefreshProgress(mId, mProgress, mFromUser, true);
+ // Put ourselves back in the cache when we are done
+ mRefreshProgressRunnable = this;
+ }
+
+ public void setup(int id, int progress, boolean fromUser) {
+ mId = id;
+ mProgress = progress;
+ mFromUser = fromUser;
+ }
+
+ }
+
+ private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
+ boolean callBackToApp) {
+ float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
+ final Drawable d = mCurrentDrawable;
+ if (d != null) {
+ Drawable progressDrawable = null;
+
+ if (d instanceof LayerDrawable) {
+ progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
+ }
+
+ final int level = (int) (scale * MAX_LEVEL);
+ (progressDrawable != null ? progressDrawable : d).setLevel(level);
+ } else {
+ invalidate();
+ }
+
+ if (callBackToApp && id == android.R.id.progress) {
+ onProgressRefresh(scale, fromUser);
+ }
+ }
+
+ void onProgressRefresh(float scale, boolean fromUser) {
+ if (mAccessibilityManager.isEnabled()) {
+ scheduleAccessibilityEventSender();
+ }
+ }
+
+ private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
+ if (mUiThreadId == Thread.currentThread().getId()) {
+ doRefreshProgress(id, progress, fromUser, true);
+ } else {
+ RefreshProgressRunnable r;
+ if (mRefreshProgressRunnable != null) {
+ // Use cached RefreshProgressRunnable if available
+ r = mRefreshProgressRunnable;
+ // Uncache it
+ mRefreshProgressRunnable = null;
+ r.setup(id, progress, fromUser);
+ } else {
+ // Make a new one
+ r = new RefreshProgressRunnable(id, progress, fromUser);
+ }
+ post(r);
+ }
+ }
+
+ /**
+ * <p>Set the current progress to the specified value. Does not do anything
+ * if the progress bar is in indeterminate mode.</p>
+ *
+ * @param progress the new progress, between 0 and {@link #getMax()}
+ *
+ * @see #setIndeterminate(boolean)
+ * @see #isIndeterminate()
+ * @see #getProgress()
+ * @see #incrementProgressBy(int)
+ */
+ public synchronized void setProgress(int progress) {
+ setProgress(progress, false);
+ }
+
+ synchronized void setProgress(int progress, boolean fromUser) {
+ if (mIndeterminate) {
+ return;
+ }
+
+ if (progress < 0) {
+ progress = 0;
+ }
+
+ if (progress > mMax) {
+ progress = mMax;
+ }
+
+ if (progress != mProgress) {
+ mProgress = progress;
+ refreshProgress(android.R.id.progress, mProgress, fromUser);
+ }
+ }
+
+ /**
+ * <p>
+ * Set the current secondary progress to the specified value. Does not do
+ * anything if the progress bar is in indeterminate mode.
+ * </p>
+ *
+ * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
+ * @see #setIndeterminate(boolean)
+ * @see #isIndeterminate()
+ * @see #getSecondaryProgress()
+ * @see #incrementSecondaryProgressBy(int)
+ */
+ public synchronized void setSecondaryProgress(int secondaryProgress) {
+ if (mIndeterminate) {
+ return;
+ }
+
+ if (secondaryProgress < 0) {
+ secondaryProgress = 0;
+ }
+
+ if (secondaryProgress > mMax) {
+ secondaryProgress = mMax;
+ }
+
+ if (secondaryProgress != mSecondaryProgress) {
+ mSecondaryProgress = secondaryProgress;
+ refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
+ }
+ }
+
+ /**
+ * <p>Get the progress bar's current level of progress. Return 0 when the
+ * progress bar is in indeterminate mode.</p>
+ *
+ * @return the current progress, between 0 and {@link #getMax()}
+ *
+ * @see #setIndeterminate(boolean)
+ * @see #isIndeterminate()
+ * @see #setProgress(int)
+ * @see #setMax(int)
+ * @see #getMax()
+ */
+ @ViewDebug.ExportedProperty(category = "progress")
+ public synchronized int getProgress() {
+ return mIndeterminate ? 0 : mProgress;
+ }
+
+ /**
+ * <p>Get the progress bar's current level of secondary progress. Return 0 when the
+ * progress bar is in indeterminate mode.</p>
+ *
+ * @return the current secondary progress, between 0 and {@link #getMax()}
+ *
+ * @see #setIndeterminate(boolean)
+ * @see #isIndeterminate()
+ * @see #setSecondaryProgress(int)
+ * @see #setMax(int)
+ * @see #getMax()
+ */
+ @ViewDebug.ExportedProperty(category = "progress")
+ public synchronized int getSecondaryProgress() {
+ return mIndeterminate ? 0 : mSecondaryProgress;
+ }
+
+ /**
+ * <p>Return the upper limit of this progress bar's range.</p>
+ *
+ * @return a positive integer
+ *
+ * @see #setMax(int)
+ * @see #getProgress()
+ * @see #getSecondaryProgress()
+ */
+ @ViewDebug.ExportedProperty(category = "progress")
+ public synchronized int getMax() {
+ return mMax;
+ }
+
+ /**
+ * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
+ *
+ * @param max the upper range of this progress bar
+ *
+ * @see #getMax()
+ * @see #setProgress(int)
+ * @see #setSecondaryProgress(int)
+ */
+ public synchronized void setMax(int max) {
+ if (max < 0) {
+ max = 0;
+ }
+ if (max != mMax) {
+ mMax = max;
+ postInvalidate();
+
+ if (mProgress > max) {
+ mProgress = max;
+ }
+ refreshProgress(android.R.id.progress, mProgress, false);
+ }
+ }
+
+ /**
+ * <p>Increase the progress bar's progress by the specified amount.</p>
+ *
+ * @param diff the amount by which the progress must be increased
+ *
+ * @see #setProgress(int)
+ */
+ public synchronized final void incrementProgressBy(int diff) {
+ setProgress(mProgress + diff);
+ }
+
+ /**
+ * <p>Increase the progress bar's secondary progress by the specified amount.</p>
+ *
+ * @param diff the amount by which the secondary progress must be increased
+ *
+ * @see #setSecondaryProgress(int)
+ */
+ public synchronized final void incrementSecondaryProgressBy(int diff) {
+ setSecondaryProgress(mSecondaryProgress + diff);
+ }
+
+ /**
+ * <p>Start the indeterminate progress animation.</p>
+ */
+ void startAnimation() {
+ if (getVisibility() != VISIBLE) {
+ return;
+ }
+
+ if (mIndeterminateDrawable instanceof Animatable) {
+ mShouldStartAnimationDrawable = true;
+ mAnimation = null;
+ } else {
+ if (mInterpolator == null) {
+ mInterpolator = new LinearInterpolator();
+ }
+
+ mTransformation = new Transformation();
+ mAnimation = new AlphaAnimation(0.0f, 1.0f);
+ mAnimation.setRepeatMode(mBehavior);
+ mAnimation.setRepeatCount(Animation.INFINITE);
+ mAnimation.setDuration(mDuration);
+ mAnimation.setInterpolator(mInterpolator);
+ mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
+ }
+ postInvalidate();
+ }
+
+ /**
+ * <p>Stop the indeterminate progress animation.</p>
+ */
+ void stopAnimation() {
+ mAnimation = null;
+ mTransformation = null;
+ if (mIndeterminateDrawable instanceof Animatable) {
+ ((Animatable) mIndeterminateDrawable).stop();
+ mShouldStartAnimationDrawable = false;
+ }
+ postInvalidate();
+ }
+
+ /**
+ * Sets the acceleration curve for the indeterminate animation.
+ * The interpolator is loaded as a resource from the specified context.
+ *
+ * @param context The application environment
+ * @param resID The resource identifier of the interpolator to load
+ */
+ public void setInterpolator(Context context, int resID) {
+ setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+ }
+
+ /**
+ * Sets the acceleration curve for the indeterminate animation.
+ * Defaults to a linear interpolation.
+ *
+ * @param interpolator The interpolator which defines the acceleration curve
+ */
+ public void setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ }
+
+ /**
+ * Gets the acceleration curve type for the indeterminate animation.
+ *
+ * @return the {@link Interpolator} associated to this animation
+ */
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ @Override
+ public void setVisibility(int v) {
+ if (getVisibility() != v) {
+ super.setVisibility(v);
+
+ if (mIndeterminate) {
+ // let's be nice with the UI thread
+ if (v == GONE || v == INVISIBLE) {
+ stopAnimation();
+ } else {
+ startAnimation();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (mIndeterminate) {
+ // let's be nice with the UI thread
+ if (visibility == GONE || visibility == INVISIBLE) {
+ stopAnimation();
+ } else {
+ startAnimation();
+ }
+ }
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable dr) {
+ if (!mInDrawing) {
+ if (verifyDrawable(dr)) {
+ final Rect dirty = dr.getBounds();
+ final int scrollX = getScrollX() + getPaddingLeft();
+ final int scrollY = getScrollY() + getPaddingTop();
+
+ invalidate(dirty.left + scrollX, dirty.top + scrollY,
+ dirty.right + scrollX, dirty.bottom + scrollY);
+ } else {
+ super.invalidateDrawable(dr);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ *
+ @Override
+ public int getResolvedLayoutDirection(Drawable who) {
+ return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
+ getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who);
+ }
+ */
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ updateDrawableBounds(w, h);
+ }
+
+ private void updateDrawableBounds(int w, int h) {
+ // onDraw will translate the canvas so we draw starting at 0,0
+ int right = w - getPaddingRight() - getPaddingLeft();
+ int bottom = h - getPaddingBottom() - getPaddingTop();
+ int top = 0;
+ int left = 0;
+
+ if (mIndeterminateDrawable != null) {
+ // Aspect ratio logic does not apply to AnimationDrawables
+ if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
+ // Maintain aspect ratio. Certain kinds of animated drawables
+ // get very confused otherwise.
+ final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
+ final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
+ final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
+ final float boundAspect = (float) w / h;
+ if (intrinsicAspect != boundAspect) {
+ if (boundAspect > intrinsicAspect) {
+ // New width is larger. Make it smaller to match height.
+ final int width = (int) (h * intrinsicAspect);
+ left = (w - width) / 2;
+ right = left + width;
+ } else {
+ // New height is larger. Make it smaller to match width.
+ final int height = (int) (w * (1 / intrinsicAspect));
+ top = (h - height) / 2;
+ bottom = top + height;
+ }
+ }
+ }
+ mIndeterminateDrawable.setBounds(0, 0, right - left, bottom - top);
+ mIndeterminateRealLeft = left;
+ mIndeterminateRealTop = top;
+ }
+
+ if (mProgressDrawable != null) {
+ mProgressDrawable.setBounds(0, 0, right, bottom);
+ }
+ }
+
+ @Override
+ protected synchronized void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ Drawable d = mCurrentDrawable;
+ if (d != null) {
+ // Translate canvas so a indeterminate circular progress bar with padding
+ // rotates properly in its animation
+ canvas.save();
+ canvas.translate(getPaddingLeft() + mIndeterminateRealLeft, getPaddingTop() + mIndeterminateRealTop);
+ long time = getDrawingTime();
+ if (mAnimation != null) {
+ mAnimation.getTransformation(time, mTransformation);
+ float scale = mTransformation.getAlpha();
+ try {
+ mInDrawing = true;
+ d.setLevel((int) (scale * MAX_LEVEL));
+ } finally {
+ mInDrawing = false;
+ }
+ if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) {
+ mLastDrawTime = SystemClock.uptimeMillis();
+ postInvalidateDelayed(mAnimationResolution);
+ }
+ }
+ d.draw(canvas);
+ canvas.restore();
+ if (mShouldStartAnimationDrawable && d instanceof Animatable) {
+ ((Animatable) d).start();
+ mShouldStartAnimationDrawable = false;
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Drawable d = mCurrentDrawable;
+
+ int dw = 0;
+ int dh = 0;
+ if (d != null) {
+ dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
+ dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
+ }
+ updateDrawableState();
+ dw += getPaddingLeft() + getPaddingRight();
+ dh += getPaddingTop() + getPaddingBottom();
+
+ if (IS_HONEYCOMB) {
+ setMeasuredDimension(View.resolveSizeAndState(dw, widthMeasureSpec, 0),
+ View.resolveSizeAndState(dh, heightMeasureSpec, 0));
+ } else {
+ setMeasuredDimension(View.resolveSize(dw, widthMeasureSpec),
+ View.resolveSize(dh, heightMeasureSpec));
+ }
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ updateDrawableState();
+ }
+
+ private void updateDrawableState() {
+ int[] state = getDrawableState();
+
+ if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
+ mProgressDrawable.setState(state);
+ }
+
+ if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
+ mIndeterminateDrawable.setState(state);
+ }
+ }
+
+ static class SavedState extends BaseSavedState {
+ int progress;
+ int secondaryProgress;
+
+ /**
+ * Constructor called from {@link IcsProgressBar#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ progress = in.readInt();
+ secondaryProgress = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(progress);
+ out.writeInt(secondaryProgress);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ // Force our ancestor class to save its state
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+
+ ss.progress = mProgress;
+ ss.secondaryProgress = mSecondaryProgress;
+
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ setProgress(ss.progress);
+ setSecondaryProgress(ss.secondaryProgress);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mIndeterminate) {
+ startAnimation();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (mIndeterminate) {
+ stopAnimation();
+ }
+ if(mRefreshProgressRunnable != null) {
+ removeCallbacks(mRefreshProgressRunnable);
+ }
+ if (mAccessibilityEventSender != null) {
+ removeCallbacks(mAccessibilityEventSender);
+ }
+ // This should come after stopAnimation(), otherwise an invalidate message remains in the
+ // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setItemCount(mMax);
+ event.setCurrentItemIndex(mProgress);
+ }
+
+ /**
+ * Schedule a command for sending an accessibility event.
+ * </br>
+ * Note: A command is used to ensure that accessibility events
+ * are sent at most one in a given time frame to save
+ * system resources while the progress changes quickly.
+ */
+ private void scheduleAccessibilityEventSender() {
+ if (mAccessibilityEventSender == null) {
+ mAccessibilityEventSender = new AccessibilityEventSender();
+ } else {
+ removeCallbacks(mAccessibilityEventSender);
+ }
+ postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
+ }
+
+ /**
+ * Command for sending an accessibility event.
+ */
+ private class AccessibilityEventSender implements Runnable {
+ public void run() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
+}